手写实现简易Spring框架

该文章记录的是自己实现简易Spring框架的记录,简化了源码中的大量操作,没有实现三级缓存的机制。
其中将会对@Component@ComponentScan@Scope@Autowired注解。BeanDefinition类以及ApplicationContextBeanPostProcessorInitializingBean接口进行实现。
注: 该实现参考了“伟大的Yve菌”的手写一个简单的spring框架

文章目录

  • 一、预备知识
    • @Component
    • @ComponentScan
    • @Scope
    • @Autowired
    • BeanDefinition类
    • ApplicationContext接口
    • BeanPostProcessor接口
    • InitializingBean接口
  • 二、项目结构
  • 三、初始化项目
  • 四、扫描和加载逻辑
  • 五、实现`@Autowired`注解
  • 六、初始化以及前后的操作

一、预备知识


后续会对下列的注解、类、接口进行实现,在该部分对这些注解等进行介绍,之后再下个部分进行实现。

@Component

@Component注解是定义一个组件,表示这个类会被Spring自动扫描并创建实例化。它还衍生出了其他三个注解:@Controller用在表现层,@Service用在业务层,@Repository用在数据层。这些注解与@Component的作用和属性一样,只是提供了更加明确的语义化。
当我们需要将一个类注册为Spring容器中的Bean对象时,就可以在这个类上添加@Component注解。

@Component
public class MyService {// ...
}

@ComponentScan

@ComponentScan注解是定义扫描的路径,从中找出被@Component在这里插入代码片及衍生注解@Component@Service@Repository等注解的类,并将它们注册为Spring应用程序上下文中的Bean。该注解可以用在配置类上,作为配置扫描组件的指令。一般来说,@ComponentScan会扫描指定路径下的所有类,并将它们注册为Bean。
其中@Configuration注解的作用是用于标识一个类是配置类,通常用于替代XML配置文件。并且其中的@Bean注解方法将会被Spring容器处理和管理。

@Configuration
@ComponentScan(basePackages = "com.example.package")
public class AppConfig {// ...
}

@Scope

@Scope注解是用于定义Bean的作用域。它可以修饰在类级别,表示注入的Bean在整个应用中是单例(Singleton)还是多例(Prototype),从而影响Bean的生命周期和存储方式。默认情况下,所有被Spring管理的Bean都是单例的。

  1. singleton作用域,它是默认的作用域。当一个bean的作用域被设置为singleton时,在整个应用程序生命周期内,只会存在一个实例化的bean对象,所有请求该bean的对象都会共享同一个实例。也就是说,无论是在单线程环境还是多线程环境下,都只有一个bean对象存在。
  2. prototype作用域,它与singleton作用域有所不同。当一个bean的作用域被设置为prototype时,每次请求该bean的对象都会创建一个新的实例。也就是说,每次请求该bean,都会创建一个独立的新对象。

因为不同的业务场景需要不同的作用域来满足需求。

  • singleton作用域可以节省内存资源,适用于那些无状态(stateless)的bean,例如工具类、配置类等。
  • prototype作用域适用于那些有状态(stateful)的bean,每次请求都需要一个新的实例,例如用户请求、线程任务等。
@Component
@Scope("prototype")
public class MyBean {// ...
}

@Autowired

@Autowired是 Spring 框架中的一个注解,用于实现自动装配(Autowired)功能。它可以标注在类的成员变量、方法、构造函数或参数上,让 Spring 完成对相应依赖的自动注入。
@Autowired作用有以下几个方面:

  1. 自动装配依赖: 通过 @Autowired 注解,Spring 可以自动识别并注入相应的依赖对象,无需手动编写繁琐的依赖注入代码。这样可以简化开发过程,提高代码的可读性和可维护性。
  2. 解决依赖冲突: 当存在多个符合条件的依赖对象时,@Autowired 注解可以根据一定的规则(如类型匹配、限定符等)来解决依赖冲突,确保正确的依赖对象被注入。
  3. 支持多种注入方式: @Autowired 注解可以用于不同的注入方式,包括字段注入、方法注入、构造函数注入和参数注入。这样可以根据具体情况选择最合适的注入方式。

