spring注解版小结

Spring注解版

IOC

组件注册

@Configuration

告诉Spring这是一个配置类(可以看成是Spring的配置文件)

@Bean

在config下配置,代表将对象加入到容器中

(@Bean注解下的参数会默认的从ioc容器中取,这点很重要,它是隐式调用@Autowired)

@ComponentScan

我们使用这个去扫描指定包下的组件,只有规定了这个,我们在指定包下的类标注@Component,@Repository,@Service,@Controller等,才会被我们加入到ioc容器中

@ComponentScan(basePackages = {"com.jd.nxj"},excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
    //FilterType.ASSIGNABLE_TYPE:按照给定的类型过滤
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {User.class}),
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {HelloService.class})
    //还有自定义的type ->CUSTOM 需要实现TypeFilter接口 然后放在这里就好,这里不写了,以后如果用到再讲
})
@Configuration
@ComponentScan(basePackages = {"com.jd.nxj"},excludeFilters = {
       @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
       //FilterType.ASSIGNABLE_TYPE:按照给定的类型过滤
       @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {User.class}),
       @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {HelloService.class})
       //还有自定义的type ->CUSTOM 需要实现TypeFilter接口 然后放在这里就好,这里不写了,以后如果用到再讲
})
//Config下配置的一定生效
public class HelloConfig {

}

@Scope

(调整作用域)默认ioc管理创建对象都是单例的,在程序加载的时候就将对象创建好添加到容器中了,以后程序调用也不会再创建了,如果设置为prototype,在程序加载的时候并不会将对象创建好放入容器中,而是程序调用一次就创建一次。

 

我们可以设置常用的如下:

@Scope("prototype")
@Scope("singleton")

@Lazy

针对于Scope为singleton的,程序启动的时候就会创建好对象放入到容器中

标注这个注解意味着:程序启动的时候先不创建对象,在第一次调用的时候再创建对象,然后放入到容器中

@Lazy
@Bean("user2")
@Scope("singleton")//singleton;prototype
public User user(){
   System.out.println("添加user2到ioc容器中");
   return new User("张三",23,null);
}

*@Conditional

可以标注在上或者方法

这是一个接口,内部只有一个接受类信息的参数,需要这个类实现Condition接口

@Conditional(value = {ConditionalOnWindows.class})
@Bean("bill")
public User user2(){
System.out.println("bill add 2 ioc");
return new User("Bill Gates",66,null);
}

@Conditional(value = {ConditionalOnLinux.class})
@Bean("linus")
public User user3(){
System.out.println("linus add 2 ioc");
return new User("linus",55,null);
}
public class ConditionalOnWindows implements Condition {
/**
    * @param context 上下文信息
    * @param metadata 注释信息
    * @return
    */
   @Override
   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       //获取到ioc使用的beanFactory
       ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
       //获取到类加载器
       ClassLoader classLoader = context.getClassLoader();
       //能获取到bean定义的注册类
       BeanDefinitionRegistry registry = context.getRegistry();
       
       //获取当前环境信息
       Environment environment = context.getEnvironment();
       String property = environment.getProperty("os.name");
       if(property.toLowerCase().contains("windows"))return true;
       return false;
  }
}
public class ConditionalOnLinux implements Condition {

   @Override
   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       Environment environment = context.getEnvironment();
       String property = environment.getProperty("os.name");
       if(property.toLowerCase().contains("linux"))return true;
       return false;
  }
}

启动容器,结果只有bill add 2 ioc,这就是Conditional起作用的,他判断满足的

*@Import

