Spring进阶学习 03、Bean的生命周期

简介: Spring进阶学习 03、Bean的生命周期

一、认识BeanPostProcessor(后置处理器)


1.1、介绍BeanPostProcessor


原理介绍


BeanPostProcessor能够让我们程序员来对Bean的生成进行干涉,其能够处理当前的bean实例,我们可以对其进行一些操作。



确切的来说该接口能够实现实例化前后的一些操作,只针对于该接口不涉及其扩展的接口,提供了两个方法分别是初始化前与初始化后!

核心:


若是你自己实现了BeanPostProcessor接口并且交由Spring容器管理,那么其就会生效,此时你可以对生成的实例bean来进行一些后置处理操作。

实现的BeanPostProcessor接口作用与所有的Spring的bean实例,并不仅仅针对于某一个!

源码:


其默认实现了两个方法,两个方法的参数都是一致的都可以拿到对应spring生成的bean对象以及bean的名称,默认都是返回的bean对象即Spring生成的bean实例


public interface BeanPostProcessor {
    //初始化前
  @Nullable
  default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  return bean;
  }
    //初始化后
  @Nullable
  default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  return bean;
  }
}



测试demo



@Component
public class ChangluBeanPostProcessor implements BeanPostProcessor {
    //初始化前
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization=》Bean:"+bean+",beanName:"+beanName);
        return bean;
    }
    //初始化后
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization=》Bean:"+bean+",beanName:"+beanName);
        return bean;
    }
}
@ComponentScan("xyz.changlu")
public class Config {
}
@Component
public class User {
}
@Component
public class UserService {
}
//测试类
public class Main{
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
        System.out.println(applicationContext.getBean("userService", UserService.class));
    }
}




1.2、后置处理器小实战(实现自定义注解赋值)


案例目的:通过使用后置处理器来对某个属性进行注入值。




既然Spring给我们提供的后置处理器能够得到Bean对象以及BeanName,我们就可以对其来进行一些操作:


//该注解用于注入值
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ChangLu {
    String value();
}
//实体类
@Component
public class User {
    @ChangLu("changlu")
    private String username;
    public void test(){
        System.out.println(username);
    }
}
@ComponentScan("xyz.changlu")
public class Config {
}
//后置处理器:来通过反射实现注入操作
@Component
public class ChangluBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //对于@ChangLu注解中的值注入到对应属性中
        Class<?> aClass = bean.getClass();
        for (Field field : aClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(ChangLu.class)) { //检测其是否包含该注解
                ChangLu annotation = field.getAnnotation(ChangLu.class);
                String value = annotation.value();
                field.setAccessible(true);
                try {
                    field.set(bean, value);
                    System.out.println("成功注入值:" + value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }
}


测试方法:


public class Main{
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
        applicationContext.getBean("user", User.class).test();
    }
}




二、正式进入Bean的生命周期世界


脑图预览

单个Bean的生命周期全貌如下图,在没有了解Bean的生命周期前,我们对于Bean的基本概念仅仅停留在一个实例化阶段,当大致学习好之后我们对于Spring的全貌就有了一大部分的了解了!



在Spring的实际源码中就继承实现了BeanPostProcessor以及其他相关的接口,上图中没有标注红的就是Spring自己实现的一些抽象类,它们来为我们提供了一些的现成方法,例如AOP、参数注入等等,这些都是基于Bean的生命周期方法来实现的!


下面我们就根据这些接口来自己实现一些关于指定Bean的一些功能。



2.1、实例化前


对于声明@Component的实体类,Spring首先会生成BeanDefinition,其想要实例化为一个对象前需要经历实例化前的这个过程。


方式:通过自定义一个后置处理其来实现InstantiationAwareBeanPostProcessor接口并重写postProcessBeforeInstantiation方法即可定义bean的实例化前方法,你可以注意到其中参数仅仅只有class以及beanName名称。



