获取容器
- 通过配置文件获取:new ApplicationContext(“bean.xml”);
- 通过配置类获取:new AnnotationConfigApplicationContext(xxConfig.class);
获取系统运行环境
1 | //获取环境 |
给容器中添加组件方式
包扫描+注解(有局限性:只用于自己写的类,从外部导入的不能用这种方法)
1
2<component:contxt-scan="com.xxx"/>
@Controller、@Service、@Repository、@Component@Configuration+@Bean(可以导入第三方包里面的组件)
@Import(快速给容器中导入一个组件)
使用Spring提供的FactoryBean(工厂Bean)
自己写一个工厂类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class ColorFactoryBean implements FactoryBean<Color> {
//返回一个Color对象,这个对象会添加到容器中
public Color getObject() throws Exception {
return new Color();
}
//返回对象的类型
public Class<?> getObjectType() {
return Color.class;
}
//控制是否单例
// true表示单实例,容器中只创建一次bean,即只调用一次getObject方法
// false表示多实例,每一次都要调用getObject方法创建一个bean
public boolean isSingleton() {
return false;
}
}将这个工厂类添加到容器中
1
2
3
4
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
BeanPostProcessor–Bean后置处理器
xxxAware
在自定义的组件中要使用Spring底层的一些组件(ApplicationContext、BeanFactory等),就需要实现形式为xxxAware的接口,在创建对象的时候,会调用接口规定的方法来注入相关组件。
xxxAware就是使用xxxProcessor(后置处理器来完成相关的功能),每一个xxxAware就对应一个xxxProcessor,例如:
1 | ApplicationContextAware --> ApplicationContextAwareProcessor |
@Configuration
标注在一个类上,表示这是一个配置类,这时这个配置类就相当于之前的xml配置文件
@Bean
一般在 @Configuration 标注的类里面使用,标注在一个方法上,相当于xml配置文件中的
如果@Bean里面不加参数的话,就默认用方法名作为组件的id,加了参数就以参数为组件的id。
@Bean标注的方法来创建容器的时候,方法参数的值默认是从容器里面获取的,效果和加上@Autowired注解一样,例如:
1 |
|
@ComponentScan
这个注解就相当于在xml配置文件中的
1 | <component:contxt-scan="com.xxx"/> |
它表示将以 @Controller、@Service、@Repository、@Component注解的类当成组件扫描到容器中。在java8过后,可以在同一个配置类上添加多个。
value: 表示扫描范围
excludeFilters: 表示扫描的时候排除哪些组件,它是Filter数组形式,因此里面的参数用大括号括起来
1
2
3(type=FilterType.ANNOTATION,classes={Controller.class})
type表示排除的规则,这里是排除注解规则
classes表示要排除哪些,它是数组形式,参数是根据类型的includeFilters: 指定扫描的时候包含哪些组件,具体的参数和之前的excludeFilters一致,需要区别一点的是,需要加上useDefaultFilters = false来禁用默认的拦截器。
FilterType
1
2
3
4
5FilterType.ANNOTATION:按照注解,这时classes里面的参数可以是Controller.class、Service.class、Component.class这些注解
FilterType.ASSIGNABLE_TYPE:按照类型,这时classes可以是在在容器中一个自定义类型,比如 BookService.class
FilterType.ASPECTJ:按照AspectJ表达式(不常用)
FilterType.REGEX:按照正则表达式(不常用)
FilterType.CUSTOM:按照自定义规则,这时classes里面可以为 MyFilterType.class,其中MyFilterType这个类必须实现TypeFilter,然后实现match方法。
@ComponentScans
它用于多个ComponentScan的扫描,其中value里面为数组形式,可以指定多个 @ComponentScan
@Scope
定义作用范围,可以用在方法或者类上,取值如下:
- singleton:单实例(默认为这个),ioc容器在启动的时候(new AnnotationConfigApplicationContext(xxConfig.class)的时候)会调用方法创建对象放到IOC容器中,后面每次获取就是直接中容器中拿,而不是再来创建。
- prototype:多实例(每次创建的对象不同),IOC容器启动的时候并不会调用方法创建对象放到容器中,而是在获取的时候才会调用方法创建对象(即在applicationContext.getBean(“persion”)的时候),而且每次都创建对象。
- request:同一次请求创建一个实例
- session:同一个session创建一个实例
@Lazy
懒加载,用于单实例的时候,上面有讲在单实例的时候会在容器启动(new AnnotationConfigApplicationContext(xxConfig.class))的时候创建对象,而使用了懒加载后,容器启动时不创建对象,在第一次使用(获取)(applicationContext.getBean(“xx”))的时候才创建对象。
@Conditional(重点)
按照一定的条件进行判断,满足条件的才给容器中添加组件。它可以标注在类和方法上。
他的参数为数组形式,其参数必须实现Condition接口,实现其matchs方法,例如:WindowsCondition就是自己写的一个类,它实现了Condition接口。如下图:
@Import(重点)
第一种方式
给容器中导入组件,例如将Color组件添加到容器中,其id默认是该组件的全类名(带包名)。
其参数是数组形式,因此可以添加大括号导入多个组件,例如:
1 | ({Color.class,Red.class}) |
第二种方式(用得比较多)
- 实现ImportSelector接口,返回需要导入组件的全类名数组
- 在@Import注解里面添加上这个自定义的选择器,例:
1
({MyImportSelector.class})
第三种方式
实现ImportBeanDefinitionRegistrar接口,手动注册bean到容器中
1
2
3
4
5
6
7
8
9
10
11
12
13public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//判断容器中是否包含某个组件
boolean definition = beanDefinitionRegistry.containsBeanDefinition("com.cdn.Color");
if (definition){
//指定Bean的类型
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个Bean,指定bean的id
beanDefinitionRegistry.registerBeanDefinition("rainBow",beanDefinition);
}
}
}在@Import注解里面添加上自定义注册
1
({MyImportBeanDefinitionRegistrar.class})
@Bean生命周期
bean创建–bean初始化–bean销毁
默认是容器来管理bean的生命周期,我们可以自定义bean的初始化和销毁方法,容器在bean进行到当前生命周期的时候来调用自定义的相应的方法。
创建容器 –> 调用初始化方法 –> 关闭容器 –> 调用销毁方法
单实例
- 初始化时机:对象创建完成,并赋值好,调用初始化方法
- 销毁时机:容器调用close方法关闭后,调用销毁方法
多实例
- 初始化:在获取容器的时候,才调用初始化方法
- 销毁:多实例中容器不会管理bean的销毁
指定初始化和销毁方法
第一种方式
- 指定初始化和销毁方法
- 在@Bean注解中添加初始化和销毁方法
1
2
3
4
5//其中的init和destroy方法是Car类里面定义好的方法
"init", destroyMethod = "destroy") (initMethod =
public Car car(){
return new Car();
}
第二种方式
Bean实现InitializingBean接口实现初始化逻辑;实现DisposableBean接口定义销毁逻辑
1 | public class Cat implements InitializingBean, DisposableBean { |
然后将Cat添加到容器中即可
1 |
|
第三种方式
使用JSR250规范里面的注解
- @PostConstruct:在对象创建并赋值好之后调用
- @PreDestroy:容器关闭后调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Cat {
public Cat(){
}
//在对象创建并赋值好之后调用
public void init(){
}
//容器关闭后调用
public void destroy(){
}
}
然后再将Cat类添加到容器中
1 |
|
第四种方式
BeanPostProcessor:Bean的后置处理器,是一个接口,在bean初始化前后进行一些处理工作,即使我们没有定义初始化和销毁方法,这个也会执行。
1 | public class MyBeanPostProcessor implements BeanPostProcessor { |
然后将MyBeanPostProcessor添加到容器中
1 |
|
@PropertySource
加载外部配置文件,保存到运行环境中,其中的value是数组形式,可以导入多个配置文件。它是可重复标注的注解,可以在一个类上同时标注多个。
1 | "classpath:person.properties"}) (value={ |
@Value
对组件中的属性赋值,可以用在参数上。
基本类型
1
2"张三") (
private String name;SpEL,Spring表达式
1
2"#{20-2}") (
private Integer age;${},取出配置文件中的值(运行在环境变量里面的值)
1
2
3//person.name是配置文件中指定的值
"${person.name}") (
private String name;
自动注入
自动注入,在一个组件中需要另外一个组件,就用注解将组件注入进来。
@Autowired
Spring里面的规范,可以标注在构造器、方法、属性、参数上。
标注在方法上(一般在setter方法上)。Spring容器创建当前对象后,就会调用这个方法,完成赋值,方法的参数就是从容器里面获取的值。
1
2
3
4
5//参数里面的Car就是从容器里面获取的
@Autowired
public void setCar(Car car){
this.car = car;
}标注在构造方法上。 如果组件只有一个有参构造器,则这里的@Autowired可以省略,参数位置的组件还是可以从容器中获取。
1
2
3
4
5
6
7
8@Autowired
public Boss(Car car){
this.car = car;
}
或者
public Boss(Car car){
this.car = car;
}
默认加在ioc容器中的组件,容器在启动的时候会调用无参构造器来创建对象,然后进行初始化操作。
- 标注在参数上。
1 | public Boss(@Autowired Car car){ |
- 标注在属性上。
1 |
|
1 |
|
- 默认优先按照组件类型去容器里面找对应的组件
- 如果容器中该类型的组件有多个,再通过属性名作为id去容器里面查找
- 自动装配默认一定要找到指定的组件并赋值好,如果没有找到组件,则会报错
- 使用 @Autowired(required=false) 来表示该组件不是必须的,即没有的时候不会报错,会返回null
@Qualifier
该注解配合@Autowired使用,用于指定需要装配的组件的id,而不是使用属性名。
1 |
|
@Primary
在Spring自动装配的时候,默认使用首选的bean,即用该注解标注的bean,同时也可以使用@Qualifier指定装配哪个bean,这时默认的就会失效。
1 | public class MainConfig{ |
@Resource
JSR250里面的规范(Java规范)
默认是按照bean的名称(即属性名)来装配的,可以用name来表示需要装配哪一个bean。不能结合上面的@Qulifier和@Primary注解使用,也没有required功能。
1 |
|
@Inject
JSR330里面的规范(Java规范),它和Autowired注解功能类似,也能用@Primary功能,只不过没有required这个参数,即不支持required功能。
使用这个注解需要添加以下依赖:
1 | <dependency> |
@Profile
Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。
第一步:指定运行环境
@Profile:指定组件在哪个环境下,才能被注册到容器中;
不指定@Profile的bean,在任何环境下都能注册;
可以标注在方法和类上。写在类上的时候,只有是在指定的环境,整个类里面所有的配置才能生效。
例如下面代码指定了dev环境。
注意:加了环境标识的bean,只有在这个环境被激活的时候才能注册到容器中,否则不能注册进容器。Spring的默认是default
环境,在没有指定环境的时候默认环境生效。即
1 | "default") ( |
第二步:切换环境
命令行参数
1
2在VM arguments这一栏里面添加参数,激活dev环境
-Dspring.profiles.active=dev使用代码的方式
1
2
3
4
5
6
7
8
9
10
11
public void contextLoads() {
//第一步 创建一个ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//第二步 设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("test","dev");
//第三步 注册主配置类
applicationContext.register(ConfigOfProfile.class);
//第四步 刷新启动容器
applicationContext.refresh();
}在配置文件里面切换
1
2# 例如SpringBoot的配置文件 application.properties
spring.profiles.active=dev
AOP
在程序运行期间,动态的将某段代码切入到指定的方法位置进行运行的编程方式,底层以动态代理实现
以SpringBoot为例
1.导入依赖
1 | <dependency> |
定义一个业务逻辑类,在业务逻辑方法运行的时候进行日志打印(方法之前,之后,运行结果,运行异常)‘
1
2
3
4
5
6
7
8
9
10
11
12/**
* @author 南风
* @date 2019/8/15-17:18
*业务逻辑类,即需要被切入的类
*/
public class MathTest {
public int div(int i,int j){
System.out.println("除法被调用");
return i/j;
}
}定义一个切面类,类里面写切面方法(通知方法),需要在切面类上加上一个 @Aspect 注解,来告诉Spring哪个是切面类
- 前置通知(@Before):在目标方法之前运行
- 后置通知(@After):在目标方法运行之后运行,不论是正常结束还是异常结束
- 返回通知(@AfterReturning):在目标方法正常返回时执行
- 异常通知(@AfterThrowing):在目标方法抛出异常时执行
- 环绕通知(@Around):动态代理,手动执行目标方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52/**
* @author 南风
* @date 2019/8/15-17:19
* 切面类
* @Aspect告诉Spring这是一个切面类
*/
public class MathAop {
/**
* 抽取公共的切入点表达式
* 1. 在本类中直接使用 pointCut()
* 2. 在外部类使用全类名 com.cdn.MathAop.pointCut()
*/
"execution(public int com.cdn.MathTest.*(..))") (
public void pointCut(){
}
"pointCut()") (
public void logBefore(JoinPoint joinPoint){
//获取方法名
String methodName = joinPoint.getSignature().getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println("计算方法 "+methodName+" 之前,参数为:{"+Arrays.asList(args)+"}");
}
"pointCut()") (
public void logEnd(){
System.out.println("计算方法结束");
}
/**
* JoinPoint joinPoint 参数一定要放在参数表的第一位,否则无法识别
* returning 用来接收返回值,它和该方法对应的参数绑定
* @param result
*/
"pointCut()",returning = "result") (value =
public void logReturn(JoinPoint joinPoint, Object result){
System.out.println("计算方法正常返回,返回值:{"+result+"}");
}
/**
* JoinPoint 参数和上面同理,放在参数列表的第一位
* throwing 接收异常,和该方法的参数绑定
* @param exception
*/
"pointCut()",throwing = "exception") (value =
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println("计算方法异常,异常信息为:{"+exception.getMessage()+"}");
}
}
将切面类和业务逻辑类都添加到容器中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ConfigOfAop {
/**
* 将需要被切入的逻辑类加入到容器中
* @return
*/
public MathTest mathTest(){
return new MathTest();
}
/**
* 将切面类加入到容器中
* @return
*/
public MathAop mathAop(){
return new MathAop();
}
}需要在配置类上加上 @EnableAspectJAutoProxy 注解来开启AspectJ自动代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/**
* @author 南风
* @date 2019/8/15-17:01
* @EnableAspectJAutoProxy 开启基于注解的AspectJ自动代理
*/
public class ConfigOfAop {
/**
* 将需要被切入的逻辑类加入到容器中
* @return
*/
public MathTest mathTest(){
return new MathTest();
}
/**
* 将切面类加入到容器中
* @return
*/
public MathAop mathAop(){
return new MathAop();
}
}