手写实现简易Spring框架

news2024/10/4 20:29:34

该文章记录的是自己实现简易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存储bean
    private 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注解就保存为注解中的值,否则默认为singleton
                        if (clazz.isAnnotationPresent(Scope.class)){
                            beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
                        } else {
                           beanDefinition.setScope("singleton");
                        }

                        // 把定义好的bean放入到beanDefintionMap中
                        //如果@Comonent注解中没有值,则取首字母作为beanName
                        if (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存储bean
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    // 单例池存储所有单例的bean
    private 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类型的bean
        Object singletonBean = singleObjects.get(beanName);
        // 如果单例bean还没被加载,就直接创建加载
        if (singletonBean == null){
            singletonBean = createBean(beanName, beanDefinition);
            singleObjects.put(beanName, singletonBean);
        }
        return singletonBean;
    }else {
        // prototype类型的bean
        return 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 {

    @Autowired
    private  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类型的bean
        Object singletonBean = singleObjects.get(beanName);
        // 如果单例bean还没被加载,就直接创建加载
        if (singletonBean == null){
            singletonBean = createBean(beanName, beanDefinition);
            singleObjects.put(beanName, singletonBean);
        }
        return singletonBean;
    }else {
        // prototype类型的bean
        return 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 {

    @Autowired
    private  OrderService orderService;

    @Override
    public 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 {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println(beanName+"初始化前");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public 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存储bean
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    // 单例池存储所有单例的bean
    private 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实现了BeanPostProcessor
                if(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.coloradmin.cn/o/1033144.html

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

相关文章

C++中的 throw详解

在《C++异常处理》一节中,我们讲到了 C++ 异常处理的流程,具体为: 抛出(Throw)--> 检测(Try) --> 捕获(Catch) 异常必须显式地抛出,才能被检测和捕获到;如果没有显式的抛出,即使有异常也检测不到。在 C++ 中,我们使用 throw 关键字来显式地抛出异常,它的用…

【GAMES103】基于物理的计算机动画入门(1)前置的基础数学知识

GAMES103: 基于物理的计算机动画入门 链接&#xff1a;GAMES103 1. 坐标系的划分 在游戏引擎中分为右手和左手坐标系&#xff0c;区分的依据是什么&#xff1f; 上图可以看到如果是左手坐标系&#xff0c;那么所有的物体都在屏幕后面&#xff0c;意味着x&#xff0c;y&#x…

【Linux 之二】Ubuntu下开发环境的搭建(NFS \ SSH \ FTP \ Smba \ ...)

目前正在进行Linux相关项目的开发&#xff0c;而我的Linux开发是在Ubuntu&#xff08;版本20.04&#xff09;下进行的&#xff0c;为此需要搭建很多Linux相关的开发环境&#xff0c;方便工作的进行。这里主要是对各种开发环境的搭建做一个总结记录&#xff0c;方便后面查阅&…

彻底解决win11系统0x80070032

经过各种尝试&#xff0c;终于找到原因。第一个是电脑加密软件&#xff0c;第二个是需要的部分功能没有开启&#xff0c;第三个BIOS设置。个人觉得第三个不重要。 解决方法 笔记本型号 笔记本型号是Thinkpad T14 gen2。进入BIOS的按键是按住Enter键。 1、关闭山丽防水墙服务…

AI AIgents时代 - (四.) HuggingGPT MetaGPT

&#x1f7e2; HuggingGPT HuggingGPT是一个多模型调用的 Agent 框架&#xff0c;利用 ChatGPT 作为任务规划器&#xff0c;根据每个模型的描述来选择 HuggingFace 平台上可用的模型&#xff0c;最后根据模型的执行结果生成总结性的响应。 这个项目目前已在 Github 上开源&am…

【暴力DP】CF1409 F

Problem - F - Codeforces 题意&#xff1a; 思路&#xff1a; 首先有个很明显的结论是&#xff1a;替换的字符一定是那两个字符之一 那么替换成哪个字符贡献更大不确定&#xff0c;因此考虑DP 因为有操作次数限制&#xff0c;直接把操作放进状态里 为了计算贡献&#xff…

Go语言高级特性解析与实践

1. 并发模型与goroutine Go语言以其强大的并发模型而闻名&#xff0c;它的核心机制是goroutine。goroutine是一种轻量级线程&#xff0c;由Go运行时负责调度。我们可以通过go关键字创建goroutine&#xff0c;而不需要像传统的线程编程那样关注底层的线程管理。 示例代码&…

Spring boot:解决@RequestBody失效问题:传入的实体类为NULL

1.问题描述 使用springboot写了一个controller类&#xff0c; 在使用postman测试用户注册功能的时候&#xff0c; 出现了传入实体类user&#xff0c;没有被读取的问题&#xff08;即requestbody注解失效&#xff09;。 2.解决方法 1.用户请求方式问题 在请求时&#xff0c…

ruoyi框架修改左侧菜单样式

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

java框架-Springboot3-数据访问

整合SSM SpringSpringMVCMybatis 整合步骤 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"…

k8s master 是如何进行pod的调度的

Master 节点将 Pod 调度到指定的 Node 节点的原理 该工作由 kube-scheduler 来完成&#xff0c;整个调度过程通过执行一些列复杂的算法最终为每个 Pod 计算出一个最佳的目标 Node&#xff0c;该过程由 kube-scheduler 进程自动完成。常见的有轮询调度&#xff08;RR&#xff09…

用HTML、CSS和JavaScript制作的通用进制转换器

随着编程和计算机科学越来越受欢迎&#xff0c;我们经常需要进行进制转换。本文将介绍一个简洁、美观、适用于移动设备的进制转换工具&#xff0c;并详细讨论其实现。 目录 &#x1f30d; 用HTML、CSS和JavaScript制作的通用进制转换器 1.项目图片展示 2. 技术栈 3. 主要功…

Unity Bolt模块间通信

使用Bolt无代码设计开发的时候&#xff0c;我们不能简单的认为只需要一个FlowMachine就可以完成所有流程的开发。我们需要不同的模块进行拆分&#xff0c;以便更好的管理和协作。这就需要不同模块之间的通信处理。经过研究与使用&#xff0c;将常用的通信方式总结如下&#xff…

React中setState的原理及深层理解

1.为什么使用setState React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化 我们必须通过setState来告知React数据已经发生了变化 setState方法是从Component中继承过来的。 2.setState异步更新 setState设计为异步&#xff0c;可…

23. 图论 - 图的由来和构成

文章目录 图的由来图的构成Hi, 你好。我是茶桁。 从第一节课上到现在,我基本上把和人工智能相关的一些数学知识都教给大家了,终于来到我们人工智能数学的最后一个部分了,让我们从今天开始进入「图论」。 图论其实是一个比较有趣的领域,因为微积分其实更多的是对应连续型的…

【react】使用useEffect操作dom

简言 在学习react时&#xff0c;需要了一个需要在useEffect里操作dom的用法。 一般不推荐这么干&#xff0c;如果是函数组件在一渲染已挂载后立即需要操作dom绑定事件等可以参考下面解决方法。 描述 官网交错运动示例这个示例中&#xff0c;usePointerPosition() Hook 追踪当…

【前段基础入门之】=>初识 HTML

文章目录 前言HTML的详情简介HTML 发展史HTML 入门1. HTML 标签元素2. HTML标签属性3. HTML的标准结构 总结 前言 在整个前端开发中&#xff0c;必须掌握的技术栈为&#xff1a; HTML &#xff0c;CSS&#xff0c;JavaScript&#xff0c;它们三者&#xff0c;共同组成了前端开发…

华为云云耀云服务器L实例评测|认识redis未授权访问漏洞 漏洞的部分复现 设置连接密码 redis其他命令学习

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到过MySQL数据库被攻击的情况&#xff0c;数据丢失&#xff0c;还好我有几份备份&#xff0c;没有造成太大的损失。昨天收到华为云的邮箱提醒&#xff0c;我的redis数据库没有设置密码&…

MySQL高级语句 Part2(视图表 +存储过程+条件语句+循环语句)

这里写目录标题 一、视图表 create view1.1 视图表概述1.2 视图表能否修改&#xff1f;&#xff08;面试题&#xff09;1.3 基本语法1.3.1 创建1.3.2 查看1.3.3 删除 1.4 通过视图表求无交集值 二、case语句三、空值(null) 和 无值( ) 的区别四、正则表达式4.1 基本语法和匹配模…

pcl--第十二节 2D和3D融合和手眼标定

2D&3D融合 概述 截止目前为止&#xff0c;我们学习了机器人学&#xff0c;学习了2D和3D视觉算法。我们也学习了2D相机(图像数据的来源)和3D相机(点云数据的来源)工作原理。 实际上&#xff0c;我们最终要做的&#xff0c;是一个手眼机器人系统。在这个系统里&#xff0c…