【吃透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)){//原型BeanScope aClassDeclaredAnnotation = aClass.getDeclaredAnnotation(Scope.class);beanDefinition.setScope(aClassDeclaredAnnotation.value());}else{//单例BeanbeanDefinition.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")){//创建BeanObject 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")) {//单例BeanObject o = singletonObjects.get(beanName);return o;} else {//原型Beanreturn 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 {@Autowiredprivate 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 {@Autowiredprivate OrderService orderService;private String beanName;@Overridepublic 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 {@Autowiredprivate OrderService orderService;@Overridepublic 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 {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {System.out.println(beanName+"初始化方法之前");if(beanName.equals("userService")) {System.out.println("userService初始化方法之前");}return bean;}@Overridepublic 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)){//原型BeanScope aClassDeclaredAnnotation = aClass.getDeclaredAnnotation(Scope.class);beanDefinition.setScope(aClassDeclaredAnnotation.value());}else{//单例BeanbeanDefinition.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 测试

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我独自升级崛起怎么下载 游戏下载教程分享

《我独自升级&#xff1a;崛起》这款游戏核心聚焦于激烈的战斗与角色的持续成长。新加入的玩家首要任务是熟悉基础攻击模式&#xff0c;随后深入探索技能组合策略与连贯招式的艺术&#xff0c;同时掌握防守与躲避技巧&#xff0c;这些都是战斗中不可或缺的关键。随着战斗的持续…

重写muduo之Thread、EventLoopThread、EventLoopThreadPool

目录 1、概述 2、Thread 2.1 Thread.h 3、EventLoopThread 3.1 EventLoopThread.h 3.2 EventLoopThread.cc 4、 EventLoopThreadPool 4.1 EventLoopThreadPool.h 4.2 EventLoopThreadPool.cc 1、概述 管理事件循环线程的调度的 打包了一个EventLoop和线程&#xff0c;…

项目经理【过程】原则

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 【环境】原则 【环境】任务 【环境】绩效 【人】概述 【人】原则 【人】任务 【人】绩效 【过程】概念 【过程】原则 一、质量管理水平、质量管理的发展 1.1 质量管理水平 1.2 质量管理的发展 …

NAS选购全方位解析,性价比才是硬道理 | 2024年618威联通NAS选购攻略

NAS选购全方位解析&#xff0c;性价比才是硬道理 | 2024年618威联通NAS选购攻略 哈喽小伙伴们好&#xff0c;我是Stark-C~&#xff0c;临近618&#xff0c;今天和大家谈谈NAS的选购问题。 关注我的小伙伴都知道&#xff0c;经过我手头折腾的NAS设备非常多&#xff0c;除了群晖…

如果出现一个工具,可以让前端开发彻底不用再手写UI,这个工具意义大吗?干货!

求这样的一个工具&#xff0c;可以让后端开发、嵌入式开发、产品经理、UI设计师都能用&#xff0c;注意&#xff0c;不是在一个简单的静态页生成&#xff0c;也不是类似飞冰那种 generator &#xff0c;而是真正让设计师和开发者在各自的那侧达成自治&#xff0c;可以做到吗&am…

;【排列【

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

如何使用Tushare+ Backtrader进行股票量化策略回测

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

ICode国际青少年编程竞赛- Python-2级训练场-列表入门

ICode国际青少年编程竞赛- Python-2级训练场-列表入门 1、 Dev.step(3)2、 Flyer.step(1) Dev.step(-2)3、 Flyer.step(1) Spaceship.step(7)4、 Flyer.step(5) Dev.turnRight() Dev.step(5) Dev.turnLeft() Dev.step(3) Dev.turnLeft() Dev.step(7) Dev.turnLeft() Dev.…

【数字经济】上市公司供应链数字化数据(2000-2022)

数据来源&#xff1a; 时间跨度&#xff1a;2000-2022年 数据范围&#xff1a;各上市企业 数据指标&#xff1a; 样例数据&#xff1a; 参考文献&#xff1a;[1]刘海建,胡化广,张树山,等.供应链数字化的绿色创新效应[J].财经研究,2023,49(03):4-18. 下载链接&#xff1a;https:…

Linux(openEuler、CentOS8)基于chrony企业内网NTP服务器搭建实验

一、知识点 chrony 是由 守护进程 chronyd 以及 命令行工具 chronyc 组成的 chronyd 在后台静默运行并通过 123 端口与时间服务器定时同步时间&#xff0c;默认的配置文件是 /etc/chrony.conf chronyc 通过 323 端口与 chronyd 交互&#xff0c;可监控 chronyd 的性能并在运…

基于springboot+vue+Mysql的口腔管理平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【3dmax笔记】026:挤出和壳修改器的使用

文章目录 一、修改器二、挤出三、壳 一、修改器 3ds Max中的修改器是一种强大的工具&#xff0c;用于创建和修改复杂的几何形状。这些修改器可以改变对象的形状、大小、方向和位置&#xff0c;以生成所需的效果。以下是一些常见的3ds Max修改器及其功能&#xff1a; 挤出修改…

Day22 代码随想录打卡|字符串篇---实现 strStr()

题目&#xff08;leecode T28&#xff09;&#xff1a; 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1…

第 8 章 电机测速(自学二刷笔记)

重要参考&#xff1a; 课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 讲义链接:Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 8.3.3 电机测速01_理论 测速实现是调速实现的前提&#xff0c;本节主要介绍AB相增量式编码器测速原理。 1.概…

可视化面板布局适配屏幕-基于 flexible.js + rem 智能大屏适配

可视化面板布局适配屏幕-基于 flexible.js rem 智能大屏适配 VScode 安装cssrem插件引入flexible.js在之后的开发都使用rem为单位&#xff0c;安装cssrem插件就是为了快捷将px转为rem我们的设计稿是1920px&#xff0c;设置最小宽度为1024px&#xff0c;最后&#xff0c;我们可…

县供电公司员工向媒体投稿发文章用亲身经历告诉你并不难

在县供电公司的日子里,我肩负着一项至关重要的使命——信息宣传工作。这不仅仅是一份职责,更是连接公司与外界的桥梁,通过新闻稿件传递我们的声音,展示我们的成果。然而,回忆起刚刚踏入这个领域的时光,那段经历至今让我感慨万千。 初涉投稿,步履维艰 刚接手这项工作时,我的投稿…

又发现一个ai生成音乐的网站-heymusic

网址 https://heymusic.ai/ 尴尬&#xff0c;不挂梯子能登录进来&#xff0c;但是谷歌账号注册不了&#xff0c;刷新了几遍也没注册上。 看了下价格&#xff0c;应该不是免费的&#xff0c;所以也没了试用的兴趣。 我也不想用别的邮箱注册了&#xff0c;所以只能简单的水一…

频谱分析:深入解析与全面介绍

频谱分析 一、引言 频谱分析&#xff0c;作为一种广泛应用于信号处理和波谱分析的方法&#xff0c;其在现代科技领域的重要性不言而喻。从基础的物理现象到复杂的通信系统&#xff0c;频谱分析都扮演着至关重要的角色。本文将对频谱分析进行深入的解析和全面的介绍&#xff0…

VxTerm使用教程:连接SSH服务端设备,什么是SSH

一、什么是SSH&#xff1f; <摘自百度> 安全外壳协议 SSH&#xff0c;即安全外壳协议&#xff08;Secure Shell&#xff09;&#xff0c;是一种网络协议&#xff0c;用于在计算机网络上提供安全的远程登录和命令执行功能。 SSH通过加密通信通道来保护数据传输&#xff0c…