该文章记录的是自己实现简易Spring框架的记录,简化了源码中的大量操作,没有实现三级缓存的机制。
其中将会对@Component
,@ComponentScan
,@Scope
,@Autowired
注解。BeanDefinition
类以及ApplicationContext
、BeanPostProcessor
、InitializingBean
接口进行实现。
注: 该实现参考了“伟大的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都是单例的。
- singleton作用域,它是默认的作用域。当一个bean的作用域被设置为singleton时,在整个应用程序生命周期内,只会存在一个实例化的bean对象,所有请求该bean的对象都会共享同一个实例。也就是说,无论是在单线程环境还是多线程环境下,都只有一个bean对象存在。
- prototype作用域,它与singleton作用域有所不同。当一个bean的作用域被设置为prototype时,每次请求该bean的对象都会创建一个新的实例。也就是说,每次请求该bean,都会创建一个独立的新对象。
因为不同的业务场景需要不同的作用域来满足需求。
- singleton作用域可以节省内存资源,适用于那些无状态(stateless)的bean,例如工具类、配置类等。
- prototype作用域适用于那些有状态(stateful)的bean,每次请求都需要一个新的实例,例如用户请求、线程任务等。
@Component
@Scope("prototype")
public class MyBean {// ...
}
@Autowired
@Autowired
是 Spring 框架中的一个注解,用于实现自动装配(Autowired)功能。它可以标注在类的成员变量、方法、构造函数或参数上,让 Spring 完成对相应依赖的自动注入。
@Autowired
作用有以下几个方面:
- 自动装配依赖: 通过
@Autowired
注解,Spring 可以自动识别并注入相应的依赖对象,无需手动编写繁琐的依赖注入代码。这样可以简化开发过程,提高代码的可读性和可维护性。 - 解决依赖冲突: 当存在多个符合条件的依赖对象时,
@Autowired
注解可以根据一定的规则(如类型匹配、限定符等)来解决依赖冲突,确保正确的依赖对象被注入。 - 支持多种注入方式:
@Autowired
注解可以用于不同的注入方式,包括字段注入、方法注入、构造函数注入和参数注入。这样可以根据具体情况选择最合适的注入方式。
常见的使用方法:
- 字段注入:
@Autowiredprivate SomeDependency someDependency;
- 方法注入:
@Autowiredpublic void setSomeDependency(SomeDependency someDependency) {this.someDependency = someDependency;
}
- 构造函数注入:
@Autowiredpublic MyClass(SomeDependency someDependency) {this.someDependency = someDependency;
}
- 参数注入:
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
是一个接口,具体的实现类有多个,如 GenericBeanDefinition
、RootBeanDefinition
、ChildBeanDefinition
等。每个实现类都有不同的特点和用途,用于满足不同场景下的需求。
ApplicationContext接口
ApplicationContext
是 Spring 框架中的一个关键接口,它是 Spring 容器的核心接口之一。是 Spring 框架中负责管理和组织 bean 的创建、配置和生命周期的核心接口。它提供了丰富的功能和服务,可以方便地实现依赖注入、AOP、国际化等功能,是构建企业级应用程序的重要组成部分。
ApplicationContext
接口继承了 BeanFactory
接口,因此它具备了 BeanFactory
的所有功能,并在此基础上提供了更多的特性。以下是 ApplicationContext
的一些主要特点和功能:
- Bean 的生命周期管理:
ApplicationContext
负责管理 bean 的生命周期,包括创建、初始化和销毁。它会根据配置信息自动创建和初始化 bean,并在容器关闭时销毁 bean。 - 依赖注入:
ApplicationContext
支持依赖注入,可以自动将依赖的 bean 注入到目标 bean 中,无需手动编写繁琐的依赖注入代码。这样可以简化开发过程,提高代码的可读性和可维护性。 - AOP(面向切面编程)支持:
ApplicationContext
提供了对 AOP 的支持,可以通过配置和使用切面来实现横切关注点的模块化。它可以方便地实现事务管理、日志记录、性能监控等功能。 - 国际化支持:
ApplicationContext
提供了国际化支持,可以方便地实现多语言的应用程序。它可以根据不同的语言环境加载相应的资源文件,并提供统一的访问接口。 - 事件机制:
ApplicationContext
支持事件机制,可以发布和监听事件。通过事件机制,不同的组件可以进行解耦,实现松耦合的设计。 - 配置文件的加载和解析:
ApplicationContext
负责加载和解析配置文件,可以使用多种格式的配置文件,如 XML、注解、Java 配置等。它可以根据配置文件中的信息创建和配置相应的 bean。 - 容器的扩展性:
ApplicationContext
提供了容器的扩展机制,可以通过自定义扩展点来扩展容器的功能。例如,可以自定义BeanPostProcessor
、BeanFactoryPostProcessor
等来对 bean 进行定制化处理。
ApplicationContext
接口有多个实现类,如ClassPathXmlApplicationContext
、AnnotationConfigApplicationContext
、FileSystemXmlApplicationContext
等,每个实现类都有不同的特点和用途,用于满足不同场景下的需求。
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
- 创建一个
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;}
}
- 创建一个service包,里面包含
UserService
和OrderService
两个类。
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");}
}
- 为user创建一个
AppConfig
配置类。
package com.example;public class AppConfig {
}
- 创建一个
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。
- 现在去实现
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
属性值时,它将默认为一个空字符串。
- 在
AppConfig
类上添加咱们自己写的@ComponentScan
注解,配置扫描路径,扫描到包,不要到具体类
package com.example;import com.example.springframe.ComponentScan;@ComponentScan("com.example.user.service")
public class AppConfig {
}
- 再配置个
@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 "";
}
- 在之前创建好的
UserSerivce
添加@Component
注解,之后@Component
进行扫描的时候就会将带有@Component
注解的类注册为Bean。OrderService
不添加注解,与UserService
进行对比。
@Component
public class UserService {
现在spring已经可以扫描到包了,下面我们来写相关扫描逻辑和处理方法
四、扫描和加载逻辑
在完成了上个部分的创建Bean的过程之后,在这个部分来编写扫描和加载的逻辑。
- 在
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
。
- 实现
@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 "";
}
UserService
和OrderService
加入@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");}
}
- 创建
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;}
}
- 在
MyApplicationContext
中定义一个map
来存放这些Bean,beanName
为map
的key
,BeanDefinition
为value
。
public class MyApplicationContext {private Class appConfig;// 用map存储beanprivate Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
- 完善
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();} }}}
}
- 完善
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}
}
- 在
MyApplicationContext
中创建一个单例池存放所有单例Bean,后续使用时从单例池中取用。
public class MyApplicationContext {private Class appConfig;// 用map存储beanprivate Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();// 单例池存储所有单例的beanprivate Map<String, Object> singleObjects = new HashMap<>();
- 完善构造方法,扫描完成后遍历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);}}
}
- 完善
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;
}
- 完善
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);}
}
- 验证。我们再修改
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
注解
@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 {
}
- 在
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);}
}
- 创建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;
}
- 在
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);}
}
- 现在通过test方法调用
UserService
的test()
方法可以顺利使用带有@Autowired
的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);UserService userService = (UserService) context.getBean("userService");userService.test();}
}
六、初始化以及前后的操作
- 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);}
}
- 在
CreateBean()
方法中添加初始化逻辑。
// 初始化,没法在bean创建时调用初始化方法,所以在createBean中实现
// 判断instance是不是InitializingBean的实例
if(instance instanceof InitializingBean){((InitializingBean) instance).afterPropertiesSet();
}
- 定义
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);}
}
- 在初始化操作前后添加两个方法,去实现初始化前和初始化后的一些操作,在扫描的时候判断有哪些带有
@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<>();
- 在
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);}
- 在初始化前后添加对应的前后操作方法
// 初始化前的操作
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);
}
- 这样我们就可以取到经过完整
BeanPostProcessor
以及初始化后的Bean对象,可以通过这种方法在初始化Bean的前后进行很多操作。