简易实现Spring Framework底层机制
Posted Al_tair
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易实现Spring Framework底层机制相关的知识,希望对你有一定的参考价值。
实现Spring Framework底层机制
大家好呀!我是小笙,本节主要是想和大家简单讲讲ioc的实现以及aop的部分实现原理
实现Spring Framework底层机制
思考三个问题:
- Spring容器 如何实现依赖注入singleton,prototype(单例,多例)
- Spring 如何实现BeanPostProcesser (后置处理器)
- Spring 如何实现AOP切面编程机制
注意:以下实现只是为了便于理解真正的 spring 底层机制,并非完全一致
整体架构
思路分析以及实现过程
1.创建Spring容器
扫描包下所有的文件,将标有特定注解的类通过反射创建bean对象,存入到ioc容器中
- 使用lnsSpringConfig.java代替bean.xml文件
- 扫描包下文件是否被标记注解
- 将单例bean对象实例化放入singleton 单例池中
知识点
类加载器 详细说明
Bootstrap 类加载器 <=> 对应路径 jre/lib
Ext 类加载器 <=> 对应路径 jre/lib/ext
App 类加载器 <=> 对应路径 classpath
1.指定需要扫描包的全路径
使用 lnsSpringConfig.java 代替 bean.xml 文件(指定需要扫描包的全路径)
/**
* @author Al_tair
* @date 2022/7/14-22:14
* 作用类似于bean.xml文件配置扫描包的全路径
*/
@ComponentScan(value = "com.Al_tair.spring.myComponent")
public class ClassPathConfig
@ComponentScan注解是自己实现的,就是用 value 来存储指定扫描包的全路径
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan
// 通过 value 指定扫描包的全路径
String value() default "";
主方法就是将配置类的信息传给 ioc 容器,进行扫描包进行初始化
public class AppMain
public static void main(String[] args)
// ClassPathConfig.class 配置类信息,可以通过获取该类上的注解信息来进行 bean 对象的注入
SpringIocApplicationContext ioc = new SpringIocApplicationContext(ClassPathConfig.class);
2.扫描包并进行分类(单例、多例)
首先,我们需要选择存储的数据结构,我采用 Map 键值对的形式来定义 BeanDefinition 对象和 singletonObject 单例池对象
- BeanDefinition 对象是用来存储 scope(单例或者多例) 和 bean 对象的 class 信息,方便反射初始化对象
- singletonObject 单例池对象是提前创建来懒加载单例对象,方便获取,多例对象都是通过创建 bean 对象的时候创建
/**
* 定义属性 BeanDefinitionMap id <-> BeanDefinition 对象
* ConcurrentHashMap 线程安全
*/
private final ConcurrentHashMap<String,BeanDefinition> ioc = new ConcurrentHashMap<>();
/**
* 单例池:存放单例对象
*/
private ConcurrentHashMap<String,Object> singletonObject = new ConcurrentHashMap<>();
BeanDefinition 类信息
/**
* 用于封装/记录Bean的信息
* 1. scope
* 2. bean 对应的 Class 对象
*/
public class BeanDefinition
private String scope;
private Class clazz;
// ...省略
然后,我们需要找到需要扫描的类(找出.class的文件,并存储类路径)
// 1.获取注解信息:@com.Al_tair.spring.annotation.ComponentScan(value=com.Al_tair.spring.myComponent)
ComponentScan componentScan = (ComponentScan)this.springConfig.getDeclaredAnnotation(ComponentScan.class);
// 2.取出注解里的 value 值并转换成相对文件路径 com/Al_tair/spring/myComponent
String ScanPackage = componentScan.value().replace('.','/');
// 3.得到类加载器:找到资源运行的真实路径
ClassLoader classLoader = SpringIocApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(ScanPackage);
// 4.获取到资源路径上的文件夹
File file = new File(resource.getFile());
// 通过文件夹所在位置进行遍历
scanFiles(file);
扫描指定文件夹下的 .class 文件,我将这个类路径放在 list 列表下,方便以后获取
/**
* 存储包下的全部.class文件路径
*/
private List<String> list = new ArrayList<>();
scanFiles 方法:采用递归的方式进行遍历搜索
/**
* 扫描所有包以及子包下的类,返回全路径
* @param files 传入需要扫描的文件
*/
private void scanFiles(File files)
File[] f = files.listFiles();
for (File file : f)
if(file.isDirectory())
scanFiles(file);
else
String absolutePath = file.getAbsolutePath();
if(absolutePath.endsWith("class"))
// 例如:com.Al_tair.spring.myComponent.myService.PrototypeServiceImpl
list.add(absolutePath.substring(absolutePath.lastIndexOf("\\\\com") + 1, absolutePath.lastIndexOf(".class")).
replace("\\\\","."));
最后,我们要对指定注入的 bean 对象带上 MyComponent 注解以及 区分单例还是多例的 MyScope 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent
// 通过 value 可以给组件注入对象的id名,默认id名为类名首字母小写
String value() default "";
/**
* 指定一个 bean 的作用范围 【singleton,prototype】
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyScope
/**
* 使用value来指定 singleton,prototype
* @return
*/
String value() default "";
然后我们需要遍历列表中每个类路径,并进行类加载,判断该类是否被 MyComponent 注解标注,如果有则将 id 和 Class 信息存储在 BeanDefinition 中
for (String s : list)
try
Class<?> aClass = classLoader.loadClass(s);
// 判断该类是否被 MyComponent 注解标注
if(aClass.isAnnotationPresent(MyComponent.class))
MyComponent declaredAnnotation = aClass.getDeclaredAnnotation(MyComponent.class);
// beanName
String id = declaredAnnotation.value();
if("".equals(id))
// 将类名首字母小写作为 id
id = s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
+ s.substring(s.lastIndexOf(".")+2);
// 将 id 和 Class 信息存储在 BeanDefinition 中
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(aClass);
// 判断是单例还是多例
if(aClass.isAnnotationPresent(MyScope.class))
// 标注注解则从注解的 value 取出值判断是多例还是单例
MyScope ScopeValue = aClass.getDeclaredAnnotation(MyScope.class);
beanDefinition.setScope(ScopeValue.value());
else
// 没有标注注解默认为单例对象
beanDefinition.setScope("singleton");
ioc.put(id,beanDefinition);
// 如果是单例对象,放入单例池
if("singleton".equals(beanDefinition.getScope()))
Object bean = createBean(beanDefinition,id);
singletonObject.put(id,bean);
catch (Exception e)
e.printStackTrace();
3.创建 Bean 对象
分三种情况:
- 依赖注入 MyAutowired 方法:在其他类 创建的时候需要注入其他 bean 对象(控制反转,动态依赖注入)
- 单例对象直接获取该 Bean 对象(而且只能创建一次)
- 多例对象每次获取都需要创建 Bean 对象(每个对象都是不同的)
/**
* 创建 bean 对象
*/
private Object createBean(BeanDefinition beanDefinition,String beanName)
// 得到 bean 的 Class 对象
Class clazz = beanDefinition.getClazz();
try
// 通过反射得到 bean 实例
Object instanse = clazz.getDeclaredConstructor().newInstance();
// 依赖注入
// 1.遍历当前的所有对象的字段
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields)
// 2. 判断字段是否被特定注解注释
if(declaredField.isAnnotationPresent(MyAutowired.class))
MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
// 3.是否必须依赖注入
boolean required = annotation.required();
if(required)
// 4.获取字段名字
String name = declaredField.getName();
// 5.通过 getBean 来组装对象
Object bean = getBean(name);
// 6.进行组装(因为属性是私有的,所以需要进行暴破)
declaredField.setAccessible(true);
declaredField.set(instanse,bean);
else
// 假设都需要依赖注入
return instanse;
catch (Exception e)
e.printStackTrace();
// 如果创建对象失败
return null;
4.获取 Bean 对象
通过 id 来获取 bean 对象
public Object getBean(String id)
BeanDefinition beanDefinition = ioc.get(id);
if(beanDefinition != null && ioc.containsKey(id))
String scope = beanDefinition.getScope();
// 如果是单例对象,直接获取
if("singleton".equals(scope))
return singletonObject.get(id);
else
// 反之多例对象,需要创建 Bean
return createBean(beanDefinition,id);
else
throw new RuntimeException("没有该 bean 对象");
讲到这里 IOC 容器就算简单的创建出来了,接下来我们需要讲一下 AOP机制
2.实现 AOP 机制
1.首先需要实现添加对象初始化接口
/**
* 初始化 Bean 对象的接口:用来初始化 Bean 的时候调用
*/
public interface MyInitializingBean
// 初始化方法:在 setter 方法执行后调用
void afterPropertiesSet() throws Exception;
实现了该初始化接口的类:当创建 MyServiceImpl 对象的时候将会初始化
@MyComponent
public class MyServiceImpl implements MyInitializingBean
@MyAutowired
private MyDaoImpl myDaoImpl;
@Override
public void afterPropertiesSet() throws Exception
System.out.println("MyServiceImpl 初始化方法被调用");
将初始化操作放在创建 Bean 对象方法的后面
private Object createBean(BeanDefinition beanDefinition,String beanName)
// 得到 bean 的 Class 对象
Class clazz = beanDefinition.getClazz();
// 通过反射得到 bean 实例
try
Object instanse = clazz.getDeclaredConstructor().newInstance();
// 依赖注入
// ...省略
// 创建好实例之后进行初始化
// instanceof: 判断当前某个对象的运行类型是否为该类型实现的接口或者父类
if(instanse instanceof MyInitializingBean)
((MyInitializingBean)instanse).afterPropertiesSet();
return instanse;
catch (Exception e)
e.printStackTrace();
// 如果创建对象失败
return null;
2.后置处理器的实现
MyBeanPostProcessor 接口实现:主要是在所有 bean 对象初始化前后调用(切面编程)
/**
* 后置处理器:会对 Spring 容器中的所有 Bean实例生效(切面编程)
*/
public interface MyBeanPostProcessor
/**
* 在 bean 对象初始化方法之前调用
*
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName)
return bean;
/**
* 在 bean 对象初始化方法之后调用
*
* @param bean
* @param beanName
* @return
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName)
return bean;
实现自制的后置处理器接口: AOP 动态代理机制的实现核心就是后置处理器实现类中实现
/**
* 实现自制化的后置处理器
*/
@MyComponent
public class LnsBeanPostProcessor implements MyBeanPostProcessor
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
// 后置处理器会在容器初始化 bean 对象之前生效
// 通过 instanceof 关键字判断,可以用于多个对象的编程
// 可以用来处理日志、权限、身份、事务等
System.out.println("Before=" + beanName);
return bean;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
System.out.println("After=" + beanName);
return bean;
为了简化操作,我将后置处理器对象单独放一个容器中,方便调用对象初始化方法前后调用
/**
* 存放实现了 MyBeanPostProcessor 接口的后置处理器的对象
* 实际是存放在单例池中,这里为了方便取出后置处理器的对象单独写个集合
*/
private List<MyBeanPostProcessor> beanPostProcessorList = new ArrayList<>();
为了添加后置处理器对象,需要使用 isAssignableFrom() 方法判断某个类是否实现了某个接口(注意区别 instanceof 关键字)
// beanDefinitionByScan 方法
for (String s : list)
try
Class<?> aClass = classLoader.loadClass(s);
if(aClass.isAnnotationPresent(MyComponent.class))
// 判断是否实现了MyBeanPostProcessor 接口
// 这里不能使用instanceof来判断,因为instanceof是用来判断实例对象,但是判断某个类是否实现了某个接口就要用底下这个方法
if(MyBeanPostProcessor.class.isAssignableFrom(aClass))
MyBeanPostProcessor myBeanPostProcessor = (MyBeanPostProcessor)aClass.newInstance();
beanPostProcessorList.add(myBeanPostProcessor);
continue;
MyComponent declaredAnnotation = aClass.getDeclaredAnnotation(MyComponent.class);
// beanName
String id = declaredAnnotation.value();
if("".equals(id))
// 将类名首字母小写作为 id
id = s.substring(s.lastIndexOf(".") + 1,s.lastIndexOf(".") + 2).toLowerCase()
+ s.substring(s.lastIndexOf(".")+2);
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setClazz(aClass);
// 判断是单例还是多例
if(aClass.isAnnotationPresent(MyScope.class))
MyScope ScopeValue = aClass.getDeclaredAnnotation(MyScope.class);
beanDefinition.setScope(ScopeValue.value());
else
beanDefinition.setScope("singleton");
ioc.put(id,beanDefinition);
// 如果是单例对象,放入单例池
if("singleton".equals(beanDefinition.getScope()))
Object bean = createBean(beanDefinition,id);
singletonObject.put(id,bean);
catch (Exception e)
e.printStackTrace();
最后需要在初始化方法前后添加后置处理器对象的调用
// createBean 方法
// 后置处理器
for (MyBeanPostProcessor myBeanPostProcessor : beanPostProcessorList)
Object o = myBeanPostProcessor.postProcessBeforeInitial以上是关于简易实现Spring Framework底层机制的主要内容,如果未能解决你的问题,请参考以下文章