组件名字是类的全路径名(调用的都是无参构造器,若没有无参构造器,使用@Import就会报错

快速给容器中导入一个组件(id默认是全类名,如:com.jd.nxj.properties.TestImport1)

//这个类一般是第三方的组件(我们想导入进来,我们可以使用方法@Bean或者直接导入)
public class TestImport1 {}

public class TestImport2 {}
@Import({TestImport1.class, TestImport2.class}) //将这个组件注册到容器
public class MyConfig2
*ImportSelector

组件名字是类的全路径名 springboot中这里用到而很多

@Import({TestImport1.class, TestImport2.class, MyImportSelector.class})

由于MyImportSelector实现了ImportSelector接口,因此并不是将这个类导入到容器中,而是将它里面方法返回的全类路径名的类导入到组件中!

//自定义逻辑需要导入的组件
public class MyImportSelector implements ImportSelector {
/**
    * 返回值就是导入到容器中的组件的全类名
    * @param importingClassMetadata 当前标注@Import注解的类的所有注解信息
    * @return
    */
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       return new String[]{TestImport3.class.getName(),TestImport4.class.getName()};
  }
}

此时TestImport3与TestImport4都加入到容器中了,这个和直接放在@Import中一样,但是这里可以做相应的逻辑判断。

ImportBeanDefinitionRegistrar

组件名字是可以自定义的

@Import({TestImport1.class, MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MyConfig2
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    if(registry.containsBeanDefinition("bill")){
        //注册进去(名字自己起)
        //第一个参数:组件名
        //第二个参数:BeanDefinition 是一个接口 我选择一个实现
        registry.registerBeanDefinition("myClass",new RootBeanDefinition(MyClass.class));
    }
}
}

*FactoryBean<T>

在和其他框架整合的时候 这个用的很多!

public class MyFactoryBean implements FactoryBean<Color>  {


@Override
public Color getObject() throws Exception {
    System.out.println("..");
    return new Color();
}

@Override
public Class<?> getObjectType() {
    return Color.class;
}

//返回的是true,代表添加到容器中是单例,否则是多实例,每次获取都会创建一个新的(调用getObject)
@Override
public boolean isSingleton() {
    return true;
}
}
@Bean
public MyFactoryBean myFactoryBean(){
return new MyFactoryBean();
}
Object bean = context.getBean("myFactoryBean");//获取的是getObject()返回的对象
System.out.println(bean.getClass());//class com.jd.nxj.properties.Color

Object bean2 = context.getBean("&myFactoryBean");//前面加个&,获取的就是myFactoryBea对象了
System.out.println(bean2.getClass());//class com.jd.nxj.properties.MyFactoryBean

小结:

(使用注解)给容器中注册组件的方式

  1. 包扫描+组件标注注解(就是使用@ComponentScan扫描 使用@Controller,@Repository等进行标注)【局限于我们自己写的组件】

  2. @Bean【使用第三方的包(第三方的和我们包扫描的肯定不同)】,我们使用@Bean自己加入到容器

  3. @Import【快速给容器中导入一个组件】

    1. 直接放在@Import注解中

    2. 通过实现ImportSelector的类,然后放到@Import注解中

    3. 通过实现ImportBeanDefinitionRegistrar类,然后放到@Import注解中

  4. 使用Spring提供的FactoryBean【但是还是通过@Bean加入到容器中,只不过虽然名字是这个名,但是这个对象是我们设置的对象】

    1. 默认按照名字获取到的是工厂bean调用getObject创建的对象

    2. 要获取工厂bean本身,我们需要给id前面加一个&来获取

bean生命周期

initMethod/destroyMethod

(主要针对于singleton)

对应xml中bean的配置

<bean id="dog" class="com.jd.nxj.domain.Dog" init-method="getName" destroy-method="getAge">

注解中也可以进行如下设置

@Bean(initMethod = "init",destroyMethod = "destroy")

这些方法必须要无返回值,无参数的!

% 调用结果
car constructor...
car init...
car destroy...

initMethod:是在调用完构造函数后就马上执行的

destroyMethod :是容器关闭的时候执行(如果是prototype即使关闭context,销毁也不会执行)

@PostConstruct/@PreDestroy

使用JSR250的注解来实现

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@PostConstruct
public void init(){
System.out.println("car init...");
}
@PreDestroy
public void destroy(){
System.out.println("car destroy...");
}

