Spring注解补充(一)

发布时间 2023-03-22 19:12:06作者: 情韵

注解补充

挑一些常用,但是深入不多的总结一下。

Bean的生命周期

在@Bean注解中,添加init属性destroy属性

@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public User user() {
    return new User();
}

使用@PostContructor@PreDestory

官网描述:

官网说这 JSR-250@PostConstruct@PreDestroy注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。

image-20230225092910663

image-20230225092658826

代码实现:

自己定义初始化和结束的方法,分别加上两个注解。

@PostConstruct
public void initJSR250(){
    System.out.println("User.initJSR250");
}

@PreDestroy
public void destroyJSR250(){
    System.out.println("User.destroyJSR250");
}

实现InitializingBean 以及DisposableBean 接口

代码实现:

 public class User implements InitializingBean, DisposableBean {
	
     ......

     @Override
     public void destroy() throws Exception {
         System.out.println("User.destroy  ---- bean");
     }

     @Override
     public void afterPropertiesSet() throws Exception {
         System.out.println("User.afterPropertiesSet ----- bean");
     }
 }

这些声明周期的注解、方法或者属性有着明显的先后顺序。

简单测试:

 /*
  * Copyright (c) 2006, 2023, wuyahan 编写
  *
  */
 package cn.lele.entity;

 import lombok.AllArgsConstructor;
 import lombok.Data;
 import org.springframework.beans.factory.DisposableBean;
 import org.springframework.beans.factory.InitializingBean;

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

 /**
  * <p>Project: ssm - User
  * <p>Powered by wuyahan On 2023-02-24 09:36:11
  *
  * @author wuyahan [tianwenle2000@163.com]
  * @version 1.0
  * @since 17
  */
 @Data
 @AllArgsConstructor
 public class User implements InitializingBean, DisposableBean {


    private int id;

    private String name;

    private boolean sex;

    public User(){
        System.out.println("User类的构造方法执行");
    }

    public void save(){
        System.out.println("User.save方法执行了");
    }


     @PostConstruct
     public void initJSR250(){
         System.out.println("User.initJSR250---------->@PostConstruct");
     }

     @PreDestroy
     public void destroyJSR250(){
         System.out.println("User.destroyJSR250----------->@PreDestroy");
     }


     public void initMethod(){
         System.out.println("User.initMethod------->@Bean属性");
     }



     public void destroyMethod(){
         System.out.println("User.destroyMethod-------->@Bean属性");
     }


     @Override
     public void destroy() throws Exception {
         System.out.println("User.destroy  ----> 实现Spring提供的接口");
     }

     @Override
     public void afterPropertiesSet() throws Exception {
         System.out.println("User.afterPropertiesSet ----> 实现Spring提供的接口");
     }
 }

运行测试:

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        User user = context.getBean(User.class);
        user.save();
        context.close();
    }
}

运行结果:

image-20230225093954895

可以发现执行顺序: JSR250的两个注解 > 实现Spring提供的接口 > 在@Bean中的属性

简单查看源码

自己构建Spring源码项目真的很好用,阅读起来也方便,还能自己加注释。

当前版本: Spring 5.3.x

将断点打在设置的Bean属性的初始化方法上,通过Debug的调用栈可以发现:

image-20230225144345960

image-20230225144304923

继续把Debug调试在1800行:

image-20230225144602503

当beanName为user时,JSR250已经执行。

image-20230225144709326

执行下一步:

image-20230225144754714

可以发现 实现Spring的初始化和关闭的接口以及给Bean添加初始化方法和关闭的方法是在一起执行的。

继续跟进1800行的代码,给这两行代码打上断点:

image-20230225145022711

继续Debug:

image-20230225145127038

执行下一步:

image-20230225145145882

继续执行下一步:

image-20230225145209004

可以发现Spring提供的两个接口执行顺序在自己给Bean添加属性的上面,所以先执行实现那两个接口的方法。


继续给User的JSR250注解的初始化方法加上断点:

image-20230225145617029

继续执行下一步:

image-20230225145643990

image-20230225145654755

image-20230225145704960

可以发现在1796行的代码就是JSR250注解的方法的入口。

image-20230225145824787

所以 对比三个初始化方法的源码位置,可以发现:JSR250 > 实现Spring的两个接口 > Bean属性添加方法

额外补充:在1796行代码继续跟进,发现里面是对BeanPostProcessor的处理。这个后面再继续补充。

image-20230225151202957

@Import导入

基本导入

注入一个类:

public class User {
    
}

在配置类上导入:

 @ComponentScan("cn.lele")
 @Import(User.class)
 public class AppConfig {


 }

输出容器中Bean的名字:

public class Main {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name = " + name);
        }

    }

}

image-20230225101619018

实现ImportSelector接口

image-20230225102210549

在配置类上导入:

 @ComponentScan("cn.lele")
 @Import({User.class, MySelector.class})
 public class AppConfig {


 }

查看导入Bean的名字:

image-20230225102141193

实现MyImportBeanDefinitionRegistar接口

我们可以在ioc容器创建时,管理并且注册bean。通过实现MyImportBeanDefinitionRegistar接口。

image-20230225103115989

在配置类上导入:

 @ComponentScan("cn.lele")
 @Import({MyImportBeanDefinitionRegistrar.class, MySelector.class})
 public class AppConfig {


 }

查看容器中所有Bean的名字:

image-20230225103215116

@CompnentScan包扫描

这个注解还是有一些小坑的......

包含include小坑

image-20230225152400035

CompnentScan注解默认开启自动检测带有@Component@Repository@Service@Controller。所以就会有一个坑,当我们默认去include的时候,如果不关闭默认过滤,添加将不会发挥作用。

当我们只想加载Service注解和Controller注解的时候,如果不取消默认的扫描,包含将会失效:

image-20230225153416297

取消默认扫描:

image-20230225153606111

排除不受影响

自定义扫描CUSTOM

自定义扫描需要实现TypeFilter 接口

public class MyTypeFilter implements TypeFilter {


    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

        // 获取Bean的名字
        String className = metadataReader.getClassMetadata().getClassName();
        // 包含Dao的允许加载
        if (className.toUpperCase().contains("DAO")){
            return true; //允许加载
        }

        return false; // 不允许加载
    }
}

type选择为FilterType.CUSTOM

@Configuration
@ComponentScan(
    value = "cn.lele" ,
    includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class}),
    useDefaultFilters = false // 包含都需要关闭这个默认过滤
)
public class AppConfig {



}

运行结果:

image-20230225154920468