【吃透Java手写】1- Spring(上)-启动-扫描-依赖注入-初始化-后置处理器

【吃透Java手写】Spring(上)启动-扫描-依赖注入-初始化-后置处理器

  • 1 准备工作
    • 1.1 创建自己的Spring容器类
    • 1.2 创建自己的配置类 @ComponentScan
    • 1.3 @ComponentScan
      • 1.3.1 @Retention
      • 1.3.2 @Target
    • 1.4 用户类UserService @Component
    • 1.5 @Component
    • 1.6 测试类
  • 2 启动和扫描逻辑模拟实现
    • 2.1 解析配置类-获取扫描路径
    • 2.2 解析配置类-扫描
      • 2.2.1 通过包名来获取类
      • 2.2.2 判断是否为Bean对象
      • 2.2.3 单例/原型Bean实现
        • 2.2.3.1 单例池
        • 2.2.3.2 BeanDefinition池
        • 2.2.3.3 单例Bean实例化
        • 2.2.3.4 原型Bean实例化
        • 2.2.3.5 测试
  • 3 依赖注入模拟实现
    • 3.1 @Autowired
    • 3.2 为UserService引入OrderService依赖
    • 3.3 BeanNameAware接口
  • 4 初始化机制模拟实现
    • 4.1 InitializingBean接口
  • 5 BeanPostProcessor模拟实现
    • 5.1 BeanPostProcessor接口
    • 5.2 具体实现类
    • 5.3 Spring扫描时对BeanPostProcessor实现类处理
    • 5.4 BeanPostProcessor实现类初始换前后处理
    • 5.5 测试


1 准备工作

在这里插入图片描述

1.1 创建自己的Spring容器类

创建com.spring.ZhouyuApplicationContext

public class ZhouyuApplicationContext {
    private Class configClass;

    public ZhouyuApplicationContext(Class configClass) {
        this.configClass = configClass;
        //解析配置类
        //ComponentScan注解--->扫描路径--->扫描

    }

    public Object getBean(String beanName) {
        return null;
    }
}
  • public ZhouyuApplicationContext(Class configClass)用配置类来初始换容器
  • public Object getBean(String beanName)获取bean方法
  • 获取到配置类会通过解析配置类来完成对容器的初始化->//解析配置类,这里的解析,Spring只会解析Spring中提供的注解,对于自定义的注解是无法自动解析的

1.2 创建自己的配置类 @ComponentScan

创建com.zhouyu.AppConfig

import com.spring.ComponentScan;

@ComponentScan("com.zhouyu.service")
public class AppConfig {
}

配置类需要定义扫描路径,则需要创建@ComponentScan注解

@ComponentScan("com.zhouyu.service")定义扫描路径为"com.zhouyu.service"

1.3 @ComponentScan

@ComponentScan是Spring用于扫描路径的注解,所以在Spring目录下

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default "";
}

1.3.1 @Retention

  • @Retention(RetentionPolicy.RUNTIME)注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

    1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;

    2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;

    3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

1.3.2 @Target

  • @Target(ElementType.TYPE)可以用于类、接口和枚举类型。

@Target注解用于指定注解可以应用的程序元素类型,它有一个ElementType枚举类型的参数,可以取值为:

ElementType.TYPE:可以用于类、接口和枚举类型。

ElementType.FIELD:可以用于字段(包括枚举常量)。

ElementType.METHOD:可以用于方法。

ElementType.PARAMETER:可以用于方法的参数。

ElementType.CONSTRUCTOR:可以用于构造函数。

ElementType.LOCAL_VARIABLE:可以用于局部变量。

ElementType.ANNOTATION_TYPE:可以用于注解类型。

ElementType.PACKAGE:可以用于包。

ElementType.TYPE_PARAMETER:可以用于类型参数声明(Java 8新增)。

ElementType.TYPE_USE:可以用于使用类型的任何语句中(Java 8新增)。

  • String value() default ""定义扫描路径,默认值为空

1.4 用户类UserService @Component

创建com.zhouyu.service.UserService

@Component("userService")
public class UserService {
}