这个和@bean中的方法一样,都是标注在对象内部的方法,且只是针对于当前对象

InitializingBean/DisposableBean

这是Spring给我们提供的方式,去实现对应的接口

@Component
public class CustomLifeCycle implements InitializingBean, DisposableBean {
//Bean创建完成,且属性都赋值完,会调用
@Override
public void afterPropertiesSet() throws Exception {
    System.out.println("m:afterPropertiesSet");
}
//容器关闭的时候调用
@Override
public void destroy() throws Exception {
    System.out.println("m:destroy");
}
}

这样scope为单例的时候,每次被加载都会调用afterPropertiesSet,但是这个方法我测试的时候在第一位输出,所以了解一下知道有这个方法就好,这样写,容器中所有组件,在程序启动创建的时候都会调用这个创建和销毁,这和单独写@Bean的区别是@Bean只能在他声明的bean上起作用一次。

*BeanPostProcessor

(Spring底层bean注入值,Autowaired等等这些原理都是使用BeanPostProcessor)【@Autowaired 生命周期注解等都是】

原理就是:获取到注解所标识的方法,然后利用反射在相应的时机进行调用

实现这个接口,和上方的InitializingBean一样,作用于全局,任何一个bean要创建到入到容器前后都会执行相应的方法(我觉得有点乱,主要是暂时没有想到一个好的应用的场景,先放着吧,以后真的能用到的时候,回头知道有这个东西,然后自己再看自己使用框架版本,该方法加载的时机)

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessBeforeInitialization-->"+beanName);
    return null;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessAfterInitialization-->"+beanName);
    return null;
}
}

属性赋值

@Value

/**
* @Value使用
* 1.直接赋值,String,Boolean等
* 2.使用SPEL表达式
* 3.读取配置文件的值 (对应配置文件是: <context:property-placeholder location="a.properties"/>)【需要在容器能识别到的位置标注@PropertySource(value = {"classpath:/a.properties"}),一般标注在配置文件上】
*/
@Value("${user.name1}")//获取不到就是把这个当成一个字符串输出
private String name;
@Value("#{20+3}")//EL表达式
private Integer age;

@PropertySource

@Configuration
//需要在容器能识别到的位置标注@PropertySource(value = {"classpath:/a.properties"}),一般标注在配置文件上
@PropertySource(value = {"classpath:/a.properties"})//
//@ComponentScan("com.jd.nxj.domain")
public class MainConfigOfPropertyValue {
@Bean
public User user(){
    return new User();
}
}
@Value("${user.name1}")
private String name;
#a.properties
user.name1=寧新傑
dog.name=小红112312
dog.age=5

读取配置文件的几种方式

@PropertySource("classpath:/data.properties")
@Configuration
public class MyConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${data.user}")
private String user;///第一种,在外部设置变量通过@Value获取配置文件信息
private String jdbc_url;
//private StringValueResolver valueResolver;

@Bean("dev")
public DataSource dataSource1(
        @Value("${data.password}") String password//第二种,在参数里面直接使用@Value获取配置文件信息(和第一种一样都是用@Value来获取值)
        ,@Value("${data.dirverclass}") String driverclass
        //@Value("${data.user}") String user
        //,@Value("${data.jdbcurl}") String jdbcurl
) throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser(user);
    dataSource.setPassword(password);
    dataSource.setDriverClass(driverclass);
    dataSource.setJdbcUrl(jdbc_url);
    return dataSource;
}

@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
    jdbc_url=resolver.resolveStringValue("${data.jdbcurl}");//通过值解析器来获取配置文件中的值
}
}

**@Autowired

默认按照类型寻找,如果按照类型找到组件只有一个,那么直接装配,如果有多个,则按名字寻找,当然我们也可以使用如下注解

  1. 按类型寻找,如果类型只有一个则自动装配

  2. 如果类型有多个则按照名字寻找,如果找到则自动装配,否则报错!(除非某一个bean上标注了@Primary,则加载这个bean)