常见的使用方法:

  1. 字段注入:
@Autowiredprivate SomeDependency someDependency;
  1. 方法注入:
@Autowiredpublic void setSomeDependency(SomeDependency someDependency) {this.someDependency = someDependency;
}
  1. 构造函数注入:
@Autowiredpublic MyClass(SomeDependency someDependency) {this.someDependency = someDependency;
}
  1. 参数注入:
public void doSomething(@Autowired SomeDependency someDependency) {// 使用注入的依赖对象进行操作
}

BeanDefinition类

BeanDefinition 类是 Spring 框架中的一个关键类,用于描述和定义一个 bean 的元数据信息。它包含了创建和配置 bean 实例所需的所有信息,如类名、属性值、构造函数参数等,是 Spring 容器实现依赖注入和对象创建的基础。
BeanDefinition 接口定义了一些常用的方法和属性,用于获取和设置 bean 的相关信息。以下是一些主要的方法和属性:

  • getBeanClassName():获取 bean 的类名。
  • setBeanClassName(String beanClassName):设置 bean 的类名。
  • getScope():获取 bean 的作用域,如 singleton、prototype 等。
  • setScope(String scope):设置 bean 的作用域。
  • getPropertyValues():获取 bean 的属性值集合。
  • getPropertyValues().addPropertyValue(PropertyValue pv):向属性值集合中添加一个属性值。
  • getConstructorArgumentValues():获取 bean 的构造函数参数值集合。
  • getConstructorArgumentValues().addIndexedArgumentValue(int index, Object value):向构造函数参数值集合中添加一个索引参数值。
  • getConstructorArgumentValues().addGenericArgumentValue(Object value):向构造函数参数值集合中添加一个通用参数值。
  • getDependsOn():获取 bean 的依赖关系。
  • setDependsOn(String[] dependsOn):设置 bean 的依赖关系。
  • isSingleton():判断 bean 是否为单例。
  • isPrototype():判断 bean 是否为原型。
  • isAbstract():判断 bean 是否为抽象。
  • getRole():获取 bean 的角色。
  • setRole(int role):设置 bean 的角色。

除了上述方法和属性,BeanDefinition 还提供了其他一些方法,用于获取和设置 bean 的其他元数据信息,如初始化方法、销毁方法、是否懒加载等。
BeanDefinition 是一个接口,具体的实现类有多个,如 GenericBeanDefinitionRootBeanDefinitionChildBeanDefinition 等。每个实现类都有不同的特点和用途,用于满足不同场景下的需求。

ApplicationContext接口

ApplicationContext是 Spring 框架中的一个关键接口,它是 Spring 容器的核心接口之一。是 Spring 框架中负责管理和组织 bean 的创建、配置和生命周期的核心接口。它提供了丰富的功能和服务,可以方便地实现依赖注入、AOP、国际化等功能,是构建企业级应用程序的重要组成部分。
ApplicationContext 接口继承了 BeanFactory 接口,因此它具备了 BeanFactory 的所有功能,并在此基础上提供了更多的特性。以下是 ApplicationContext 的一些主要特点和功能:

  1. Bean 的生命周期管理:ApplicationContext 负责管理 bean 的生命周期,包括创建、初始化和销毁。它会根据配置信息自动创建和初始化 bean,并在容器关闭时销毁 bean。
  2. 依赖注入:ApplicationContext 支持依赖注入,可以自动将依赖的 bean 注入到目标 bean 中,无需手动编写繁琐的依赖注入代码。这样可以简化开发过程,提高代码的可读性和可维护性。
  3. AOP(面向切面编程)支持:ApplicationContext 提供了对 AOP 的支持,可以通过配置和使用切面来实现横切关注点的模块化。它可以方便地实现事务管理、日志记录、性能监控等功能。
  4. 国际化支持:ApplicationContext 提供了国际化支持,可以方便地实现多语言的应用程序。它可以根据不同的语言环境加载相应的资源文件,并提供统一的访问接口。
  5. 事件机制:ApplicationContext 支持事件机制,可以发布和监听事件。通过事件机制,不同的组件可以进行解耦,实现松耦合的设计。
  6. 配置文件的加载和解析:ApplicationContext 负责加载和解析配置文件,可以使用多种格式的配置文件,如 XML、注解、Java 配置等。它可以根据配置文件中的信息创建和配置相应的 bean。
  7. 容器的扩展性:ApplicationContext 提供了容器的扩展机制,可以通过自定义扩展点来扩展容器的功能。例如,可以自定义 BeanPostProcessorBeanFactoryPostProcessor 等来对 bean 进行定制化处理。
    ApplicationContext 接口有多个实现类,如 ClassPathXmlApplicationContextAnnotationConfigApplicationContextFileSystemXmlApplicationContext 等,每个实现类都有不同的特点和用途,用于满足不同场景下的需求。

