Spring
Spring介绍
-
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
Spring优点
- DI(Dependency Injection)与IOC(Inverse Of Control)
- AOP(Aspect Oriented Programming)
- 与所有框架完美集成
- 使得JDBC/事务等更加方便
Spring容器和IOC与DI思想
Spring容器为我们管理对象的创建和销毁全过程
对于像jdbc或是连接池需要经历一系列的对象创建、使用和销毁过程的项目,在需求越来越大,对应的对象越来越多,整个过程如果通过我们自己去处理将会十分复杂.
Spring容器通过依赖注入,和自动装配(过程)实现控制反转(结果)
类与类之间的依赖关系若通过new构造方法创建对象的方式解决,耦合性太强.Spring获得创建对象的控制权,程序需要什么对象,只要去容器中取
- DI(Dependency Injection)依赖注入就是不通过原始的方法创建对象实例,而是通过自动装配(底层通过反射实现)依赖容器为程序注入对象,自动装配通过配置文件,反射来获取程序需要的对象类型、实例名
- IOC(Inverse Of Control)控制反转:将实例化对象的控制权交给Spring容器
Spring配置
IOCjar包介绍
jar包 | 概述 |
---|---|
Spring-core | Spring的核心工具包,spring的其它包都要用到该jar中的包 |
Spring-beans | 包含配置文件,bean的创建等,所有jar包都需要用到 |
Spring-context | 在上面两个jar完成基础IOC功能上提供扩展服务,此外还提供许多企业级服务的支持,有邮件服务、任务调度、JNDI定位,EJB集成、远程访问、缓存以及多种视图层框架的支持。 |
Spring-expression | Spring表达式语言,spel表达式,SpEL支持标准数学运算符,关系运算符,逻辑运算符,条件运算符,集合和正则表达式等。 |
Spring-context-support | Spring context的扩展支持,用于MVC方面,比如支持freemarker等模板引擎 |
Maven引入jar包
<properties>
<spring.version>5.1.5.RELEASE</spring.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source> <!-- 源代码使用jdk1.8支持的特性 -->
<target>1.8</target> <!-- 使用jvm1.8编译目标代码 -->
<compilerArgs> <!-- 传递参数 -->
<arg>-parameters</arg>
<arg>-Xlint:unchecked</arg>
<arg>-Xlint:deprecation </arg>
</compilerArgs>
</configuration>
</plugin>
日志配置
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
在resources文件夹中加入log4j.properties
log4j.rootCategory=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n
IOC配置文件
在resources文件夹中加入applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
Spring案例剖析
配置文件加入
-
创建Maven普通项目,添加jar包以及配置文件.
-
创建StudentService类,在applicationCotext中配置类信息
<bean id="studentService" class="com.edu.bean.StudentService"></bean>
-
创建测试,在鼠标点击类名按下
Alt
+Enter
选择Create Test
,最后在测试方法注解中去掉org.junit.
并导入junit包@Test public void show() { //1、加载容器,通过配置文件加载ApplicationContext,beanFactory(原始)(SpringIOC容器) //classpath:会在当前项目resources下面去找(编译后target下面找) //classpath*:查找除了在本项目包括其他项目的jar包的resources文件夹路径 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml"); //2、创建对象 StudentService studentService = (StudentService) context.getBean("studentService"); //3、调用对象方法 studentService.show(); //4、销毁 //classPathXmlApplicationContext.close(); context.registerShutdownHook();//更为优雅 }
bean标签属性详解
id/name
对象的唯一标识,两者性质一样,value
也不能相同
Scope
设置对象的实例类型
值 | 概述 |
---|---|
singleton | SpringIoc容器只会创建该Bean的唯一实例,所有的请求和引用都只使用这个实例 |
prototype | 每次请求都创建一个实例 |
request | 在一次Http请求中,容器会返回该Bean的同一个实例,而对于不同的用户请求,会返回不同的实例。需要注意的是,该作用域仅在基于Web的Spring ApplicationContex t情形下有效,以下的session和global Session也是如此 |
session | 同上,唯一的区别是请求的作用域变为了session |
global session | 全局的HttpSession 中 |
autowire:自动装配
为依赖的javabean设置自动装配的方式,在原对象中必须创建getter,setter(javabean规范),框架底层调用实现
值 | 概述 |
---|---|
no | 即不启用自动装配。autowire默认的值。 |
byName | 通过属性的名字的方式查找JavaBean依赖的对象并为其注入,使用Seter 方法为其注入。 |
byType | 通过属性的类型查找JavaBean依赖的对象并为其注入。使用Seter 方法为其注入 |
constructor | 通byType 一样,也是通过类型查找依赖对象。与byType的区别在于它不是使用Seter 方法注入,而是使用构造子注入 |
autodetect | 在byType和constructor之间自动的选择注入方式 |
default | 由上级标签的default-autowire属性确定 |
注意:在配置bean时,标签中autowire属性的优先级比其上级标签高,即是说,如果在上级标签中定义default-autowire属性为byName,而在中定义为byType时,Spring IoC容器会优先使用标签的配置。
factory-bean/factory-method 工厂方法注入
<bean factory-bean="" factory-method=""></bean>
lazy-init:延迟加载
值为true时,在Spring容器初始化时该javabean不加载而是等到程序使用该类时加载
默认值为false,javabean会随着Spring容器一同被加载.
注意:
- lazy-init只有在scope为singleton(单例)时才生效
- 若lazy-init的javabean被不是lazy-init依赖,则前者被后者第一次主动使用,两者同时加载
init-method/destroy-method
初始化(构造方法先执行)和销毁前javabean时调用的方法
关于在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:
第一种:通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
第二种是:通过 在xml中定义init-method 和 destory-method方法
第三种是: 通过bean实现InitializingBean和 DisposableBean接口
Spring容器架构
ApplicationContext
类名 | 概述 |
---|---|
AnnotationConfigApplicationContext | 基于注解开发时使用的容器 |
ClassPathXmlApplicationContext | 查找配置文件时默认到classpath路径下去找,也就是编译后的classes文件夹 |
FileSystemXmlApplicationContext | 查找配置文件默认到当前项目的文件系统路径 |
AnnotationConfigApplicationContext案例分析
//@Configuration标记的方法将该类描述为配置类,就类似一个xml配置文件,其中多个@Bean标记的方法将被AnnotationConfig(Web)ApplicationContext对象扫描,并用于构造javabean,初始化Spring容器
@Configuration
public class MyConfig {
//@Bean注解标记的方法返回的bean将被交与容器管理并在容器中以name的value值作为标识
@Bean(name = "studentService")
public StudentService getstudentService()
{
return new StudentService();
}
}
public class AnnoConfigTest {
@Test
public void show()
{
//1、通过注解构造的配置类获取容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
//2、获取对象实例
StudentService service = context.getBean(StudentService.class);
service.start();
//3、销毁
context.registerShutdownHook();
}
}
BeanFactory和ApplicationContext的区别
BeanFactory
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
ApplicationContext
应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
-
国际化(MessageSource)
-
访问资源,如URL和文件(ResourceLoader)
-
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
-
消息发送、响应机制(ApplicationEventPublisher)
-
AOP(拦截器)
两者装载bean的区别
BeanFactory
BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
ApplicationContext
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
选择
延迟实例化的优点:(BeanFactory)
应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;
不延迟实例化的优点: (ApplicationContext)
- 所有的Bean在启动的时候都加载,系统运行的速度快;
- 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题
- 建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)
SpringAPI
spring的getBean方法
依赖注入(两种)
setter注入
<bean class="com.edu.bean.Student">
<!-- int-->
<property name="age" value="23" ></property>
<!-- String-->
<property name="name" value="DLz"></property>
<!-- Array-->
<property name="friends">
<array>
<value>zyh</value>
<value>rit</value>
<value>laz</value>
</array>
</property>
<!-- List-->
<property name="girlfriends">
<list>
<value>zjey</value>
</list>
</property>
<!-- Set<Object>-->
<property name="courses">
<set>
<bean class="com.edu.bean.Course">
<property name="id" value="1"/>
<property name="name" value="语文"/>
</bean>
<bean class="com.edu.bean.Course">
<property name="id" value="2"/>
<property name="name" value="数学"/>
</bean>
</set>
</property>
<!-- Properties-->
<property name="score">
<props>
<prop key="1">88</prop>
<prop key="2">100</prop>
</props>
</property>
</bean>
构造注入
<bean class="com.edu.bean.Student">
<constructor-arg index="0" value="Dlz"/>
<constructor-arg index="1" value="23"/>
<constructor-arg index="2">
<list>
<value>sjw</value>
<value>jyx</value>
</list>
</constructor-arg>
<constructor-arg index="3">
<list>
<value>zzyy</value>
</list>
</constructor-arg>
<constructor-arg index="4">
<set>
<bean class="com.edu.bean.Course">
<property name="id" value="1"/>
<property name="name" value="语文"/>
</bean>
<bean class="com.edu.bean.Course">
<property name="id" value="2"/>
<property name="name" value="数学"/>
</bean>
</set>
</constructor-arg>
<constructor-arg index="5">
<props>
<prop key="1">88</prop>
<prop key="2">100</prop>
</props>
</constructor-arg>
</bean>
IOC注解
当类标记了注解表示将此类交给IoC容器管理,可以为注解的value赋值来声明类的别名。
注解 | 概述 |
---|---|
@Service | 一般用于业务层 |
@Repository | 一般用于dao层 |
@Controller | 一般用于控制层 |
@Configuration | 一般用于配置类 |
@Component | 一般类 |
Configuration和Component的区别
@Configuration 中所有带 @Bean 注解的方法都会被动态代理,也就是调用会调用代理类来完成,而不是调用我们自己的写的带new的方法,因此调用该方法返回的都是同一个实例。而@Component就是直接调用方法,所以每次都在new一个新的对象
注入注解
注解 | 概述 |
---|---|
@Autowired | 消除setter/getter方法 作为标记,告诉spring该属性依赖spring容器注入默认使用类型注入, 注解属于spring框架 |
@Qualifier(value=beanName) | 上面的注解使用类型注入,如果想根据bean的名字注入需要配合该注解使用 @Autowired+@Qualifier,注解属于spring框架 |
@Resource | 注解位于java.annotation.Resource中,为@Autowired+@Qualifier综合体 |
@Primary | 为接口注入时,默认标记注入的实现类,但其他实现类依然会加入到IoC容器 |
@Value | 提取配置文件或者是配置对象的属性 |
@Scope | 类似于bean标签中的scope属性 |
@Lazy | 类似于bean标签中的lazy-init属性 |
@PostConstruct | 在构造方法和init方法(如果有的话)之间得到调用,且只会执行一次。 |
@PreDestory | 注解的方法在destory()方法调用后得到执行 |
<!--引入外部的propertie配置文件-->
<context:property-placeholder location="classpath*:jdbc.properties" file-encoding="UTF-8"></context:property-placeholder>
$与#号区别
-
#只能从配置对象中取数据,支持spel表达式
-
$与#都可以在读取配置文件为null的情况下取默认值
@Value("${jdbc.driver:abc}") private String url; //前面属性取到为null 就取后面的值 @Value("#{myProperties.username?:'abc'}") private String username;
引深一点,Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:
1.通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
2.通过 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
3.在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用
但他们之前并不等价。即使3个方法都用上了,也有先后顺序:
Constructor > @PostConstruct >InitializingBean > init-method
案例
-
导入jar包
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.5.RELEASE</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.0</version> <configuration> <source>1.8</source> <!-- 源代码使用jdk1.8支持的特性 --> <target>1.8</target> <!-- 使用jvm1.8编译目标代码 --> <compilerArgs> <!-- 传递参数 --> <arg>-parameters</arg> <arg>-Xlint:unchecked</arg> <arg>-Xlint:deprecation </arg> </compilerArgs> </configuration> </plugin> </plugins> </build>
-
添加日志配置文件
-
添加IOC配置文件并添加配置扫描路径
<!--扫描需要交给spring管理的类所在的包路径--> <context:component-scan base-package="cn.cdqf.anno" > <!--指定不需要扫描的注解--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"></context:exclude-filter> <!--指定需要扫描的注解--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"></context:include-filter> </context:component-scan>
-
测试类
@RunWith(SpringJUnit4ClassRunner.class)//让整个测试代码在spring容器的环境下运行 @ContextConfiguration(locations = {"classpath*:application0.xml"})//指定配置文件的位子 public class StudentServiceImplTest { @Autowired//从spring容器中自动注入的对象 private StudentServiceImpl studentService; @Test public void show(){ System.out.println(studentService); } }
IOC概念总结
总结:di是思想,IOC是结果。di思想产生是因为项目中当某个对象,想持有另外对象的引用,需要自己new,例如controller需要service。显然这种情况会导致耦合性很强,于是就想能不能不自己new,依赖其他方式注入给我,这是依赖注入思想。spring容器就能完成这种注入,而通过这种注入方式导致的结果就是对象控制权的转移,这是控制反转ioc。
当StudentService引用StudentDao,原始方式自己new
StudentDao studentDao = new StudentDao();
耦合性很强,当有一天需要扩展功能从新创建一个StudentDao2需要修改业务层的代码
StudentDao2 studentDao2 = new StudentDao2();
除了修改这一行,还需要修改里面的方法定义
首先消除耦合,可以使用面向接口编程,新建一个Dao接口,让这两个类都实现dao接口,将所有的方法定义放在接口中,那么业务层代码改为
Dao dao = new StudentDao();
这样修改的时候只需要修改new StudentDao()这里的代码,方法不用修改了
但是还是有耦合,于是DI思想出现,业务层代码改为
@Autowired
Private Dao dao;
就解耦合了。
然后通过这种思想达到的效果就是对引用对象控制权由自己new转移到了容器注入,这种结果就是控制反转IOC
婚姻介绍所:概念层面理解代理模式
代理模式
核心:让原有的类中的方法只关注它自己的核心业务,其它公共辅助功能由代理类来完成
静态代理
手动创建代理对象,只能代理某一类对象
package cn.cdqf.agency;
public interface Singer {
void sing();
}
==========================
package cn.cdqf.agency;
public class Andy implements Singer{
@Override
public void sing()
{
System.out.println("锻炼歌喉");
System.out.println("唱歌技巧");
System.out.println("唱歌");
}
}
==========================
package cn.cdqf.agency;
public class Agent implements Singer{
//指定代理人
private Singer singer;
public Agent(Singer singer){
this.singer = singer;
}
public void talk(){
System.out.println("谈价格");
System.out.println("其它细节");
}
public void sing(){
talk();
singer.sing();
}v
}
==========================
package cn.cdqf.agency;
public class Test {
public static void main(String[] args) {
Singer singer = new Andy();
Agent agent = new Agent(singer);
agent.sing();
}
}
JDK动态代理
可以代理所有对象
由java动态生成代理对象,被代理对象需要有接口
由程序员完成的是能生成代理对象那个工具类
其实就是通过工具类生成一个与被代理类实现相同接口的一个代理类
- 被代理的所有方法都在代理类中增强
- 代理类中有被代理类的所有方法
- 调用的是JDK动态生成的代理对象,代理对象每个方法都增强,增强的内容就是invoke的内容
- Jdk的动态代理,生成代理对象是被代理对象的兄弟(实现了同一个接口)
package cn.cdqf.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理类
* 1.实现当前接口InvocationHandler
*/
public class JDKProxy implements InvocationHandler {
//被代理对象
private Object object;
//直接传入一个被代理对象 给你返回一个代理对象 Andy 只能唱歌 给你包装一下 返回一个既可以唱歌也能谈业务的代理对象
public Object getInstance(Object object){
this.object = object;
//1.类记载器,这儿可以直接使用被代理的类加载器
//2.被代理类实现所有接口,使用jdk的动态代理需要要求被代理类实现接口
//3.能完成代理这个功能的对象,在jdk动态代理中 实现了这个接口InvocationHandler的对象就具有代理功能
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(),this);
}
//1.代理对象
//2.代理类被调用的方法
//3.代理类被调用方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
talk();
//真正的方法 这个对象应该属于被代理对象 andy
Object invoke = method.invoke(object, args);
System.out.println("完成后续工作");
return invoke+"abc";
}
public void talk(){
System.out.println("谈价格");
System.out.println("其它细节");
}
}
package cn.cdqf.agency;
public class Test {
public static void main(String[] args) {
/* Singer singer = new Andy();
Agent agent = new Agent(singer);
agent.sing();*/
//任意类:唯一的要求需要实现接口
Singer singer = new Andy();
JDKProxy jdkProxy = new JDKProxy();
Singer instance = (Singer)jdkProxy.getInstance(singer);
//调用的不是原来的方法 调用的是invoke方法
String sing = instance.sing();
System.out.println(sing);
}
}
CGLIb代理
cglib 使用asm字节码技术生成代理类
- 不需要实现接口
- 被代理类不能被final修饰
- 生成的代理类是被代理类的子类
package cn.cdqf.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 1.不要求实现接口
* 2.生成的代理类是被代理类的子类
* 3.被代理类能被继承,不能被final修饰
*
* cglib 使用asm字节码技术生成代理类
*/
public class CglibTest implements MethodInterceptor {
private Object object;
public CglibTest(Object object){
this.object = object;
}
public Object getCglibProxy( ){
//生成代理类的 功能类
Enhancer enhancer = new Enhancer();
//设置代理类的父类(被代理类)
enhancer.setSuperclass(object.getClass());
//回调方法:调用被代理类任何一个方法的时候 会调用回调设置的对象的方法
enhancer.setCallback(this);//回调this对象的intercept方法
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理前加逻辑");
//调用原来的方法
Object invoke = method.invoke(object, objects);
System.out.println("代理后加逻辑");
return null;
}
}
解耦/减少代码
代理模式是属于设计模式中的一种,核心目的是在不改变核心逻辑代码情况增强方法的功能,主要包括静态代理与动态代理,静态代理只能代理同一类对象,而动态代理可以代理任意对象。
动态代理包含了,JDk与cglib,jdk使用类加载器动态生成代理类(必须有接口),生成的代理类与被代理类实现同一个接口,增加的功能在invoke方法中实现,cglib是采用asm字节码技术生成代理对象,要求被代理能被继承(不能被final修饰),生成的代理对象是被代理对象的子类,增强的功能在intercept方法中写。