@Autowired
@Autowired(required = false)
ElementType.CONSTRUCTOR, //可以标注在构造器上

ElementType.METHOD,//方法上
//自动调用方法,参数就是从ioc中取得参数  
@Autowired
   public void setTest(T1Dao dao, MyConfig3 config3){
       System.out.println("方法自动装配");
       System.out.println(dao);
       System.out.println(config3);
  }

ElementType.PARAMETER,//参数上

ElementType.FIELD,//字段上

ElementType.ANNOTATION_TYPE//注解类型上

ℹ️总而言之理解的就是:会在容器初始化后调用方法,参数是从容器中获取

//直接在配置文件下使用@Bean的参数实际上就是去ioc容器中取(隐式调用了@Autowired)
@Bean("aliasName")
public T1Service t1Service(@Autowired @Qualifier("user") User user, Dog dog){
   T1Service t1Service = new T1Service();
   t1Service.setUser(user);
   t1Service.setDog(dog);
   return t1Service;
}
@Qualifier

指定我要寻找的是哪个组件@Qualifier("user2")

需要结合@Autowaired使用

@Qualifier("t3")
@Autowired
private T1Dao dao;

public void test(){
System.out.println(dao);
//dao.daoTestMethod();
}
@Primary

当我们不指定@Qualifier ,而在注册组件上标注@Primary时,有相同类型的时候,默认装配这个。

@Primary如果在一个类型上标注多次,我们加载的时候如果也没使用@Qualifier,就会报错

@Primary
@Bean
public T1Dao t3(){
return new T1Dao(3);
}

@Resource

是JSR250规范的注解:可以和@Autowired一样自动装配,不同的是它是优先按照名字寻找(因此@Primary可能不会生效)

  1. 按名字寻找,如果找到自动装配

  2. 如果找不到则按照类型寻找,如果只找到一个则自动装配,否则报错(除非某一个bean上标注了@Primary,则加载这个bean)

@Resource(name = "t1Dao")

@Inject

是JSR330规范的注解:@Autowired功能一样(但是它里面没什么属性)【了解下得了】

-- 需要导入如下依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

显然使用@Autowired功能已经覆盖了@Inject

Aware接口

Spring在创建的时候,底层实现Aware的接口的类,会像回调函数一样,将对应的参数回调进来,比如实现了ApplicationContextAware,那么我们就可以在创建完这个对象后获取到ioc容器中的ApplicationContext对象

 

xxxAware:将他看成Spring的回调

xxxAwareProcessor:就是xxxAware回调功能的实现

@Component
public class TestAware implements ApplicationContextAware,BeanNameAware {
//@Autowired 直接Autowired也能拿到
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context=applicationContext;
    System.out.println("ioc->Aware-->"+applicationContext);
}

@Override
public void setBeanName(String name) {
    System.out.println("获取到bean的名字"+name);
}
}

@Profile

切换环境使用(如:开发环境、测试环境、生产环境)【可以标注在上和方法上】

增加了这个注解就是加了一个环境标志,只有这个环境被激活,这个bean才能注册到ioc容器中

激活的方式:

  1. 命令行的方式【-Dspring.profiles.active=dev】

  2. 代码的方法,如下:

//ApplicationContext context = new AnnotationConfigApplicationContext(MyConfigOfProfile.class);
//代码的方法激活(其实1、3、4就是上方有参构造的代码,我们要在容器启动之前加入配置环境,就是第二步,当然可以配置多个环境)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();//1
context.getEnvironment().setActiveProfiles("dev","test");//2
context.register(MyConfigOfProfile.class);//3
context.refresh();//4

AOP

@Before

前置通知

@After

后置通知

@Around

环绕通知

@AfterReturning

返回通知

@AfterThrowing

异常通知

@Pointcut

定义切入点(一般在切面类里面定义切入点)

@Aspect

告诉Spring这是一个切面(标注在切面类上)

@EnableAspectJAutoProxy

开启切面的注解功能(标注在配置类上)