@Component("userService")定义Bean,value的值表示Bean的名字,如果不写的话Spring会解析你的类名,然后小写首字母当作Bean名

1.5 @Component

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default "";
}

规则与@ComponentScan类似

1.6 测试类

创建com.zhouyu.Test

public class Test {
    public static void main(String[] args) {
        ZhouyuApplicationContext context = new ZhouyuApplicationContext(AppConfig.class);
        Object userService = context.getBean("userService");
    }
}

2 启动和扫描逻辑模拟实现

2.1 解析配置类-获取扫描路径

在com.spring.ZhouyuApplicationContext中

public class ZhouyuApplicationContext {
    private Class configClass;

    public ZhouyuApplicationContext(Class configClass) {
        this.configClass = configClass;
        //解析配置类
        ComponentScan componentScanAnnotations = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
        //扫描路径
        String path= componentScanAnnotations.value();
        System.out.println("扫描路径:"+path);
    }

    public Object getBean(String beanName) {
        return null;
    }
}
  • configClass.getDeclaredAnnotation拿到注解,通过.value拿到注解的值

运行得到包名

扫描路径:com.zhouyu.service

2.2 解析配置类-扫描

当com.zhouyu.service下有很多类时,有些类是我们Spring不关心的类,我们Spring关心的是加了@Component注解的类,这样就把这些作为Bean然后放在容器中

2.2.1 通过包名来获取类

这里需要用到类加载器

  • 引导类加载器 Bootstrap,加载属于JVM的一部分,由C++代码实现,负责加载<JAVA_HOME\>\jre\lib路径下的核心类库,由于安全考虑只加载 包名 java、javax、sun开头的类。
  • 扩展类加载器 ExtClassLoader,扩展类加载器的父加载器是Bootstrap启动类加载器 (注:不是继承关系)。扩展类加载器负责加载<JAVA_HOME>\jre\lib\ext目录下的类库。
  • 系统类加载器 AppClassLoader,系统类加载器的父加载器是ExtClassLoader扩展类加载器(注: 不是继承关系)。系统类加载器负责加载 classpath环境变量所指定的类库,是用户自定义类的默认类加载器。
D:\Software\software_with_code\idea\jdk\jdk-17\bin\java.exe "-javaagent:D:\Software\software_with_code\idea\software\IntelliJ IDEA 2023.2\lib\idea_rt.jar=47035:D:\Software\software_with_code\idea\software\IntelliJ IDEA 2023.2\bin" -Dfile.encoding=UTF-8 -classpath D:\Code\JavaCode\mySpring\mySpring\target\classes com.zhouyu.Test
扫描路径:com.zhouyu.service

刚刚获取扫描路径时最后指定的classpath,说明运行的是系统类加载器AppClassLoader。classpath的路径是D:\Code\JavaCode\mySpring\mySpring\target\classes,所以获取类加载的相对路径就是这个。

在这里插入图片描述

public ZhouyuApplicationContext(Class configClass) {
    this.configClass = configClass;
    //解析配置类
    ComponentScan componentScanAnnotations = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
    //ComponentScan注解--->扫描路径--->扫描
    //扫描路径
    String path= componentScanAnnotations.value();
    System.out.println("扫描路径:"+path);
    //获取类加载器
    ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
    URL resource =classLoader.getResource("com/zhouyu/service");
    File file=new File(resource.getFile());
    if(file.isDirectory()){
        File[] files=file.listFiles();
        for(File f:files){
            System.out.println(f);
        }
    }
}

输出

扫描路径:com.zhouyu.service
D:\Code\JavaCode\mySpring\mySpring\target\classes\com\zhouyu\service\UserService.class
D:\Code\JavaCode\mySpring\mySpring\target\classes\com\zhouyu\service\XxUtil.class

这样获取了类,也就能获取类上的注解了

public ZhouyuApplicationContext(Class configClass) {
    this.configClass = configClass;
    //解析配置类
    ComponentScan componentScanAnnotations = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
    //ComponentScan注解--->扫描路径--->扫描
    //扫描路径
    String path= componentScanAnnotations.value();
    System.out.println("扫描路径:"+path);
    //获取类加载器
    ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
    URL resource =classLoader.getResource("com/zhouyu/service");
    File file=new File(resource.getFile());
    if(file.isDirectory()){
        File[] files=file.listFiles();
        for(File f:files){
            System.out.println(f);
            Class<?> aClass = classLoader.loadClass("xxx");
            if(aClass.isAnnotationPresent(Component.class)){
                //..
            }
        }
    }
}