BeanPostProcessor接口

BeanPostProcessor 是 Spring 框架中的一个重要接口,用于在 bean 的实例化和初始化过程中进行扩展和定制。它允许开发者在 bean 的创建过程中干涉并修改 bean 的行为。
BeanPostProcessor 接口定义了两个方法:

  • postProcessBeforeInitialization(Object bean, String beanName):在 bean 初始化之前调用。开发者可以在此方法中对 bean 进行修改或扩展操作。例如,可以对 bean 的属性进行修改、添加一些自定义的逻辑等。
  • postProcessAfterInitialization(Object bean, String beanName):在 bean 初始化之后调用。开发者可以在此方法中对 bean 进行进一步的处理。例如,可以对 bean 进行代理、添加一些额外的功能等。
    通过实现 BeanPostProcessor 接口,开发者可以在 Spring 容器实例化和初始化 bean 的过程中插入自己的逻辑,对 bean 进行个性化的定制。这为开发者提供了很大的灵活性和扩展性。
    BeanPostProcessor 的应用场景非常广泛,常见的用途包括:
  • 属性注入:可以在 postProcessBeforeInitialization 方法中对 bean 的属性进行修改或扩展,实现自定义的属性注入逻辑。
  • AOP(面向切面编程):可以在 postProcessAfterInitialization 方法中对 bean 进行代理,实现 AOP 的功能,如事务管理、日志记录等。
  • 自定义初始化逻辑:可以在 postProcessAfterInitialization 方法中添加一些自定义的初始化逻辑,如数据初始化、资源加载等。

InitializingBean接口

InitializingBean 是 Spring 框架中的一个接口,用于在 bean 的属性设置完成后执行自定义的初始化逻辑。它定义了一个方法 afterPropertiesSet(),在该方法中可以编写需要在 bean 初始化之后执行的代码。
当一个 bean 实现了 InitializingBean 接口并被 Spring 容器管理时,当所有的属性都被设置完成后,Spring 容器会自动调用该 bean 的 afterPropertiesSet() 方法。开发者可以在该方法中进行一些初始化操作,例如数据加载、资源初始化、校验等。
使用 InitializingBean 接口的优点是,它提供了一种标准化的方式来定义 bean 的初始化逻辑,使得初始化代码与 bean 的定义紧密结合,提高了代码的可读性和可维护性。此外,通过实现 InitializingBean 接口,可以确保在 bean 的属性设置完成后执行初始化逻辑,避免了手动调用初始化方法的繁琐操作。

二、项目结构


springframe包中放的是用来实现spring框架的包。
user为用户的包
在这里插入图片描述

三、初始化项目


首先我们去实现一个自己的ApplicationContext

  1. 创建一个MyApplicationContext类定义构造方法和getBean()方法,后续都需要进行完善。
package com.example.springframe;public class MyApplicationContext {private Class appConfig;public MyApplicationContext(Class appConfig) {this.appConfig = appConfig;}public Object getBean(String beanName) {return null;}
}
  1. 创建一个service包,里面包含UserServiceOrderService两个类。
package com.example.user.service;public class UserService {public void test() {System.out.println("userService");}
}package com.example.user.service;public class OrderService {public void test(){System.out.println("orderService");}
}
  1. 为user创建一个AppConfig配置类。
package com.example;public class AppConfig {
}
  1. 创建一个Test类运行测试程序来测试自己实现的spring