底层使用的原理是动态代理

  1. 需要导入AOP的jar包:spring-aspects

  2. 定义一个业务逻辑类(我们的切面要切入这些类的方法)

  3. 定义一个切面类【@Aspect 告诉Spring这是一个切面类】

    1. 前置通知(@Before)

    2. 后置通知(@After)

    3. 返回通知(@AfterReturning)

    4. 异常通知(@AfterThrowing)

    5. 环绕通知(@Around)

  4. 给切面类的目标方法标注何时运行

  5. 将切面类和业务逻辑都加入到容器中(使用@Bean、@Component、@Import方式都行)

  6. 必须告诉Spring哪个是切面类(切面类上加一个注解@Aspect

  7. 给配置类中加@EnableAspectJAutoProxy【开启基于注解的AOP模式】

总结就如下三点:

  1. 将业务逻辑组件和切面类都加入到容器中,告诉Spring哪个是切面类(@Aspect)

  2. 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)

  3. 开启基于注解的AOP模式@EnableAspectJAutoProxy

=======================

* *

* *

* *

=======================

0.业务逻辑组件示例(加入到容器)如下:

@Component
public class MyClass {

   public void myMethod(){
       System.out.println("(:run myMethod!:)");
  }
}

1.切面类(把各个切入的方法都写上,并且告诉Spring这是一个切面类,并加入到容器)

执行顺序:

  1. @Around标注的方法中proceed()前的方法执行

  2. @Before标注的方法执行

  3. @Around标注的方法中proceed()后的方法执行

  4. @After标注的方法执行

  5. @AfterReturning/@AfterThrowing标注的方法执行

里面定义切入点(@Pointcut)

@Component
@Aspect
public class LogAspects {
   @Pointcut("execution(public * com.jd.nxj.aop.MyClass.*(..))")
   public void pointCut(){}

   @Before("pointCut()")
   public void logStart(){
       System.out.println("log start");
  }
   @After("pointCut()")
   public void logEnd(){
       System.out.println("log end");
  }
   @AfterThrowing("pointCut()")
   public void logException(){
       System.out.println("log exception");
  }
   @AfterReturning("pointCut()")
   public void logReturn(){
       System.out.println("log return");
  }

   @Around("com.jd.nxj.aop.LogAspects.pointCut()")
   public void logAround(ProceedingJoinPoint point) throws Throwable {
       System.out.println("log around before");
       point.proceed();
       System.out.println("log around after");
  }
}

2.配置文件中开启

@Configuration
@ComponentScan(value = {"com.jd.nxj.aop"})
@EnableAspectJAutoProxy//开启切面的注解功能
public class MyConfigOfAOP {}

扩展开始了

BeanFactoryPostProcessor

所有bean的定义信息被加载了,但是bean并没有被初始化的时候调用

Modify the application context's internal bean factory after its standard
initialization. All bean definitions will have been loaded, but no beans
will have been instantiated yet. This allows for overriding or adding
properties even to eager-initializing beans.

根据标准修改应用程序上下文的内部bean工厂初始化。
所有bean定义都已加载,但没有bean还没有实例化。
这允许重写或添加属性甚至可以用于急于初始化bean。

 

BeanDefinitionRegistryPostProcessor

可以利用它给容器中再添加一些组件

这个是BeanFactoryPostProcessor的子接口

它的加载时机是在bean定义信息将要写入之前【这里讲的不太规范,实际上是,我们定义要加入到容器中和默认的如下几个组件名字都已经定义完成了,只不过我们可以在这里继续加上我们自定义的】

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

(由此可见,它是在他的父接口方法之前执行的)

❤️***加载过程

【看源码能看到,先找BeanDefinitionRegistryPostProcessor,先触发他的postProcessBeanDefinitionRegistry(),再触发postProcessBeanFactory(),然后再找BeanFactoryPostProcessor,触发他的postProcessBeanFactory()】