//指定类实例化
@Component
public class User {
    public User(){
        System.out.println("实例化");
    }
}
//自定义后置处理器
@Component
public class ChangluBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    /**
     * 在实例化前执行的方法,此时还没有实例化
     * @param beanClass 实例化的class类
     * @param beanName 此次产生bean实例对应的名称
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if("user".equals(beanName)){
            System.out.println("实例化前");
        }
        //若是中途这里直接返回对象的话,后面的实例化以及实例化后的步骤会直接被过滤掉。返回的对象就会直接作为bean实例来存储
        return null;
    }
}


其他配合辅助方法:只在这部分来进行展示,其他部分就不作演示了


@ComponentScan("xyz.changlu") //进行自动扫描
public class Config {
}
public class Main{
    public static void main(String[] args) throws IOException {
        //这里我们仅需引入该类即可实现User的实例化以及实例化前方法执行
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
    }
}



注意:若是实例化前就返回指定一个对象,其就会作为对应beanName的bean实例,此时实例化后包含实例化这些步骤都不会执行了!



2.2、实例化和推断构造方法


实例化前方法结束之后,就要进行bean的实例化了,在真正进行bean的实例化时还要进行推断构造方法选择指定构造器来进行实例化的产生!


推断构造方法:分为下面多种情况,简而言之Spring会根据你的配置来选择一个构造器来进行bean对象的初始化


①若是你仅仅有一个有参构造方法(没有无参构造),其带有一个参数,spring对其实例化时就会默认使用该有参构造,对于参数spring会去容器中寻找是否有对应的bean实例(先type,后name)有的话就注入。


@Component
public class UserService {
    public UserService(User user){
        System.out.println(user);
        System.out.println("1个有参构造器");
    }
}


②若是你类中有多个有参构造但是没有定义无参构造就会报错,那时因为只有多个有参构造方法时,Spring默认去使用无参的构造方法的原因!


//此时执行一定会报错,因为有多个有参构造器时并且这些构造器没有标注特定的注解,Spring会默认去找无参构造器来进行实例化
@Component
public class UserService {
    public UserService(User user){
        System.out.println(user);
        System.out.println("1个有参构造器");
    }
    public UserService(User user,Person person){
        System.out.println(user);
        System.out.println(person);
        System.out.println("2个有参构造器");
    }
}


③若是有多个有参构造方法以及无参构造方法时,我想要使用指定的有参构造,就需要配合@Autowired到对应的构造器方法上即可推断该构造方法是要进行实例化的


@Component
public class UserService {
    public UserService() {
    }
    @Autowired  //表示一定使用该构造器来进行实例化
    public UserService(User user){
        System.out.println(user);
        System.out.println("1个有参构造器");
    }
    public UserService(User user,Person person){  //注意:若是@Autowired加在这个构造器上时,若是person没有在Sprng容器中找到就会报错!
        System.out.println(user);
        System.out.println(person);
        System.out.println("2个有参构造器");
    }
}


若是在两个构造方法上都加入@Autowired也会进行报错!除非你在@Autowired注解中添加required属性,默认为true表示一定要进行依赖注入在该属性或方法时,这也就导致为什么都加入会报错的情况,若是设置某个required=false就不会报错啦!

若是多个有参构造上都设置了@Autowired(required=false),那么Spring会自动推断去使用参数最多的有参构造器来先进行实例化。需要额外提下的是,若是最多参数的构造器其参数在Spring容器中找不到,那么就不会采用其构造器进行实例化,进而再去尝试使用参数少的构造器来进行实例化!

若是多个有参构造器上设置了@Autowired(required=false),他们都有相同的多个参数属性,并且这些参数属性都有够在Spring容器中找到,此时选择从上到下第一个构造器来进行实例化!


2.3、实例化后与填充属性阶段


当实例化对象好了之后,我们就可以进入到实例化后中了!


顺序为:实例化—实例化后—填充属性


方式:自定义后置处理器继承InstantiationAwareBeanPostProcessor接口重写postProcessAfterInstantiation方法,由于执行该方法前bean已经进行了实例化,所以这里的参数就有bean以及beanName了。


@Component
public class UserService {
    private User user;
  //实例化
    public UserService() {
        System.out.println("实例化");
    }
  //填充属性阶段
    @Autowired
    public void setUser(User user) {
        this.user = user;
        System.out.println("填充属性");
    }
}
//同样该实例化后方法在Spring生成Bean对象时自动会执行
@Component
public class ChangluBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    /**
     * 实例化后
     * @param bean bean对象
     * @param beanName 对应的beanName
     * @return 返回true表示继续执行之后的一系列生命周期;false表示不执行之后的生命周期
     * @throws BeansException
     */
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if ("userService".equals(beanName)) {
            System.out.println("实例化后");
        }
        return true;
    }
}


其他相关类:


@Component
public class User {
    public User(){
    }
}
@ComponentScan("xyz.changlu")
public class Config {
}



2.4、初始化前


BeanPostProcessor接口中提供的两个方法一个是初始化前,另一个是初始化后。我们也可以通过继承BeanPostProcessor接口InstantiationAwareBeanPostProcessor接口来实现初始化前的方法,见如下:


下面通过实现InstantiationAwareBeanPostProcessor接口来实现初始化前的方法执行,与实现BeanPostProcessor其实性质都是相同的:


@Component
public class ChushihuaBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    //初始化前方法中的小应用:来手动执行bean对象中的某个方法
    //也就是说再执行实例化后方法时,会去额外对该实例化对象中的方法进行遍历查看是否有指定注解,有的话执行该方法
    if("userService".equals(beanName)){
        System.out.println("初始化前");
        Class<?> aClass = bean.getClass();
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method : methods) {
            //若是有@PostConstruct注解就就执行该方法
            if(method.isAnnotationPresent(ChangLu.class)){
                try {
                    method.invoke(bean,null);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    return bean;
}



其他辅助方法:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ChangLu {
    String value();
}
@Component
public class UserService {
    @ChangLu  //其实该注解表示初始化时的方法
    public void test(){
       System.out.println("初始化前中通过反射执行的方法!");
    }
}



2.5、初始化


初始化在生命周期阶段位置为:填充属性—初始化前—初始化—初始化后,一旦初始化前方法执行好后就会执行初始化方法!


初始化方法可以通过两种方式实现:


//方式一:通过实现InitializingBean接口
@Component
public class UserService implements InitializingBean{ //1、实现InitializingBean接口
    //2、重写afterPropertiesSet方法
  @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(this);//可以通过this来得到当前实例对象
        System.out.println("初始化");
    }
}
//方式二:在对应实体类中编写方法,添加注解@PostConstruct即表示该方法为初始化方法
@Component
public class UserService implements InitializingBean{
    @PostConstruct
    public void test(){
        System.out.println("初始化...");
    }
}


注意:若是同时使用这两种方式,使用@PostConstruct注解的会优先执行。并且初始前的方法返回的bean若是为null,@PostConstruct注解声明的方法不会执行,而是执行后面的afterPropertiesSet方法及之后!



2.6、初始化后


2.6.1、基本使用


同样我们通过实现BeanPostProcessor接口或继承BeanPostProcessor的相关接口来进行方法重写即可实现初始化后方法:


@Component
public class ChushihuaBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("userService".equals(beanName)){
            System.out.println("初始化后方法");
        }
        return bean;
    }
}


其他相关类:


@Component
public class UserService implements InitializingBean{
    private User user;
    //实例化
    public UserService() {
        System.out.println("实例化");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(this);//可以通过this来得到当前实例对象
        System.out.println("初始化");
    }
}




2.6.2、初始化后来进行AOP


Spring中自带的实现AOP的类



你可以看到该抽象类实现了InstantiationAwareBeanPostProcessor接口,其内部实际上就是通过重写初始化后方法来实现的AOP!

一般流程:UserService->UserService对象->AOP->代理对象,也就是在实例好对象之后来进行的AOP生成代理对象并返回。


在Spring本身的逻辑里面,会看你这个类有没有实现自己所定义的接口,若是你实现了就会用JDK的动态代理;若是没有实现自己所定义的接口,那么就会使用CGLIB动态代理。


默认情况下,Spring会基于你的类来生成一个对象然后来进行AOP,产生一个代理对象



手写后置处理器来实现AOP


手动实现AOP功能:


public interface MyInterface {
    void handle();
}
@Component
public class UserService MyInterface {
    @Override
    public void handle() {
        System.out.println("AOP=>handle业务主方法");
    }
}
//自定义后置处理器
@Component
public class ChushihuaBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    //初始化后
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("userService".equals(beanName)) {
            System.out.println("实例化后");
            //进行AOP,返回的是代理对象
            return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("AOP前置方法");
                    //核心主方法
                    method.invoke(bean,args);
                    System.out.println("AOP后置方法");
                    return null;
                }
            });
        }
        return bean;
    }
}



测试方法:


@ComponentScan("xyz.changlu")
public class Config {
}
public class Main{
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
        //由于后置处理器最终返回的是Proxy代理对象,所以这里第二个参数就传入对应动态代理的接口
        MyInterface userService = applicationContext.getBean("userService", MyInterface.class);
        //需要调用指定业务方法才能够模拟实现AOP操作
        userService.handle();
    }
}


相关文章
|
18天前
|
XML 安全 Java
|
1天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
15 6
|
3天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
28 3
|
1月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
16天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
27 1
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
63 9
|
2月前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
41 9
|
29天前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
32 0
|
2月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
2月前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
26 1
下一篇
DataWorks