package com.example;import com.example.springframe.MyApplicationContext;
import com.example.user.service.UserService;public class Test {public static void main(String[] args) {MyApplicationContext context = new MyApplicationContext(AppConfig.class);System.out.println(context.getBean("userService"));System.out.println(context.getBean("orderService"));        }
}

注: 当前MyApplicationContext无法通过AppConfig获取到内容,因为没有添加注解说明扫描的路径,getBean()方法也是无法获取到userService的,因为MyApplicationContext 中的getBean()方法的返回值还是null。

  1. 现在去实现AppConfig中添加的路径扫描注解ComponentScan
package com.example.springframe;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}

其中:

  • @interface是用于声明注解的,注解是一种用于向代码中添加元数据的方式。注解是在源代码级别保留的,可以被编译器和其他工具处理。
  • @Target(ElementType.FIELD)指定了@Autowired注解可以应用于字段上。这意味着可以在类的字段上使用@Autowired注解来实现自动装配,让Spring框架自动将相应的依赖注入到字段中。
  • @Retention(RetentionPolicy.RUNTIME)指定了@Autowired注解在运行时保留。这意味着在程序运行时,可以通过反射机制获取到被@Autowired注解标记的字段,并进行相应的处理。
  • String value() default "";: 这一行代码定义了一个注解元素,名为 value,并指定其类型为 String。注解元素允许使用者在注解中指定值。在这里,value@Component 注解的一个属性。通过 default 关键字,可以为这个属性设置默认值为空字符串 "",表示当使用者不显式指定 value 属性值时,它将默认为一个空字符串。
  1. AppConfig类上添加咱们自己写的@ComponentScan注解,配置扫描路径,扫描到包,不要到具体类
package com.example;import com.example.springframe.ComponentScan;@ComponentScan("com.example.user.service")
public class AppConfig {
}
  1. 再配置个@Component注解来让Spring进行扫描。
package com.example.springframe;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {String value() default "";
}
  1. 在之前创建好的UserSerivce添加@Component注解,之后@Component进行扫描的时候就会将带有@Component注解的类注册为Bean。OrderService不添加注解,与UserService进行对比。
@Component
public class UserService {

现在spring已经可以扫描到包了,下面我们来写相关扫描逻辑和处理方法

四、扫描和加载逻辑


在完成了上个部分的创建Bean的过程之后,在这个部分来编写扫描和加载的逻辑。

  1. MyApplicationContext中写一个scan()方法来处理扫描操作。将扫描路径下带有@Component的类进行存放。
private void scan(Class appConfig){// 首先判断是否有扫描路径的注解if(appConfig.isAnnotationPresent(ComponentScan.class)){// 取出注解中的路径ComponentScan componentScan = (ComponentScan) appConfig.getAnnotation(ComponentScan.class);String path = componentScan.value();// 找到文件位置,修改为路径path = path.replace(".","/");// 需要解析的是编译之后的class类上的注解ClassLoader classLoader = MyApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);// 获取对应路径下的所有文件assert resource != null;File file = new File(resource.getFile());// 判断文件是否为目录if (file.isDirectory()){for (File listFile: file.listFiles()){// 获取到每个文件的绝对路径String absolutePath = listFile.getAbsolutePath();absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class")).replace("\\",".");try {// 判断每个文件是否有@Component注解Class<?> clazz = classLoader.loadClass(absolutePath);if (clazz.isAnnotationPresent(Component.class)){System.out.println(clazz);              }}catch (ClassNotFoundException e){e.printStackTrace();} }}}
}

scan()方法在MyApplicationContext构造函数中进行调用。

public MyApplicationContext(Class appConfig) {this.appConfig = appConfig;scan(appConfig);
}

现在运行Test测试文件就会发现,只有UserService,这是因为只有UserService加了@Component注解所以被扫描到了,下面的两个打印都是null是因为getBean()方法没有被实现,返回值还是null
在这里插入图片描述