过程:

  1. 组件名称定义完成(registry.registerBeanDefinition)

  2. 执行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry()

  3. 执行BeanDefinitionRegistryPostProcessor实现BeanFactoryPostProcessor的方法

  4. 执行BeanFactoryPostProcessor的方法

  5. 执行组件的构造函数(组件的实例化)

  6. 执行BeanPostProcessor的方法

❤️*BeanDefinitionRegistry概念

BeanDefinitionRegistry就是bean信息的保存中心,以后BeanFactory就是按照它保存的每个bean的定义信息创建bean的 (包括,bean是单例还是原型,bean的类型等等信息都是存在BeanDefinitionRegistry的)

在这里就知道了,此时注册,只是注册bean的名字,而bean还没有真正被初始化加入到容器中

最后真正加入到容器中,实例化的时候,是按照bean定义的名字来创建的(什么意思呢?【默认都是单例】假如我自己注册进去一个beanName,但是这个Class我也包含在容器中了,Spring会按照我们定义的名字来创建两个组件,组件名字不一样,虽然组件类型一样,但是并不是同一个对象)

最后加入到容器中的组件信息都是从BeanDefinitionRegistry中拿的,因此如果我们注册时候只起名字而不放入相应的类作为组件,如:registry.registerBeanDefinition("myBeanName",new RootBeanDefinition());这样当容器创建代码走到了要创建myBeanName组件的时候,找不到类型,就会报错,就是创建组件异常。

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

//BeanDefinitionRegistry就是bean信息的保存中心,以后BeanFactory就是按照它保存的每个bean的定义信息创建bean的
//(包括,bean是单例还是原型,bean的类型等等信息都是存在BeanDefinitionRegistry的)
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    //此时输出注册了10个名字
    System.out.println("postProcessBeanDefinitionRegistry,这个在bean定义信息将要写入之前,此时bean的数量:"+registry.getBeanDefinitionCount());
    //如果new RootBeanDefinition()中不
    registry.registerBeanDefinition("myBeanName",new RootBeanDefinition(MyExtClass.class));
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
    //此时输出11个
    System.out.println("这个在bean定义信息写入之后,初始化之前,此时bean的数 量:"+beanFactory.getBeanDefinitionCount());
}
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Object myBeanName1 = context.getBean("myBeanName");
Object myBeanName2 = context.getBean("myExtClass");
System.out.println(myBeanName1.getClass());//class com.jd.nxj.ext.MyExtClass
System.out.println(myBeanName2.getClass());//class com.jd.nxj.ext.MyExtClass
System.out.println(myBeanName1==myBeanName2);//false

ApplicationListener

监听容器中发布的时间。事件驱动模型开发

只要容器中发布了事件,事件监听器就会监听到这个事件,进行回调

标注什么泛型,则只监听标注的泛型或者它的子类,不标注则全监听

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {

//当容器中发布此事件后,方法触发
@Override
public void onApplicationEvent(ApplicationEvent event) {
    System.out.println("收到事件"+event);
}
}
//这样代表我只接收ContextRefreshedEvent的事件
@Component
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

//当容器中发布此事件后,方法触发
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    System.out.println("收到事件"+event);
}
}
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener

监听ApplicationEvent及下面的子事件

ioc容器在refresh【所有bean完全创建完】后发布org.springframework.context.event.ContextRefreshedEvent事件,

在容器关闭后,发布org.springframework.context.event.ContextClosedEvent事件

发布事件
context.publishEvent(new String("自定义事件01"));//默认是PayloadApplicationEvent(它也是继承了ApplicationEvent)
context.publishEvent(new ApplicationEvent("自定义事件02") {
});

@EventListener

(标注什么那么classes就监听哪些事件,不是标注的或者它的子类我不监听)

什么都不标注,则全部监听,因为事件必须是ApplicationEvent,默认就是它,那么所有要么是它,要么是它的子类,所以全部监听

标注这个注解的方法,就相当于实现了ApplicationListener

这样很方便