但是我们获取到的类是编译好的

D:\Code\JavaCode\mySpring\mySpring\target\classes\com\zhouyu\service\UserService.class
D:\Code\JavaCode\mySpring\mySpring\target\classes\com\zhouyu\service\XxUtil.class

怎么才能加载原先的com.zhouyu.service.UserService这样的路径呢?这样就要将D:\Code\JavaCode\mySpring\mySpring\target\classes\com\zhouyu\service\UserService.class``转换为com.zhouyu.service.UserService,掐头去尾,把\换成.

String fileName=f.getAbsolutePath();
if(fileName.endsWith(".class")){
    String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
    className = className.replace("\\", ".");

ZhouyuApplicationContext全部代码

  • 先获取扫描路径(com.zhouyu.service)—>
  • 转换为相对路径(com/zhouyu/service)—>
  • 通过相对路径获取类加载器获取编译好的类—>
  • 获取编译好的类的路径名(D:\Code\JavaCode\mySpring\mySpring\target\classes\com\zhouyu\service\UserService.class)—>
  • 转换为可获取的类名(com.zhouyu.service.UserService)—>
  • 判断是否有注解Component来决定是不是Bean对象
public ZhouyuApplicationContext(Class configClass) {
    this.configClass = configClass;
    //解析配置类
    ComponentScan componentScanAnnotations = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
    //ComponentScan注解--->扫描路径--->扫描
    //扫描路径
    String path= componentScanAnnotations.value();
    path = path.replace(".", "/");
    //System.out.println("扫描路径:"+path);
    //获取类加载器
    ClassLoader classLoader = ZhouyuApplicationContext.class.getClassLoader();
    URL resource =classLoader.getResource(path);
    File file=new File(resource.getFile());
    if(file.isDirectory()){
        File[] files=file.listFiles();
        for(File f:files){
            //System.out.println(f.getAbsolutePath());
            String fileName=f.getAbsolutePath();
            if(fileName.endsWith(".class")){
                String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                className = className.replace("\\", ".");
                try {
                    Class<?> aClass = classLoader.loadClass(className);
                    if(aClass.isAnnotationPresent(Component.class)){
                        //有Component注解表示这个类是一个Bean需要被Spring管理
                    }
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

2.2.2 判断是否为Bean对象

在Spring中的Bean对象也分为单例Bean和原型Bean

@Component("userService")
public class UserService {
}

如果没有指定@Scope,则默认为单例Bean

@Scope取值:

  • singleton:此取值时表明容器中创建时只存在一个实例,所有引用此bean都是单一实例。
  • prototype:每次获取一个不同的Bean对象

如果加上@Scope("prototype")则代表是原型Bean

@Component("userService")
@Scope("prototype")
public class UserService {
}

2.2.3 单例/原型Bean实现

2.2.3.1 单例池

底层有一个map对象,map<beanName,bean对象>,如果获取的相同的beanName,则通过单例池来获取相同的Bean对象

在com.spring.ZhouyuApplicationContext中实现单例池

//单例池
private ConcurrentHashMap<String,Object>singletonObjects=new ConcurrentHashMap<String,Object>();
  • HashMap
    HashMap是线程不安全的,因为HashMap中操作都没有加锁,因此在多线程环境下会导致数据覆盖之类的问题,所以,在多线程中使用HashMap是会抛出异常的。

  • HashTable
    HashTable是线程安全的,但是HashTable只是单纯的在put()方法上加上synchronized。保证插入时阻塞其他线程的插入操作。虽然安全,但因为设计简单,所以性能低下。

  • ConcurrentHashMap
    ConcurrentHashMap是线程安全的,ConcurrentHashMap并非锁住整个方法,而是通过原子操作和局部加锁的方法保证了多线程的线程安全,且尽可能减少了性能损耗。

在获取Component注解后,判断是否为单例Bean,如果是,就需要生成Bean对象然后放入单例池中。

2.2.3.2 BeanDefinition池

在Spring中如果要getBean,拿到beanName则需要每次都要解析Bean对象的注解,这样相当麻烦,因此在Spring中,统一为解析Bean时生成BeanDefinition

生成com.spring.BeanDefinition

public class BeanDefinition {
    //名字
    private Class clazz;
    //作用域
    private String scope;
    public Class getClazz() {
        return clazz;
    }
    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }
    public String getScope() {
        return scope;
    }
    public void setScope(String scope) {
        this.scope = scope;
    }
}

扫描Bean时如果有Component注解就要创建BeanDefinition对象,再根据是否有Scope填充相应BeanDefinition对象的属性

if(aClass.isAnnotationPresent(Component.class)){
    //有Component注解表示这个类是一个Bean需要被Spring管理
    ComponentScan componentAnnotations = aClass.getDeclaredAnnotation(ComponentScan.class);
    String beanName = componentAnnotations.value();
    BeanDefinition beanDefinition = new BeanDefinition();
    beanDefinition.setClazz(aClass);
    if(aClass.isAnnotationPresent(Scope.class)){
        //原型Bean
        Scope aClassDeclaredAnnotation = aClass.getDeclaredAnnotation(Scope.class);
        beanDefinition.setScope(aClassDeclaredAnnotation.value());
    }else{
        //单例Bean
        beanDefinition.setScope("singleton");
    }
    beanDefinitionMap.put(beanName,beanDefinition);


}
2.2.3.3 单例Bean实例化

Spring启动时就要完成单例Bean实例化

在完成扫描后,要对单例池中的实例进行实例化,将以前的方法抽成一个方法scan

public ZhouyuApplicationContext(Class configClass) {
        this.configClass = configClass;
        //解析配置类
        scan(configClass);
        //针对单例池中的Bean要在Spring启动的时候进行实例化
        for(Map.Entry<String,BeanDefinition> entry:beanDefinitionMap.entrySet()){
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if(beanDefinition.getScope().equals("singleton")){
                //创建Bean
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }
    }
2.2.3.4 原型Bean实例化

在获取bean方法时,获取对应的BeanDefinition,如果是单例bean,则从单例池直接返回,如果不是则需要创建bean

public Object getBean(String beanName) {
    if (beanDefinitionMap.containsKey(beanName)) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")) {
            //单例Bean
            Object o = singletonObjects.get(beanName);
            return o;
        } else {
            //原型Bean
            return createBean(beanDefinition);
        }
    } else {
        throw new RuntimeException("Not found [" + beanName + "] BeanDefinition");
    }
}
private Object createBean(BeanDefinition beanDefinition) {
    Class clazz = beanDefinition.getClazz();
    try {
        Object o = clazz.getDeclaredConstructor().newInstance();
        return o;
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}

通过beanDefinition.getClazz().getDeclaredConstructor().newInstance()获取实例。

2.2.3.5 测试
public class Test {
    public static void main(String[] args) {
        ZhouyuApplicationContext context = new ZhouyuApplicationContext(AppConfig.class);
        Object userService = context.getBean("userService");
        Object userService1 = context.getBean("userService");
        Object userService2 = context.getBean("userService");
        System.out.println(userService);
        System.out.println(userService1);
        System.out.println(userService2);
    }
}

userService是一个原型Bean,可以看出每回获取的Bean对象地址都不同

com.zhouyu.service.UserService@2e5d6d97
com.zhouyu.service.UserService@238e0d81
com.zhouyu.service.UserService@31221be2

如果去掉@Scope("prototype")

@Component("userService")
//@Scope("prototype")
public class UserService {
}

输出

com.zhouyu.service.UserService@7daf6ecc
com.zhouyu.service.UserService@7daf6ecc
com.zhouyu.service.UserService@7daf6ecc

则全是单例bean

3 依赖注入模拟实现

3.1 @Autowired

创建com.spring.Autowired

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Autowired {
}

3.2 为UserService引入OrderService依赖

创建com.zhouyu.service.OrderService

@Component("orderService")
public class OrderService {
}

引入依赖

@Component("userService")
//@Scope("prototype")
public class UserService {
    @Autowired
    private OrderService orderService;

    public void test() {
        System.out.println(orderService);
    }
}

这样引入test方法

public class Test {
    public static void main(String[] args) {
        ZhouyuApplicationContext context = new ZhouyuApplicationContext(AppConfig.class);
        UserService userService = (UserService)context.getBean("userService");
        userService.test();
    }
}

在main中调用userService.test()则为空,因为依赖注入后并没有任何操作

null

一般来说我们依赖注入后,Spring就应该为我们赋好值了。

因此我们需要在创建Bean是准备好这一切。在com.spring.ZhouyuApplicationContext#createBean中

Class clazz = beanDefinition.getClazz();
try {
    Object o = clazz.getDeclaredConstructor().newInstance();
    //依赖注入
    for (Field field : clazz.getDeclaredFields()) {
        if (field.isAnnotationPresent(Autowired.class)) {
            Object bean = getBean(field.getName());
            if(bean==null) {
                throw new RuntimeException("Not found [" + field.getName() + "] BeanDefinition");
            }
            field.setAccessible(true);
            field.set(o, bean);
        }
    }
    return o;
}
  • field.setAccessible(true):设置了setAccessible为true之后,就能编辑final变量以及访问private变量。

在这里插入图片描述

重新测试userService.test(),得到

com.zhouyu.service.OrderService@433c675d

3.3 BeanNameAware接口

BeanNameAware 主要用于获取在 Spring 容器中配置的 Bean 名称,使得 Bean 能够获取自身在容器中的标识。

创建com.spring.BeanNameAware

public interface BeanNameAware {
    void setBeanName(String name);
}

就可以在UserService中实现这个接口,在创建Bean时就可以添加beanName

@Component("userService")
//@Scope("prototype")
public class UserService implements BeanNameAware {
    @Autowired
    private OrderService orderService;
    private String beanName;
    
    @Override
    public void setBeanName(String name) {
        beanName = name;
    }
    public void test() {
        System.out.println(orderService);
        System.out.println("userService beanName: " + beanName);
    }
}

在com.spring.ZhouyuApplicationContext#createBean中

private Object createBean(String beanName,BeanDefinition beanDefinition) {
    Class clazz = beanDefinition.getClazz();
    try {
        Object o = clazz.getDeclaredConstructor().newInstance();
        //依赖注入
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                Object bean = getBean(field.getName());
                if(bean==null) {
                    throw new RuntimeException("Not found [" + field.getName() + "] BeanDefinition");
                }
                field.setAccessible(true);
                field.set(o, bean);
            }
        }
        if(o instanceof BeanNameAware){
            ((BeanNameAware) o).setBeanName(beanName);
        }
        return o;
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}
  • o instanceof BeanNameAware判断是否实现BeanNameAware,如果实现了就把Object类转为其实现的BeanNameAware的接口类,并调用其setBeanName完成赋值。

4 初始化机制模拟实现

4.1 InitializingBean接口

InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

创建com.spring.InitializingBean

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

UserService实现InitializingBean接口

@Component("userService")
//@Scope("prototype")
public class UserService implements InitializingBean {
    @Autowired
    private OrderService orderService;
        @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化方法");
    }
	//InitializingBean模拟实现
    public void test() {
        System.out.println(orderService);
        //System.out.println("userService beanName: " + beanName);
    }
}

在其创建bean时,对是否实现InitializingBean接口进行判断,在com.spring.ZhouyuApplicationContext#createBean中

//初始化方法
if(o instanceof InitializingBean){
    ((InitializingBean) o).afterPropertiesSet();
}
private Object createBean(String beanName,BeanDefinition beanDefinition) {
    Class clazz = beanDefinition.getClazz();
    try {
        Object o = clazz.getDeclaredConstructor().newInstance();
        //依赖注入
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                Object bean = getBean(field.getName());
                if(bean==null) {
                    throw new RuntimeException("Not found [" + field.getName() + "] BeanDefinition");
                }
                field.setAccessible(true);
                field.set(o, bean);
            }
        }
        //Aware回调
        if(o instanceof BeanNameAware){
            ((BeanNameAware) o).setBeanName(beanName);
        }
        //初始化方法
        if(o instanceof InitializingBean){
            ((InitializingBean) o).afterPropertiesSet();
        }

        return o;
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

5 BeanPostProcessor模拟实现

该接口我们也叫后置处理器,作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。

注意是Bean实例化完毕后及依赖注入完成后触发的。

5.1 BeanPostProcessor接口

创建com.spring.BeanPostProcessor接口

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception;

    Object postProcessAfterInitialization(Object bean, String beanName) throws Exception;
}

5.2 具体实现类

创建com.zhouyu.service.ZhouyuBeanPostProcessor实现BeanPostProcessor接口,并且用@Component交给Spring管理

@Component("zhouyuBeanPostProcessor")
public class ZhouyuBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
        System.out.println(beanName+"初始化方法之前");
        if(beanName.equals("userService")) {
            System.out.println("userService初始化方法之前");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
        System.out.println(beanName+"初始化方法之后");
        if(beanName.equals("userService")) {
            System.out.println("userService初始化方法之后");
        }
        return bean;
    }
}

5.3 Spring扫描时对BeanPostProcessor实现类处理

因为ZhouyuBeanPostProcessor对实例方法前后都进行处理,所以需要在扫描时对实现BeanPostProcessor的类进行处理

要保证BeanPostProcessor接口的实现类率先被实例化,扫描顺序无所谓。

在com.spring.ZhouyuApplicationContext中用一个List来存放BeanPostProcessor接口的实现类

private List<BeanPostProcessor> beanPostProcessorsList=new ArrayList<>();

在com.spring.ZhouyuApplicationContext#scan中,对于判断这个类是否实现BeanPostProcessor接口,不能使用instanceof BeanPostProcessor因为该实现类还未被实例化。

所以需要使用BeanPostProcessor.class.isAssignableFrom(aClass)判断是否实现BeanPostProcessor接口

再调用aClass.getDeclaredConstructor().newInstance()进行实例化再放入List中

try {
    Class<?> aClass = classLoader.loadClass(className);
    if(aClass.isAnnotationPresent(Component.class)){
        //判断是否实现了BeanPostProcessor接口
        if(BeanPostProcessor.class.isAssignableFrom(aClass)){
            BeanPostProcessor instance = (BeanPostProcessor) aClass.getDeclaredConstructor().newInstance();
            beanPostProcessorsList.add(instance);
        }

        //有Component注解表示这个类是一个Bean需要被Spring管理
        Component componentAnnotations = aClass.getDeclaredAnnotation(Component.class);
        String beanName = componentAnnotations.value();
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setClazz(aClass);
        if(aClass.isAnnotationPresent(Scope.class)){
            //原型Bean
            Scope aClassDeclaredAnnotation = aClass.getDeclaredAnnotation(Scope.class);
            beanDefinition.setScope(aClassDeclaredAnnotation.value());
        }else{
            //单例Bean
            beanDefinition.setScope("singleton");
        }
        beanDefinitionMap.put(beanName,beanDefinition);
    }
}

这里在扫描到BeanPostProcessor接口创建了一次实例,在扫描Component注解又单例实例化了一次,有些不严谨。

Spring源码是使用createBean进行创建原型的,这也就可以在实现类中支持一些@Autowired的其他操作。这里只提供一种解决方法。

5.4 BeanPostProcessor实现类初始换前后处理

因为BeanPostProcessor是对初始化前后进行操作的,所以需要在对其他Bean对象创建时的初始化方法时进行处理

在com.spring.ZhouyuApplicationContext#createBean中

//BeanPostProcessor初始化方法之前
for(BeanPostProcessor beanPostProcessor:beanPostProcessorsList){
    o = beanPostProcessor.postProcessBeforeInitialization(o,beanName);
}
//初始化方法
if(o instanceof InitializingBean){
    ((InitializingBean) o).afterPropertiesSet();
}
//BeanPostProcessor初始化方法之后
for(BeanPostProcessor beanPostProcessor:beanPostProcessorsList){
    o = beanPostProcessor.postProcessAfterInitialization(o,beanName);
}

5.5 测试

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/605004.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI实景自动无人直播软件:引领直播行业智能化革命;提升直播效果,无人直播软件助力智能讲解

随着科技的快速发展&#xff0c;AI实景自动无人直播软件正在引领直播行业迈向智能化革命。它通过智能讲解、一键开播和智能回复等功能&#xff0c;为商家提供了更高效、便捷的直播体验。此外&#xff0c;软件还支持手机拍摄真实场景或搭建虚拟场景&#xff0c;使直播画面更好看…

Unity 性能优化之动态批处理(四)

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、动态合批是什么&#xff1f;二、使用动态批处理1.打开动态合批2.满足条件 三、检查动态合批是否成功五、动态合批弊端总结 前言 动态批处理是常用优…

Flutter笔记:手动配置VSCode中Dart代码自动格式化

Flutter笔记 手动配置VSCode中Dart代码自动格式化 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csd…

pcm转MP3怎么转?只需3个步骤~

PCM&#xff08;Pulse Code Modulation&#xff09;是一种用于数字音频编码的基础技术&#xff0c;最早起源于模拟音频信号数字化的需求。通过PCM&#xff0c;模拟音频信号可以被精确地转换为数字形式&#xff0c;为数字音频的发展奠定了基础。 MP3文件格式的多个优点 MP3的优…

【深度学习】网络安全,SQL注入识别,SQL注入检测,基于深度学习的sql注入语句识别,数据集,代码

文章目录 一、 什么是sql注入二、 sql注入的例子三、 深度学习模型3.1. SQL注入识别任务3.2. 使用全连接神经网络来做分类3.3. 使用bert来做sql语句分类 四、 深度学习模型的算法推理和部署五、代码获取 一、 什么是sql注入 SQL注入是一种常见的网络安全漏洞&#xff0c;它允许…

模糊的图片文字,OCR能否正确识别?

拍照手抖、光线不足等复杂的环境下形成的图片都有可能会造成文字模糊&#xff0c;那这些图片文字对于OCR软件来说&#xff0c;是否能否准确识别呢&#xff1f; 这其中的奥秘&#xff0c;与文字的模糊程度紧密相连。想象一下&#xff0c;如果那些文字对于我们的双眼来说&#x…

sed小实践2(随手记)

删除/etc/passwd的第一个字符 #本质是利用sg替换&#xff0c;将第一个字符替换成空 sed s|^.||g /etc/passwd删除/etc/passwd的第二个字符 sed -r s|^(.).(.*$)|\1\2|g /etc/passwd sed -r s|^(.).|\1|g /etc/passwd删除/etc/passwd的最后一个字符 sed s|.$||g /etc/passwd删…

Java快速入门系列-11(项目实战与最佳实践)

第十一章&#xff1a;项目实战与最佳实践 11.1 项目规划与需求分析项目规划需求分析实例代码 11.2 系统设计考虑实例代码 11.3 代码实现与重构实例代码 11.4 性能优化与监控实例代码 11.5 部署与持续集成/持续部署(CI/CD)实例代码 11.1 项目规划与需求分析 在进行任何软件开发…

基于Vumat的修正JC本构模型的切削研究

JC渐进损伤本构是研究切削中的重要本构模型&#xff0c;主要包括材料硬化和损伤两部分&#xff1a;其中&#xff0c;原始JC的硬化部分本构为&#xff1b; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 材料屈服应力的硬化解耦为三部分独立的效应&#x…

blender导出gltf模型混乱

最近用户给了几个blender文件&#xff0c;在blender打开是这样的&#xff1a; 我导出成gltf候&#xff0c;在本地打开时&#xff0c;底部发生了改变&#xff1a; 可以看出来&#xff0c;底部由原来的类型box变为了两个平面&#xff0c;后来我查了下blender里的属性设置&#xf…

文件IO-使用dup2实现错误日志功能及判断文件权限,并终端输出

1&#xff1a;使用 dup2 实现错误日志功能 使用 write 和 read 实现文件的拷贝功能&#xff0c;注意&#xff0c;代码中所有函数后面&#xff0c;紧跟perror输出错误信息&#xff0c;要求这些错误信息重定向到错误日志 err.txt 中去 代码&#xff1a; #incl…

后教培时代的新东方,正在找寻更大的教育驱动力?

近段时间&#xff0c;K12教育主要上市公司的阶段性业绩皆已出炉。从具体数据来看&#xff0c;随着时间推移&#xff0c;教培机构的转型之路已愈走愈顺。 财报显示&#xff0c;2023年12月1日-2024年2月29日&#xff0c;好未来实现营收4.3亿美元&#xff0c;同比增长59.7%&#…

GIS 中的空间模式

空间模式显示了地球上事物的相互联系方式。这些图案可以是天然的或人造的。当我们使用 GIS 时&#xff0c;我们可以看到事物的位置以及它们之间的关系。今天&#xff0c;让我们关注地理和 GIS 领域的空间模式。 点分布的类型 点分布是将特定位置映射为地图上的单个点的方式。这…

W801学习笔记二十四:NES模拟器游戏

之前已经实现了NES模拟器玩游戏。W801学习笔记九&#xff1a;HLK-W801制作学习机/NES游戏机(模拟器) 现在要在新版本掌机中移植过来。 1、把NES文件都拷贝到SD卡中。 这回不会受内存大小限制了。我这里拷贝了4个&#xff0c;还可以拷贝更多。 2、应用初始化中&#xff0c;加载…

【运维网络篇】史上最全的 网络知识 思维导图!

01 TCP/IP网络协议栈 02 TCP/IP协议层次划分 03 传输介质简介 04 以太网帧结构 05 IP编址 06 ICMP协议 07 ARP协议 08 传输层协议 09 路由基础 10 静态路由基础 11 距离矢量路由协议——RIP 12 链路状态路由协议——OSPF 13 HDLC&PPP原理与应用 14 帧中继…

创新指南|创新组合管理的7个陷阱以及如何避免它们

进入未知领域的第一步可能具有挑战性。尽管创新会犯错误&#xff0c;但在将 IPM 作为公司实践实施时&#xff0c;您可以准备好并避免一些常见的陷阱。在这篇文章中&#xff0c;我们将讨论组织在实施创新组合管理时遇到的最常见的陷阱。 01. 在映射中包含日常业务任务 映射中的…

【iOS】-- 内存五大分区

【iOS】-- 内存五大分区 内存五大分区1.栈区优点&#xff1a; 2.堆区优点&#xff1a; 3.全局区4.常量区5.代码区 验证static、extern、const关键字比较1.static关键字static关键字的作用&#xff1a;全局静态变量局部静态变量 2.extern关键字对内的全局变量对外的全局变量 3.c…

Intel® Platform Firmware Resilience (Intel® PFR):英特尔® 平台固件恢复力(Intel® PFR)

为了降低与固件相关的安全风险&#xff0c;英特尔为服务器平台开发了英特尔平台固件恢复力&#xff08;Intel PFR&#xff09;。 此功能可保护关键固件在启动和运行时免受攻击。这可以被视为是 Cerberus 项目或 NIST SP800-193 的实现。 英特尔平台固件恢复力&#xff08;Int…

抖音小店是什么?它和直播带货有什么区别和联系?一篇详解!

大家好&#xff0c;我是电商糖果 在网上大家都说抖音的流量大&#xff0c;在抖音做电商比较赚钱。 可是有很多人对抖音电商并不了解。 甚至搞不懂抖音小店是什么&#xff1f;它和直播带货的区别和联系也不清楚。 下面&#xff0c;糖果就来给大家好好解答一下这个问题。 抖音…

Linux(openEuler、CentOS8)企业内网samba服务器搭建(Windows与Linux文件共享方案)

本实验环境为openEuler系统<以server方式安装>&#xff08;CentOS8基本一致&#xff0c;可参考本文) 目录 知识点实验1. 安装samba2. 启动smb服务并设置开机启动3. 查看服务器监听状态4. 配置共享访问用户5. 创建共享文件夹6. 修改配置文件7. 配置防火墙8. 使用windows…