  1. 实现@Scope类,用来判断扫描到的Bean的作用域。作用域分为两种,singleton和prototype,在前文的预备知识中有详细介绍。
package com.example.springframe;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {String value() default "";
}

UserServiceOrderService加入@Scope注解

package com.example.user.service;import com.example.springframe.Component;
import com.example.springframe.Scope;@Component("userService")
@Scope("prototype")
public class UserService{public void test(){System.out.println("userService");}
}
package com.example.user.service;import com.example.springframe.Component;
import com.example.springframe.Scope;@Component("orderService")
@Scope("singleton")
public class OrderService {public void test(){System.out.println("orderService");}
}
  1. 创建BeanDefinition类,对Bean的内部属性进行定义。在这里我使用了两个属性,一个类,一个作用域。
package com.example.springframe;public class BeanDefinition {private Class type;private String scope;public Class getType() {return type;}public void setType(Class type) {this.type = type;}public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}
}
  1. MyApplicationContext中定义一个map来存放这些Bean,beanNamemapkeyBeanDefinitionvalue
public class MyApplicationContext {private Class appConfig;// 用map存储beanprivate Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
  1. 完善scan()方法,扫描过后将信息存放到beanDefinitionMap
private void scan(Class appConfig){// 首先判断是否有扫描路径的注解if(appConfig.isAnnotationPresent(ComponentScan.class)){// 取出注解中的路径ComponentScan componentScan = (ComponentScan) appConfig.getAnnotation(ComponentScan.class);String path = componentScan.value();// 找到文件位置,修改为路径path = path.replace(".","/");// 需要解析的是编译之后的class类上的注解ClassLoader classLoader = MyApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);// 获取对应路径下的所有文件assert resource != null;File file = new File(resource.getFile());// 判断文件是否为目录if (file.isDirectory()){for (File listFile: file.listFiles()){// 获取到每个文件的绝对路径String absolutePath = listFile.getAbsolutePath();absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class")).replace("\\",".");try {// 判断每个文件是否有@Component注解Class<?> clazz = classLoader.loadClass(absolutePath);if (clazz.isAnnotationPresent(Component.class)){String beanName = clazz.getAnnotation(Component.class).value();// 创建BeanDefinition保存Bean对象信息BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(clazz);// 如果该类中有@Scope注解就保存为注解中的值,否则默认为singletonif (clazz.isAnnotationPresent(Scope.class)){beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());} else {beanDefinition.setScope("singleton");}// 把定义好的bean放入到beanDefintionMap中//如果@Comonent注解中没有值,则取首字母作为beanNameif (beanName.isEmpty()){beanName = Introspector.decapitalize(clazz.getSimpleName());}beanDefinitionMap.put(beanName, beanDefinition);}}catch (ClassNotFoundException e){e.printStackTrace();} }}}
}
  1. 完善MyApplicationContext中的getBean()方法,具体singleton和prototype处理方法后续完善。
public Object getBean(String beanName){// 通过beanDefinitionMap获取到beanDefinition并根据作用域来返回bean对象BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);// 如果没有beanName则证明该类型bean没有被声明if (beanDefinition == null){return new NullPointerException("No bean definition");}if (beanDefinition.getScope().equals("singleton")){// singleton类型的bean}else {// prototype类型的bean}
}
  1. MyApplicationContext中创建一个单例池存放所有单例Bean,后续使用时从单例池中取用。
public class MyApplicationContext {private Class appConfig;// 用map存储beanprivate Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();// 单例池存储所有单例的beanprivate Map<String, Object> singleObjects = new HashMap<>();
  1. 完善构造方法,扫描完成后遍历Bean,将所有Bean存放到单例池。
public MyApplicationContext(Class appConfig) {this.appConfig = appConfig;scan(appConfig);// 遍历bean,将所有单例的bean放入单例池中for (Map.Entry<String, BeanDefinition> entry: beanDefinitionMap.entrySet()){// 获取bean的名字和信息String beanName = entry.getKey();BeanDefinition beanDefinition = entry.getValue();// 如果是单例的则放入单例池中if(beanDefinition.getScope().equals("singleton")){Object bean = createBean(beanName, beanDefinition);singleObjects.put(beanName, bean);}}
}
  1. 完善CreateBean()方法
private Object createBean(String beanName, BeanDefinition beanDefinition) {// 通过type获取到类Class clazz = beanDefinition.getType();Object instance = null;try {// 构造方法创建对象instance = clazz.getConstructor().newInstance();} catch (InvocationTargetException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}return instance;
}
  1. 完善getBean()方法
public Object getBean(String beanName){// 通过beanDefinitionMap获取到beanDefinition并根据作用域来返回bean对象BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);// 如果没有beanName则证明该类型bean没有被声明if (beanDefinition == null){return new NullPointerException("No bean definition");}if (beanDefinition.getScope().equals("singleton")){// singleton类型的beanObject singletonBean = singleObjects.get(beanName);// 如果单例bean还没被加载,就直接创建加载if (singletonBean == null){singletonBean = createBean(beanName, beanDefinition);singleObjects.put(beanName, singletonBean);}return singletonBean;}else {// prototype类型的beanreturn createBean(beanName, beanDefinition);}
}
  1. 验证。我们再修改Test,测试不同作用域的不同反馈。
@Component("userService")
@Scope("prototype")
public class UserService  {
@Component("orderService")
@Scope("singleton")
public class OrderService {
package com.example;import com.example.springframe.MyApplicationContext;
import com.example.user.service.UserService;public class Test {public static void main(String[] args) {MyApplicationContext context = new MyApplicationContext(AppConfig.class);System.out.println("prototype: "+context.getBean("userService"));System.out.println("prototype: "+context.getBean("userService"));System.out.println("singleton: "+context.getBean("orderService"));System.out.println("singleton: "+context.getBean("orderService"));}
}

观察下面的输出结果我们可以发现,singleton两次是一个对象,prototype两次是两个对象。
在这里插入图片描述

五、实现@Autowired注解