@Component
public class MyEventListener {
//只接受ContextRefreshedEvent与ContextClosedEvent事件
@EventListener(classes ={ContextRefreshedEvent.class, ContextClosedEvent.class} )
public void sdsdasd(ApplicationEvent event){
    System.out.println("标注@EventListener注解的回调处理1"+event);
}
//默认接收所有事件
@EventListener
public void sdsdasdsd(ApplicationEvent event){
    System.out.println("标注@EventListener注解的回调处理2"+event);
}
}

这个接口点开,看EventListenerMethodProcessor

内部实现了SmartInitializingSingleton

在我们最后创建组件的时候(首先遍历一遍名字创建组件,然后再遍历一遍名字,取出对象,判断它是不是SmartInitializingSingleton的实例,如果是则调用afterSingletonsInstantiated()完成回调)

至于@EventListener与EventListenerMethodProcessor的绑定关系,以及我参数ApplicationEvent的值从哪来的,这个还没看出来,需要继续分析

是这样:

EventListenerMethodProcessor里面实现了SmartInitializingSingleton接口的时候调用了processBean()

而processBean(),里面扫描每个组件,找EventListener标识的方法,

然后通过context.addApplicationListener(applicationListener);增加监听器

此时疑惑就解决了,原来他就是在实现的方法中寻找容器中哪些方法标注了@EventListener,如果标注了,将这个监听器增加到容器中,等着后面一起调用!

原理:(因为EventListenerMethodProcessor实现了SmartInitializingSingleton,实现的方法中调用寻找@EventListener注解的方法调用)

其中EventListenerMethodProcessor容器默认添加进去,其中的SmartInitializingSingleton怎么会生效呢?如下:
refresh();
finishBeanFactoryInitialization(beanFactory);
beanFactory.preInstantiateSingletons();
下方判断是不是SmartInitializingSingleton的实例,如果是则调用afterSingletonsInstantiated();方法

 

 

❤️很重要的总结

this.
singletonObjects //是我们的ioc判断是否注册的容器
alreadyCreated //是我们创建完放的地方,

重要的理解总结就是:我们判别组件是否注册进了singletonObjects,所以说,这个是判别ioc容器是否注册了组件的基本容器(或者说我们的组件实例基本上都放在了这里!)


beanFactory.registerSingleton()这个意思就是将组件加入到容器中去!

 

创建bean过程

  1. invokeAwareMethods(beanName, bean);//判别当前类是否实现了*Aware,如果是则执行
  2. applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//BeanFactory的Before
  3. invokeInitMethods(beanName, wrappedBean, mbd);//执行比如我们在@Bean中加initMethod这个
  4. applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);//BeanFactory的After

 

 

也就是说,@EnableAspectJAutoProxy导入的组件,给我们注册了实现InstantiationAwareBeanPostProcessor的AnnotationAwareAspectJAutoProxyCreator类

而实现BeanPostProcessor的组件在任何组件初始化之前,都会被容器寻找一下判别它是不是InstantiationAwareBeanPostProcessor,如果是则调用内部相应方法【postProcessBeforeInstantiation】,如果这个方法返回不是null,则调用【postProcessAfterInitialization】方法,如果返回不为null,如果不为null则返回,也就是说相当于组件创建返回给的就是这个。

 

 

 

 

个人随时记录总结:

关于BeanPostProcessor作用范围以及加载的疑问解决

在加载配置类的时候new AnnotationConfigApplicationContext(MyConfigOfAOP.class);

  • refresh();

  • registerBeanPostProcessors();

  • PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this【applicationContext】);

    1. //内部实现:
      String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
      //通过断点能够看到,我们自己添加的实现 BeanPostProcessor的类的名字在这里获取到了

由此可见,实现BeanPostProcessor的组件只要注册到容器中,确实作用于全局。

何时加载

请教磊哥:Spring加载的时候,先统计要加入到容器中所有组件的元数据(类的全路径,它上面标注的注解等),此时并没有将组件注册到ioc容器中,统计完成后,才会将组件依次注册进去。(这样就解决了一个疑问,就是如何保证@Condition的类先注入进来才能保证,显然是不需要@Condition判断的类先进来,我只需要配判断元信息有没有我需要的类的时候,就解决了,如果有,则加载这个类,调用方法,这个就是整个思想!)

 

