IOC
向Spring容器中注册bean通常有以下几种方式:
- 包扫描+给组件标注注解(@Controller、@Servcie、@Repository、@Component),但这种方式比较有局限性,局限于我们自己写的类
- @Bean注解,通常用于导入第三方包中的组件
- @Import注解,快速向Spring容器中导入一个组件
- 包扫描+自定义包含规则
- 使用BeanDefinitionRegistry类的registerBeanDefinition方法
- 使用FactoryBean注册bean
@Configuration注解和@Bean注解
-
@configuration:告诉spring这是一个配置类 使用AnnotationConfigApplicationContext类进行加载 同时加了此注解的类也会被spring管理
-
@Bean:
给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean(“bean的id”) 通过@Bean的value属性可以更改bean的id
AnnotationConfigApplicationContext类的getBean()方法可以通过id值获取bean对象。也可以通过 bean.class获取对象,但是不能有多个@bean标注的相同返回值类型的方法。
@ComponentScan注解
设置包扫描的注解,@ComponentScan注解为可重复注解,可在一个配置类上标识多次
basePackages和value属性
属性的值为String类型的数组,数组中设定包名。
扫描时会扫描包下的所有类。在不设置包含规则的情况下,所有标注了@Repository、@Service、@Controller、@Component注解的类注入到Spring容器中
includeFilters和excludeFilters属性
这两个属性的值为Filter[]数组,includeFilters指定Spring扫描的时候按照什么规则只需要包含哪些组件,而excludeFilters指定Spring扫描的时候按照什么规则排除哪些组件
@ComponentScan(value="com.meimeixia", includeFilters={
/*
* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:我们需要Spring在扫描时,只包含@Controller注解标注的类
*/
@Filter(type=FilterType.ANNOTATION, classes={Controller.class})
}, useDefaultFilters=false)
注意:扫描包时有默认的过滤规则,只设置包含某个类的过滤规则时需禁用默认规则——useDefaultFilters=false
出现规则之外的spring管理的bean,是用@Bean注解在内部注册的或自带的bean
type取值
Filter[]数组的元素为 @Filter类型,type属性为FilterType枚举
-
FilterType.ANNOTATION:按照注解进行包含或者排除
-
FilterType.ASSIGNABLE_TYPE:按照给定的类型进行包含或者排除
-
FilterType.ASPECTJ:按照ASPECTJ表达式进行包含或者排除(要查用法)
-
FilterType.REGEX:按照正则表达式进行包含或者排除(要查用法)
-
FilterType.CUSTOM:按照自定义规则进行包含或者排除
如果实现自定义规则进行过滤时,自定义规则的类必须是org.springframework.core.type.filter.TypeFilter接口的实现类。
当我们实现TypeFilter接口时,需要实现该接口中的match()方法,match()方法的返回值为boolean类型。当返回true时,表示符合规则,会包含在Spring容器中;当返回false时,表示不符合规则。另外,在match()方法中存在两个参数,分别为MetadataReader类型的参数和MetadataReaderFactory类型的参数
- metadataReader:读取到的当前正在扫描的类的信息
- metadataReaderFactory:可以获取到其他任何类的信息的工厂
package com.meimeixia.config; import java.io.IOException; import org.springframework.core.io.Resource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.filter.TypeFilter; public class MyTypeFilter implements TypeFilter { /** * 参数: * metadataReader:读取到的当前正在扫描的类的信息 * metadataReaderFactory:可以获取到其他任何类的信息的(工厂) */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // 获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); // 获取当前正在扫描的类的类信息,比如说它的类型是什么啊,它实现了什么接口啊之类的 ClassMetadata classMetadata = metadataReader.getClassMetadata(); // 获取当前类的资源信息,比如说类的路径等信息 Resource resource = metadataReader.getResource(); // 获取当前正在扫描的类的类名 String className = classMetadata.getClassName(); System.out.println("--->" + className); return false; } }
在includeFilters和excludeFilters属性值中使用
@Filter(type=FilterType.CUSTOM, classes={MyTypeFilter.class})
@Scope注解
value可取的值为 “singleton”、“prototype”、“request”、“session”
-
“singleton” :默认值,ioc容器中的bean为单实例。Spring容器在启动时就会将实例对象加载到Spring容器中,之后,每次从Spring容器中获取实例对象,都是直接将对象返回,而不必再创建新的实例对象了
单实例bean是整个应用所共享的,所以需要考虑到线程安全问题。SpringMVC中的Controller默认是单例的,有些开发者在Controller中创建了一些变量,那么这些变量实际上就变成共享的了,Controller又可能会被很多线程同时访问,这些线程并发去修改Controller中的共享变量,此时很有可能会出现数据错乱的问题,所以使用的时候需要特别注意。
-
“prototype” 多实例,当向Spring容器中获取Person实例对象时(执行getBean()方法),Spring容器才会实例化Person对象
@Lazy (懒加载)
单实例的情况下,在注册bean的方法上添加@Lazy注解后就会延迟加载,在Spring容器启动的时候并不会加载,而是在第一次使用此bean的时候才会加载,但当你多次获取bean的时候并不会重复加载,只是在第一次获取的时候才会加载。
可以加在配置类中@Bean的方法上,也可以加在扫描包的类上
@Conditional
org.springframework.context.annotation包
按照一定的条件进行判断,满足条件给容器中注册bean
该注解可以放在类上,也可以放在方法上。方法主要是使用了@Bean注解的方法。放在配置类上,会根据条件判断是否创建@Bean注解方法的返回值,放在被扫描的类上,会根据条件判断是否加入容器
要想使用@Conditional注解,我们需要实现Condition接口来为@Conditional注解设置条件
package com.meimeixia.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 判断操作系统是否是Windows系统
* @author liayun
*
*/
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Windows")) {
return true;
}
return false;
}
}
//使用
@Conditional({WindowsCondition.class})
//条件为Condition实现类.class 的数组,意为所有实现类的matches()方法返回true 才加入容器
BeanDefinitionRegistry类
bean定义的注册类
registerBeanDefinition方法; //向容器注册bean
removeBeanDefinition方法; //容器移除bean
getBeanDefinition方法; //获取bean的定义信息
containsBeanDefinition方法; //容器是否包含bean
@Import
用在配置类上(其他位置未知)
直接填写class数组的方式
@Import({Color.class, Red.class}) // @Import快速地导入组件,id默认是组件的全类名
ImportSelector接口的方式,即批量导入,这是重点
id为方法返回数组中的全类名
ImportSelector接口是Spring中导入外部配置的核心接口,在Spring Boot的自动化配置和@EnableXXX(功能性注解)都有它的存在。
使用:创建ImportSelector接口的实现类,实现selectImports方法,在@Import注解的值的数组中加入
package com.meimeixia.condition;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* 自定义逻辑,返回需要导入的组件
* @author liayun
*
*/
public class MyImportSelector implements ImportSelector {
// 返回值:就是要导入到容器中的组件的全类名
// AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 在这一行打个断点,debug调试一下
// 方法不要返回null值,否则会报空指针异常
// 数组元素必须为存在的全类名的字符串
return new String[]{"com.meimeixia.bean.Bule", "com.meimeixia.bean.Yellow"};
}
}
@Import({Color.class, Red.class, MyImportSelector.class}) // 并不会导入实现类对象,只把方法中返回的String数组中的全类名元素对应的类加入容器
ImportBeanDefinitionRegistrar接口方式,即手动注册bean到容器中
id自定义
ImportBeanDefinitionRegistrar需要配合@Configuration和@Import这俩注解,@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类。
package com.meimeixia.condition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import com.meimeixia.bean.RainBow;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类
*
* 我们可以通过调用BeanDefinitionRegistry接口中的registerBeanDefinition方法,手动注册所有需要添加到容器中的bean
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//逻辑
boolean definition = registry.containsBeanDefinition("com.meimeixia.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.meimeixia.bean.Bule");
if (definition && definition2) {
// 指定bean的定义信息,包括bean的类型、作用域等等
// RootBeanDefinition是BeanDefinition接口的一个实现类
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); // bean的定义信息
// 注册一个bean,并且指定bean的名称
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})// 并不会导入实现类对象,只把方法中注册的类加入容器
FactoryBean
用来注册负载的bean
与BeanFactory的区别:BeanFactory为ioc容器,进行bean的管理。而FactoryBean是一个接口,为容器中注册bean,注册的bean为getOIbject()中返回的对象。
package com.meimeixia.bean;
import org.springframework.beans.factory.FactoryBean;
/**
* 创建一个Spring定义的FactoryBean
* T(泛型):指定我们要创建什么类型的对象
*/
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class; // 返回这个对象的类型
}
// 如果返回true,那么代表这个bean是单实例,在容器中只会保存一份;
// 如果返回false,那么代表这个bean是多实例,每次获取都会创建一个新的bean
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
}
}
使用
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
//注册的bean 的id(name)为方法名,注册的bean为getObject方法返回的对象
//bean的创建为懒加载方式,无论单例多例。@Scope 会影响多例单例
在获取bean时,在id前面加上&符号就会获取到FactoryBean的实例对象。
Bean的生命周期值初始化和销毁
通常意义上讲的bean的生命周期,指的是bean从创建到初始化,经过一系列的流程,最终销毁的过程。只不过,在Spring中,bean的生命周期是由Spring容器来管理的。在Spring中,我们可以自己来指定bean的初始化和销毁的方法。我们指定了bean的初始化和销毁方法之后,当容器在bean进行到当前生命周期的阶段时,会自动调用我们自定义的初始化和销毁方法。
方式一:@Bean注解的initMethod属性和destroyMethod属性
-
在要注册的bean中创建初始化方法和销毁方法
-
使用@Bean注解的initMethod属性和destroyMethod属性来指定bean的初始化方法和销毁方法。
package com.meimeixia.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.meimeixia.bean.Car;
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod="init", destroyMethod="destroy")
public Car car() {
return new Car();
}
}
-
初始化方法
调用在bean的创建之后,也就是在执行构造方法之后。如果对象中存在一些属性,并且这些属性也都赋好值之后。 bean的创建与ioc容器、单例多例、单例设置懒加载有关。
-
销毁方法
-
对于单实例bean来说,在容器关闭或者容器中bean被移除后执行
-
对于多实例bean来说,
多实例的bean在容器关闭的时候是不进行销毁的,也就是说你每次获取时,IOC容器帮你创建出对象交还给你,至于要什么时候销毁这是你自己的事,Spring容器压根就不会再管理这些多实例的bean了。
-
方式二:实现InitializingBean和DisposableBean接口定义初始化和销毁方法
package com.meimeixia.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class Cat implements InitializingBean, DisposableBean {
public Cat() {
System.out.println("cat constructor...");
}
/**
* 会在容器关闭的时候进行调用
*/
@Override
public void destroy() throws Exception {
// TODO Auto-generated method stub
System.out.println("cat destroy...");
}
/**
* 会在bean创建完成,并且属性都赋好值以后进行调用
*/
@Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
System.out.println("cat afterPropertiesSet...");
}
}
方式三:JDK提供的@PostConstruct注解和@PreDestroy注解(JSR-250)
package com.meimeixia.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class Dog {
public Dog() {
System.out.println("dog constructor...");
}
// 在对象创建完成并且属性赋值完成之后调用
@PostConstruct
public void init() {
System.out.println("dog...@PostConstruct...");
}
// 在容器销毁(移除)对象之前调用
@PreDestroy
public void destory() {
System.out.println("dog...@PreDestroy...");
}
}
总结
这三种初始化和销毁并不是定义的同一种生命周期,可以混合使用。其顺序
- @PostConstruct注解标注的方法(JSR250)
- 实现InitializingBean接口的afterPropertiesSet方法(Spring的接口)
- 使用@Bean注解属性initMethod自定义的方法(Spring提供)
后置处理器
BeanPostProcessor是一个接口,其中有两个方法,即postProcessBeforeInitialization和postProcessAfterInitialization这两个方法,这两个方法分别是在Spring容器中的bean初始化前后执行,所以Spring容器中的每一个bean对象初始化前后,都会执行BeanPostProcessor接口的实现类中的这两个方法。
postProcessBeforeInitialization方法会在bean实例化和属性设置之后,自定义初始化方法之前被调用,而postProcessAfterInitialization方法会在自定义初始化方法之后被调用。
当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行。对于自定义的BeanPostProcessor实现类,还可以让其实现Ordered接口自定义排序。
package com.meimeixia.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* 后置处理器,在初始化前后进行处理工作
* @author liayun
*
*/
@Component // 将后置处理器加入到容器中,这样的话,Spring就能让它工作了
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean);
return bean;
}
}
Spring提供了BeanPostProcessor接口的很多实现类,例如AutowiredAnnotationBeanPostProcessor用于@Autowired注解的实现,AnnotationAwareAspectJAutoProxyCreator用于Spring AOP的动态代理等等。
后置处理器原理 视频尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)16、17集
生命周期 创建-属性赋值-后置处理器-初始化-后置处理器-销毁
sping内部定义了好多后置处理器,实现bean赋值、自动注入、注入组件(创建bean)、生命周期注解(init注解、销毁注解)、@Async注解(异步方法)
@Value注解的用法
注入普通字符串
@Value("zx")
private String name; // 注入普通字符串
注入操作系统属性
@Value("#{systemProperties['os.name']}")
private String systemPropertiesName; // 注入操作系统属性
注入SpEL表达式结果
@Value("#{ T(java.lang.Math).random() * 100.0 }")
private double randomNumber; //注入SpEL表达式结果
注入其他bean中属性的值
@Value("#{person.name}")
private String username; // 注入其他bean中属性的值,即注入person对象的name属性中的值
注入文件资源
@Value("classpath:/config.properties")
private Resource resourceFile; // 注入文件资源
注入URL资源
@Value("http://www.baidu.com")
private Resource url; // 注入URL资源
通过配置文件注入属性的情况
-
在配置类使用@PropertySource注解读取外部配置文件
package com.meimeixia.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import com.meimeixia.bean.Person; @PropertySource(value={"classpath:/person.properties"}) @Configuration public class MainConfigOfPropertyValues { @Bean public Person person() { return new Person(); } }
-
使用
${key}
取出配置文件中key所对应的值,并将其注入到bean的属性中了@Value("${person.nickName}") private String nickName; // 昵称
${}和#{}
{}里面的内容必须符合SpEL表达式
-
⋅ ⋅ ⋅ 的 用 法 通 过 @ V a l u e ( " {···}的用法 通过@Value(" ⋅⋅⋅的用法通过@Value("{spelDefault.value}")我们可以获取属性文件中对应的值,但是如果属性文件中没有这个属性,那么就会报错。不过,我们可以通过赋予默认值来解决这个问题,如下所示。
@Value("${author.name:meimeixia}") private String name;
那么便向bean的属性中注入默认值meimeixia
-
#{···}的用法
{}里面的内容同样也是必须符合SpEL表达式
// SpEL:调用字符串Hello World的concat方法
@Value("#{'Hello World'.concat('!')}")
private String helloWorld;
// SpEL:调用字符串的getBytes方法,然后再调用其length属性
@Value("#{'Hello World'.bytes.length}")
private String helloWorldBytes;
-
${···}和#{···}的混合使用
// SpEL:传入一个字符串,根据","切分后插入列表中, #{}和${}配合使用时,注意不能反过来${}在外面,而#{}在里面 @Value("#{'${server.name}'.split(',')}") private List<String> severs;
因为Spring执行
${}
的时机要早于#{}
,当Spring执行外层的${}
时,内部的#{}
为空,所以会执行失败!
@Autowired、@Qualifier、@Primary
@Autowired
@Autowired注解默认是优先按照类型去容器中找对应的组件,
如果有多个相同类型的组件,那么会到IOC容器中进行查找id为属性名称的bean(跟版本有关)
设置属性required=false
;这个玩意的意思就是说找到就装配,找不到就拉到,就别装配了
@Qualifier("bookDao")
@Autowired(required=false)
private BookDao bookDao2;
tips:如果以不同的方式注册了同类型同id的bean,会进行替换(底层为map)
深入了解
@Autowired注解不仅可以标注在字段上,而且还可以标注在构造方法、实例方法以及参数上。
set方法、有参构造、参数前
如果实体类只有有参构造,无无参构造时,在有参构造的参数位置的@Autowired可以省略
@Bean标注的方法,方法参数可以直接从容器中获取
@Qualifier
@Autowired是根据类型进行自动装配的,如果需要按名称进行装配,那么就需要配合@Qualifier注解来使用了。两者一起使用。
@Primary
在注册bean的时候标注(@Bean或@Component)
有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类。
和@Qualifier冲突。如果有多个同类型的bean,除了使用@Qualifier,也可使用@Primary
@Resource和@Inject
@Resource
@Resource注解是Java规范里面的,也可以说它是JSR250规范里面定义的一个注解。
@Resource有两个重要属性,分别是name和type
默认是按照名称装配,默认名称为属性名
不支持@Primary注解优先注入的功能的,而且也不能像@Autowired注解一样能添加required=false
属性。
@Inject注解
@Inject注解也是Java规范里面的,也可以说它是JSR330规范里面定义的一个注解。使用时需导包。
**默认根据类型装配,**配合@Named注解可以按名称装配
支持Spring的@Primary注解优先注入,不支持 required=false 属性。
注入spring底层组件
自定义组件实现Aware接口的子接口
子接口有ApplicationContextAware(获取ioc容器)、BeanNameAware(获取到当前bean在Spring容器中的名称)、EmbeddedValueResolverAware(获取到String值解析器)。。。
原理
XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类。后置处理器实现
@Profile
@Profile注解是Spring为我们提供的可以根据当前环境,动态地激活和切换一系列组件的功能。例:根据开发、测试、生产环境来提供不同的数据源。
- @Profile注解不仅可以标注在方法上,也可以标注在配置类上。
- 如果@Profile注解标注在配置类上,那么只有是在指定的环境的时候,整个配置类里面的所有配置才会生效。标注在@Bean的方法上,首先看配置类生不生效,然后根据条件判断方法是否生效
- 如果一个bean上没有使用@Profile注解进行标注,那么这个bean在任何环境下都会被注册到IOC容器中,当然了,前提是在整个配置类生效的情况下。
实例
创建配置类,并在配置类中注册不同环境的数据源
@PropertySource("classpath:/dbconfig.properties") // 加载外部的配置文件
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private StringValueResolver valueResolver;
private String dirverClass;
@Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(dirverClass);
return dataSource;
}
@Profile("dev") // 定义了一个环境标识,只有当dev环境被激活以后,我们这个bean才能被注册进来
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
dataSource.setDriverClass(dirverClass);
return dataSource;
}
@Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515");
dataSource.setDriverClass(dirverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.valueResolver = resolver;
dirverClass = valueResolver.resolveStringValue("${db.driverClass}");
}
}
//练习通过外部文件获取数据源的参数,EmbeddedValueResolverAware接口的使用
改变运行环境参数
-
方式一:根据命令行参数来确定环境,我们在运行程序的时候可以添加相应的命令行参数。
–spring.profiles.active=test
-
方式二:通过写代码的方式来激活某种环境,其实主要是通过AnnotationConfigApplicationContext类的无参构造方法来实现
@Test
public void test02() {
// 1. 使用无参构造器创建一个IOC容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 2. 在我们容器还没启动创建其他bean之前,先设置需要激活的环境(可以设置激活多个环境)
applicationContext.getEnvironment().setActiveProfiles("test");
// 3. 注册主配置类
applicationContext.register(MainConfigOfProfile.class);
// 4. 启动刷新容器
applicationContext.refresh();
String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String name : namesForType) {
System.out.println(name);
}
// 关闭容器
applicationContext.close();
}
AOP
流程
-
定义切面类,并交给ioc管理
package com.meimeixia.aop; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; /** * 切面类 * * @Aspect:告诉Spring当前类是一个切面类,而不是一些其他普通的类 * */ @Aspect public class LogAspects { // 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式 @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} // @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入 // @Before("public int com.meimeixia.aop.MathCalculator.*(..)") @Before("pointCut()") public void logStart() { System.out.println("除法运行......@Before,参数列表是:{}"); } // 在目标方法(即div方法)结束时被调用 // @After("pointCut()") @After("com.meimeixia.aop.LogAspects.pointCut()") public void logEnd() { System.out.println("除法结束......@After"); } // 在目标方法(即div方法)正常返回了,有返回值,被调用 @AfterReturning("pointCut()") public void logReturn() { System.out.println("除法正常返回......@AfterReturning,运行结果是:{}"); } // 在目标方法(即div方法)出现异常,被调用 @AfterThrowing("pointCut()") public void logException() { System.out.println("除法出现异常......异常信息:{}"); } }
其中,切入点表达式一定要写对,否则出现
Error creating bean with name ‘org.springframework.context.event.internalEventListenerProcessor’
错误。
环绕通知写法不同。
-
@EnableAspectJAutoProxy注解开启基于注解的AOP模式
package com.meimeixia.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import com.meimeixia.aop.LogAspects; import com.meimeixia.aop.MathCalculator; /** * AOP:面向切面编程,其底层就是动态代理 * 指在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式。 * * @author liayun * */ @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { // 将业务逻辑类(目标方法所在类)加入到容器中 @Bean public MathCalculator calculator() { return new MathCalculator(); } // 将切面类加入到容器中 @Bean public LogAspects logAspects() { return new LogAspects(); } }
参数使用
package com.meimeixia.aop; import java.util.Arrays; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; /** * 切面类 * * @Aspect:告诉Spring当前类是一个切面类,而不是一些其他普通的类 * @author liayun * */ @Aspect public class LogAspects { // 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式 // 1. 本类引用 // 2. 如果是外部类,即其他的切面类引用,那就在这@After("...")写的是方法的全名,而我们就要把切入点写在这儿@Pointcut("...") @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} // @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入 // @Before("public int com.meimeixia.aop.MathCalculator.*(..)") @Before("pointCut()") public void logStart(JoinPoint joinPoint) { // System.out.println("除法运行......@Before,参数列表是:{}"); Object[] args = joinPoint.getArgs(); // 拿到参数列表,即目标方法运行需要的参数列表 System.out.println(joinPoint.getSignature().getName() + "运行......@Before,参数列表是:{" + Arrays.asList(args) + "}"); } // 在目标方法(即div方法)结束时被调用 // @After("pointCut()") @After("com.meimeixia.aop.LogAspects.pointCut()") public void logEnd(JoinPoint joinPoint) { // System.out.println("除法结束......@After"); System.out.println(joinPoint.getSignature().getName() + "结束......@After"); } // 在目标方法(即div方法)正常返回了,有返回值,被调用 // @AfterReturning("pointCut()") @AfterReturning(value="pointCut()", returning="result") // returning来指定我们这个方法的参数谁来封装返回值 /* * 如果方法正常返回,我们还想拿返回值,那么返回值又应该怎么拿呢? */ public void logReturn(JoinPoint joinPoint, Object result) { // 一定要注意:JoinPoint这个参数要写,一定不能写到后面,它必须出现在参数列表的第一位,否则Spring也是无法识别的,就会报错 // System.out.println("除法正常返回......@AfterReturning,运行结果是:{}"); System.out.println(joinPoint.getSignature().getName() + "正常返回......@AfterReturning,运行结果是:{" + result + "}"); } // 在目标方法(即div方法)出现异常,被调用 @AfterThrowing(value="pointCut()", throwing="exception") public void logException(JoinPoint joinPoint, Exception exception) { System.out.println("除法出现异常......异常信息:{}"); } }
增强方法都可以带上JoinPoint类型的参数,可以获取被增强方法的方法名、参数列表等信息。
JoinPoint做参数时,一定要放在第一位,否则报错。
返回通知、异常通知可以带上返回值参数和异常参数,需要在注解中指定,如上。
原理总结
-
@EnableAspectJAutoProxy注解开启aop功能
@EnableAspectJAutoProxy利用@Import注解的ImportBeanDefinitionRegistrar接口方式向容器中注入实现了aware子接口和后置处理器接口的AnnotationAwareAspectJAutoProxyCreator类的对象
-
容器创建时的操作
-
服务器读取配置类创建容器,此时给容器中注入AnnotationAwareAspectJAutoProxyCreator(定义)。
-
调用refresh方法
-
方法中调用registerBeanPostProcessors方法注册后置处理器,会创建AnnotationAwareAspectJAutoProxyCreator对象
-
finishBeanFactoryInitialization()初始化剩下的单实例bean
1)创建业务逻辑组件和切面组件
2)后置处理器拦截组件创建过程(AnnotationAwareAspectJAutoProxyCreator拦截)
3)拦截过程:组件创建完之后判断是否需要增强,把切面的通知方法包装成增强器(Advisor),给业务逻辑组件创建代理对象
-
-
执行目标方法
-
代理对象执行目标方法
-
intercepet()拦截代理方法的执行
1)得到目标方法的拦截器链
2)利用拦截器的链式机制,以此进入每一个拦截器执行增强方法
-
-