  1. @Autowired注解
package com.example.springframe;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
  1. UserService中添加OrderService属性并添加@Autowired,现在Spring无法为OrderService赋值,后面会对内部逻辑进行定义。
package com.example.user.service;import com.example.springframe.Autowired;
import com.example.springframe.Component;
import com.example.springframe.InitializingBean;
import com.example.springframe.Scope;@Component("userService")
@Scope("prototype")
public class UserService {@Autowiredprivate  OrderService orderService;public void test(){System.out.println("userService");// 后续测试orderService属性注入System.out.println(orderService);}
}
  1. 创建Bean的时候为成员变量进行注入
private Object createBean(String beanName, BeanDefinition beanDefinition) {// 通过type获取到类Class clazz = beanDefinition.getType();Object instance = null;try {// 构造方法创造对象instance = clazz.getConstructor().newInstance();// 为添加了@Autowired注解的属性赋值for(Field field : clazz.getDeclaredFields()){if (field.isAnnotationPresent((Autowired.class))){field.setAccessible(true);// 为属性赋值field.set(instance, getBean(field.getName()));}}} catch (InvocationTargetException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}return instance;
}
  1. getBean()中添加判断逻辑,因为无法保证singleton的Bean只存在一个,所以需要做补充。
public Object getBean(String beanName){// 通过beanDefinitionMap获取到beanDefinition并根据作用域来返回bean对象BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);// 如果没有beanName则证明该类型bean没有被声明if (beanDefinition == null){return new NullPointerException("No bean definition");}if (beanDefinition.getScope().equals("singleton")){// singleton类型的beanObject singletonBean = singleObjects.get(beanName);// 如果单例bean还没被加载,就直接创建加载if (singletonBean == null){singletonBean = createBean(beanName, beanDefinition);singleObjects.put(beanName, singletonBean);}return singletonBean;}else {// prototype类型的beanreturn createBean(beanName, beanDefinition);}
}
  1. 现在通过test方法调用UserServicetest()方法可以顺利使用带有@AutowiredOrderService属性。
package com.example;import com.example.springframe.MyApplicationContext;
import com.example.user.service.UserService;public class Test {public static void main(String[] args) {MyApplicationContext context = new MyApplicationContext(AppConfig.class);UserService userService = (UserService) context.getBean("userService");userService.test();}
}

在这里插入图片描述

六、初始化以及前后的操作