prepareRefresh();
/*
1.标记容器开启(判断是否开启debug)
2.验证验证所有标记为必需的属性都是可解析的:
3.设置earlyApplicationListeners与applicationListeners

*/

===

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//告诉子类刷新内部bean工厂。
//获取beanFactory


===
   
//在这里注册了单例(environment;systemProperties;systemEnvironment)
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

===


//获取BeanDefinitionRegistryPostProcessor
invokeBeanFactoryPostProcessors();

 

//处理配置组件定义 processConfigBeanDefinitions()

在这个方法中,我们寻找下面哪些类是配置类

 

 

将配置类找出来,加入到configCandidates

 

这个方法里面就解析了我们注册的能扫到的所有组件 //下面的代码,完成了解析 ​ parser.parse(candidates); ​ parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
BeanDefinitionRegistryPostProcessor
BeanFactoryPostProcessor

 

//准备此上下文以进行刷新。
prepareRefresh ();


//准备在此上下文中使用的bean工厂。

prepareBeanFactory (beanFactory);


//允许在上下文子类中对bean工厂进行后处理。

postProcessBeanFactory (beanFactory);

//调用上下文中注册为bean的工厂处理器。

invokeBeanFactoryPostProcessors (beanFactory);

//注册拦截bean创建的bean处理器。

registerBeanPostProcessors (beanFactory);

//为这个上下文初始化消息源。

initMessageSource ();

//为这个上下文初始化事件多播器。

initApplicationEventMulticaster ();
//初始化特定上下文子类中的其他特殊bean。
onRefresh ();

//检查侦听器bean并注册它们。

有关registerlistener ();

//实例化所有剩余的(非延迟-init)单例。(这个里面先执行beanpostprocess 这个里面再执行 *Aware)(也不是 *Aware后也执行beanpostprocess)
finishBeanFactoryInitialization (beanFactory);

//最后一步:发布相应的事件。

finishRefresh ();

获取再测试一下beanpostprocess,打断点开什么时候触发

finishBeanFactoryInitialization (beanFactory);

//实例化所有剩余的(非延迟-init)单例。(这个里面先执行beanpostprocess 这个里面再执行 *Aware)(也不是 *Aware后也执行beanpostprocess)

看看是哪个

 

*Aware与BeanPostProcessor同时存在

@Component
public class TestAware implements ApplicationContextAware,BeanNameAware {
public TestAware() {
    System.out.println("TestAware构造器");
}

//@Autowired 直接Autowired也能拿到
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context=applicationContext;
    System.out.println("ioc->Aware-->"+applicationContext);
}

@Override
public void setBeanName(String name) {
    System.out.println("获取到bean的名字"+name);
}
}

标注了*Aware接口的类,加入到容器中,顺序是:

  1. 构造函数

  2. *Aware

  3. 然后BeanPostProcessor的postProcessBeforeInitialization***方法

  4. (个人感觉这个组件加入到容器的时机是在这里)

  5. 然后BeanPostProcessor的postProcessAfterInitialization***方法

TestAware构造器 获取到bean的名字testAware ioc->Aware-->org.springframework.context.annotation.AnnotationConfigApplicationContext@1761e840, started on Thu Nov 26 12:52:57 CST 2020 前postProcessBeforeInitialization-->testAware 后postProcessAfterInitialization-->testAware

 

 

 

BeanPostProcessor(整个流程!)

整个流程:refresh()-->finishBeanFactoryInitialization()-->beanFactory.preInstantiateSingletons()-->getBean()-->doGetBean()-->createBean()-->doCreateBean()-->initializeBean()【line595】

 

 

 

 

 

 

 

posted @ 2020-12-07 09:30  程序杰杰  阅读(210)  评论(0编辑  收藏  举报