  1. Bean的初始化操作,创建InitializingBean接口让UserService去实现。
package com.example.springframe;public interface InitializingBean {void afterPropertiesSet();
}
package com.example.user.service;import com.example.springframe.Autowired;
import com.example.springframe.Component;
import com.example.springframe.InitializingBean;
import com.example.springframe.Scope;@Component("userService")
@Scope("prototype")
public class UserService implements InitializingBean {@Autowiredprivate  OrderService orderService;@Overridepublic void afterPropertiesSet() {System.out.println("初始化userService");}public void test(){System.out.println("userService");System.out.println(orderService);}
}
  1. CreateBean()方法中添加初始化逻辑。
// 初始化,没法在bean创建时调用初始化方法,所以在createBean中实现
// 判断instance是不是InitializingBean的实例
if(instance instanceof InitializingBean){((InitializingBean) instance).afterPropertiesSet();
}
  1. 定义BeanPostProcesser接口,定义初始化前和初始化后的方法,之后再用一个类去实现这个接口。
package com.example.springframe;public interface BeanPostProcessor {default Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}default Object postProcessAfterInitialization(Object bean, String beanName) {return bean;}
}
package com.example.user.service;import com.example.springframe.BeanPostProcessor;
import com.example.springframe.Component;@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println(beanName+"初始化前");return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println(beanName+"初始化后");return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}
  1. 在初始化操作前后添加两个方法,去实现初始化前和初始化后的一些操作,在扫描的时候判断有哪些带有@Component的类实现了BeanPostProcessor接口,同时创建一个list来存放这些BeanPostProcessor
public class MyApplicationContext {private Class appConfig;// 用map存储beanprivate Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();// 单例池存储所有单例的beanprivate Map<String, Object> singleObjects = new HashMap<>();// 存放带有@Component注解并且实现了BeanPostProcessor方法的bean处理方法private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
  1. scan()方法中添加判断逻辑
// 判断文件是否为目录
if (file.isDirectory()){for (File listFile: file.listFiles()){// 获取到每个文件的绝对路径String absolutePath = listFile.getAbsolutePath();absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class")).replace("\\",".");try {// 判断每个文件是否有@Component注解Class<?> clazz = classLoader.loadClass(absolutePath);if (clazz.isAnnotationPresent(Component.class)){// 判断哪些bean实现了BeanPostProcessorif(BeanPostProcessor.class.isAssignableFrom(clazz)){BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();beanPostProcessorList.add(instance);}
  1. 在初始化前后添加对应的前后操作方法
// 初始化前的操作
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList){beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
}
// 初始化,没法在bean创建时调用初始化方法,所以在createBean中实现
// 判断instance是不是InitializingBean的实例
if(instance instanceof InitializingBean){((InitializingBean) instance).afterPropertiesSet();
}
// 初始化后的操作
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList){beanPostProcessor.postProcessAfterInitialization(instance, beanName);
}
  1. 这样我们就可以取到经过完整BeanPostProcessor以及初始化后的Bean对象,可以通过这种方法在初始化Bean的前后进行很多操作。
    在这里插入图片描述

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

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

相关文章

如何在RK3568开发板上实现USBNET?——飞凌嵌入式/USB Gadget/USB-NET/网络

本文将借助飞凌嵌入式OK3568-C开发板为大家介绍实现USBNET模式的方法&#xff0c;在这之前需要先知道什么是USB Gadget——USB Gadget是指所开发的电子设备以USB从设备的模式通过USB连接到主机。举个例子&#xff1a;将手机通过USB线插入PC后&#xff0c;手机就是USB Gadget。同…

BOM与DOM--记录

BOM基础&#xff08;BOM简介、常见事件、定时器、this指向&#xff09; BOM和DOM的区别和联系 JavaScript的DOM与BOM的区别与用法详解 DOM和BOM是什么&#xff1f;有什么作用&#xff1f; 图解BOM与DOM的区别与联系 BOM和DOM详解 JavaScript 中的 BOM&#xff08;浏览器对…

医疗虚拟仿真和虚拟现实有什么区别?哪个更好?

随着我们在仿真教育中越来越多地使用新技术&#xff0c;区分虚拟模式的类型很重要。虚拟仿真是一个统称&#xff0c;用来概括术语来描述各种基于仿真的体验&#xff0c;从基于屏幕的平台到沉浸式虚拟现实。然而&#xff0c;各虚拟平台在保真度、沉浸感和临场感的水平上有很大差…

流媒体及直播相关知识

文章目录 前言一、流媒体1、基本概念2、流式传输3、流媒体技术原理4、流媒体传输模式5、H.264 流媒体传输系统框架 二、直播1、直播中使用的流媒体协议2、直播的模块划分3、视频直播流程①、推流到服务器②、服务器流分发 前言 本文主要讲解流媒体及其直播相关知识&#xff0c…

【期望+状压DP】 2021 CCPC G

Problem - G - Codeforces 题意&#xff1a; 思路&#xff1a; 注意到 k 的范围是18&#xff0c;可以考虑状压 要求最小的期望长度&#xff0c;我们可以遍历所有可能的路径&#xff0c;统计这些路径的期望长度的最小值即可 那么怎么遍历呢&#xff1f;这里很经典的处理方式…

初识软件工程

软件工程是一门涵盖软件开发、维护和管理的学科&#xff0c;它通过应用工程化的原则和方法来提高软件系统的质量和可靠性。在当今数字化和信息化的时代&#xff0c;软件工程对于现代社会的各个领域都具有至关重要的作用。 基本概念&#xff1f; 计算机系统中与硬件相互依存的一…

复习Day01:数组part01:701. 二分查找、35. 搜索插入位置、367. 有效的完全平方数、69. x的平方根、74. 搜索二维矩阵

之前的blog链接&#xff1a;https://blog.csdn.net/weixin_43303286/article/details/131690654?spm1001.2014.3001.5501 我用的方法是在leetcode再过一遍例题&#xff0c;明显会的就复制粘贴&#xff0c;之前没写出来就重写&#xff0c;然后从拓展题目中找题目来写。辅以Lab…

ruoyi框架修改左侧菜单样式

菜单效果 ruoyi前端框架左侧的菜单很丑&#xff0c;我们需要修改一下样式&#xff0c;下面直接看效果。 修改代码 1、sidebar.scss .el-menu-item, .el-submenu__title {overflow: hidden !important;text-overflow: ellipsis !important;white-space: nowrap !important;//…

API文档搜索引擎

导航小助手 一、认识搜索引擎 二、项目目标 三、模块划分 四、创建项目 五、关于分词 六、实现索引模块 6.1 实现 Parser类 6.2 实现 Index类 6.2.1 创建 Index类 6.2.2 创建DocInfo类 6.2.3 创建 Weight类 6.2.4 实现 getDocInfo 和 getInverted方法 6.2.5 实现 …

【word格式】mathtype公式插入 | 段落嵌入后格式对齐 | 字体大小调整 |空心字体

1. 公式嵌入 推荐在线latex编辑器&#xff0c;可以截图转 latex 识别率很高 https://www.latexlive.com/home 美中不足&#xff0c;不开会员每天只能用3次识别。 通过公式识别后&#xff0c;输出选择align环境&#xff0c;然后在mathtype中直接粘贴latex就可以转好。 2.公式…

java框架-Springboot3-web开发

文章目录 自动配置默认效果WebMvcAutoConfigurationWebMvcConfigurer接口静态资源访问首页Favicon缓存 自定义静态资源路径1、配置方式2、代码方式 路径匹配规则内容协商默认支持json配置支持xml内容协商原理自定义支持ymal 模板引擎模板引擎Thymeleaf整合基础语法遍历判断属性…

成为威胁:网络安全中的动手威胁模拟案例

不断变化的网络威胁形势要求组织为其网络安全团队配备必要的技能来检测、响应和防御恶意攻击。然而&#xff0c;在研究中发现并继续探索的最令人惊讶的事情是&#xff0c;欺骗当前的网络安全防御是多么容易。 防病毒程序建立在庞大的签名数据库之上&#xff0c;只需更改程序内…