4

【Spring系列】- 手写模拟Spring框架 - 怒放吧德德

 1 year ago
source link: https://www.cnblogs.com/lyd-code/p/16886250.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

简单模拟Spring

😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 一个有梦有戏的人 @怒放吧德德
🌝分享学习心得,欢迎指正,大家一起学习成长!

前言

上次已经学习了Java的设计模式,接下来就先来学习一下如何手写模拟简易的Spring,通过动手实践,才会更好的了解spring底层原理,今天就简单的模拟Spring容器是如何创建,bean又是如何注入的。
来看一下本次案例的spring类图

Spring容器

模拟spring,首先就是需要一个容器,是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管理。Spring容器也就是一个bean工厂(BeanFactory)。应用中bean的实例化,获取,销毁等都是由这个bean工厂管理的。就像我们刚开始学习的时候接触的ApplicationContext,就是spring的容器,他就是为了完成容器的配置,初始化,管理bean的。因此笔者自己创建了一个LydApplicationContext来模拟简单的spring容器。

1|0开始使用

首先通过new LydApplicationContext(AppConfig.class)实例化对象,在通过applicationContext.getBean("userService")去获得bean对象。然而在容器的初始化可是做了许多的事情,包括扫描、实例化bean等等操作。
初始容器创建:



public class LydApplicationContext { private Class configClass; public LydApplicationContext(Class configClass) { // 构造方法 this.configClass = configClass; } }

Spring扫描底层实现

Spring容器建好之后我们就需要通过配置文件的注解获取扫描路径,我们需要获取所有的bean,并且需要实例对象。在此我们需要一个配置文件,就是使用new LydApplicationContext(AppConfig.class) 实例携带的配置类,当然这里有好多的形式,也可以是通过xml文件来处理。

1|0配置文件AppConfig.java

这个就是为了提供扫描的包路径的,不做任何操作,所以不需要其他代码。



@ComponentScan("com.lyd.service") // 扫描路径,扫描这个包下的 public class AppConfig { }

通过注解存放这个包路径,在后面可以通过这个注解来获取包路径,所以就需要我们创建一个ComponentScan注解。

1|0编写ComponentScan注解

这个注解是用来spring容器扫描包为之提供包路径。



@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ComponentScan { // 指定扫描路径 String value() default ""; }

1|0编写Component注解

在Spring中,通过Component注解将bean注入Spring容器中,这里我们也采用高这个注解。



@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { String value() default ""; }

1|0获取包路径

既然已经通过注解将包路径存在配置类中,接下来就可以通过这个注解来得到。但是,在这里需要注意的是,我们扫描的并非是java源文件,而是编译后的class文件。我们需要在LydApplicationContext的构造方法中去实现。
首先,我们需要通过isAnnotationPresent方法先判断是否存在ComponentScan注解,在通过类的getAnnotation方法来得到注解。这样就可以直接得到注解上的值。这个值就是我们写入的包路径,注意,这里的路径是com.lyd.service,而我们需要用替换方法将'.'替换成'/',因为在后面获取资源路径的时候,用的是com/lyd/service这种形式,也就是相对路径。
接下来需要获取资源路径,这个时候就需要用到类加载器LydApplicationContext.class.getClassLoader(),类加载器中有一个getResource(path)方法,这个可以根据传入的路径获取相应的资源,最后是能够拼出我们需要的绝对路径。



if (configClass.isAnnotationPresent(ComponentScan.class)) { ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class); // 1.1 扫描路径:只是个包名,扫描的是java的class文件,而并非源文件,com.lyd.service String path = componentScanAnnotation.value(); // 1.2 将路径文件替换成/的形式 path = path.replace(".","/"); // 1.3 通过类加载器获取资源路径 ClassLoader classLoader = LydApplicationContext.class.getClassLoader(); URL resource = classLoader.getResource(path); // 1.4 转成文件形式,主要是为了获取他的绝对地址 File file = new File(resource.getFile()); System.out.println(file); }

通过类加载器得到的资源有个获取File的方法,然后我们通过File file = new File(resource.getFile());将资源转成File类型,因为他可以表示一个地址或者是具体文件。扫描就是扫描文件路径,也就是文件夹。我们可以打印看一下这个地址:

判断是否为文件夹,如果是,那就获取里面的所有文件,在通过遍历这些文件,获取绝对路径。



if (file.isDirectory()) { // 如果是文件夹 File[] files = file.listFiles(); for (File f : files) { String absolutePath = f.getAbsolutePath(); // 获取绝对路径 System.out.println("绝对路径:" + absolutePath); } }

我们可以打印出来看一下:

因为我们要的是编译的.class文件,因此需要在遍历文件的时候进行判断文件是否为.class文件。为了拿到这个类,需要通过全限定名使用类加载器获取类 ,也就是利用反射机制。我们都知道,spring是通过Component注解来将bean注入spring的,因此最后就是通过判断是否有这个注解来得到一个bean。



for (File f : files) { String absolutePath = f.getAbsolutePath(); // 获取绝对路径 System.out.println("绝对路径:" + absolutePath); // 1.5 对编译文件进行处理 if (absolutePath.endsWith(".class")) { // 判断是否为编译文件 /** * 需要拿到的是编译文件,通过类加载器去获取 * 需要将com\lyd\service\UserService转成com.lyd.service.UserService */ String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")); className = className.replace("\\", "."); System.out.println("类名:" + className); try { // 1.6 通过全限定名使用类加载器获取类 (利用反射机制) Class<?> clazz = classLoader.loadClass(className); // 1.7 在通过这个clazz(类)来判断是否有component注解,有则是bean if (clazz.isAnnotationPresent(Component.class)) { // 到这里就是一个bean } } catch (Exception e) { e.printStackTrace(); } } }

最后我们需要的地址就是如下:

Spring生成BeanDefinition

在我们Spring容器启动或者是扫描的时候,并不建议直接实例化bean对象,因为bean是区分单例和多例的,多例bean我们是需要用到的时候再去创建。这个时候就需要生成BeanDefinition,即bean的定义,这个类存储了类和作用域(单例还是多例)。

1|0定义Scope注解

通过Scope这个注解来标明是单例bean还是多例bean。



@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Scope { String value() default ""; }

1|0定义BeanDefinition

在spring中,不会在获取bean的时候再去解析是否为单例,而是通过BeanDefinition类来操作。对bean的定义,记录了bean类和作用域。



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; } }

因为我们注入springbean对象是有Component注解,因此在扫描的时候,我们会通过这个获得到bean,在这时候去创建BeanDefinition对象。通过判断注解上的值来赋值其作用域,如果没有设置,就默认是单例模式。
创建好的bean对象,我们还需要将他进行保存起来,这个就需要定义ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();存储beanDefinition。key是component的值,如果component没有传入beanName的值,那就使用spring的命名规则,采用类名首字母小写。



// 1.7 在通过这个clazz(类)来判断是否有component注解,有则是bean if (clazz.isAnnotationPresent(Component.class)) { Component annotation = clazz.getAnnotation(Component.class); String beanName = annotation.value(); // * 默认生成bean,如果只使用Component注解,没有写上beanName的值,那么就需要自动生成 if (beanName.equals("")) { // 默认开头小字母 beanName = Introspector.decapitalize(clazz.getSimpleName()); } /** * 這就是一个bean了 * 然而在这里并不是直接就创建bean了,bean分为了单例bean和多例bean */ BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setType(clazz); if (clazz.isAnnotationPresent(Scope.class)) { // 判断是单例还是多例 Scope scope = clazz.getAnnotation(Scope.class); beanDefinition.setScope(scope.value()); } else { beanDefinition.setScope("singleton"); } beanDefinitionMap.put(beanName, beanDefinition); }

getBean底层

通过传来一个beanName,通过beanDefinitionMap中获取key为beanName的BeanDefinition对象,进行判空处理。这样我们可以通过这个BeanDefinition对象去获取作用域,判断是否为单例。

1|0获取bean

在此,我们创建单例的时候,是需要将单例保存起来的,需要定义ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();单例池来保存单例bean。然后在getBean的时候,如果是单例bean,就可以先去单例池中寻找,如果没找到,再去创建对象。而多例模式就需要每次都去创建。



// 获取bean对象 public Object getBean(String beanName) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition == null) { throw new NullPointerException("找不到bean名字为[" + beanName + "]的bean对象"); } else { // 找到就做相应的操作 String scope = beanDefinition.getScope(); if (scope.equals("singleton")) { // 通过单例池获取 Object bean = singletonObjects.get(beanName); if (bean == null) { // 单例池中如果没有bean,就需要去创建 bean = createBean(beanName, beanDefinition); singletonObjects.put(beanName, bean); } return bean; } else { // 多例的就不需要记录,每次都是通过创建 return createBean(beanName, beanDefinition); } } }

1|0创建bean

利用反射机制,通过无参构造方法去获取实例,这里就是bean对象实例了。就可以直接返回。



// 创建bean private Object createBean(String beanName, BeanDefinition definition) { // 利用反射获取实例,采用无参构造方法 Class clazz = definition.getType(); // 通过无参构造方法获取实例 try { Object instance = clazz.getConstructor().newInstance(); // 到这里直接返回,bean的对象也就创建完成

return instance;

} catch (InstantiationException e) { e.printStackTrace(); } return null; }

在构造方法扫描后要根据生成的beanDefinitionMap去创建单例bean对象。



// 2 创建单例bean对象 for (String beanName : beanDefinitionMap.keySet()) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition.getScope().equals("singleton")) { // 那么,如何保证单例呢?就需要有个单例池,singletonObjects Object bean = createBean(beanName, beanDefinition); singletonObjects.put(beanName, bean); // 将单例bean存到单例池中 } }

1|0运行



public class Test { public static void main(String[] args) { LydApplicationContext applicationContext = new LydApplicationContext(AppConfig.class); System.out.println(applicationContext.getBean("userService")); System.out.println(applicationContext.getBean("userService")); System.out.println(applicationContext.getBean("userService")); System.out.println(applicationContext.getBean("userService")); System.out.println(applicationContext.getBean("userService")); } }

我们可以先使用单例模式进行测试,我们不加scope注解,并且获取多个bean,可以看到得到的bean对象是同一个。

多例的时候在scope标上值,就可以看到每次获取的bean对象都是不一样的。

Autowired自动依赖注入

当我们在UserService中使用RoleService,我们就需要通过Autowired注解进行依赖注入。就是需要在createBean方法中,在创建实例之后,获取类中的字段,进行依赖注入,要通过判断Autowried注解。通过字段的set方法,将bean对象注入,而这个对象如何获得呢?那就是用过getBean()方法。



// 3 依赖注入 for (Field field : clazz.getDeclaredFields()) { // 判断字段上是否存在Autowried注解 if (field.isAnnotationPresent(Autowired.class)) { /** * 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。 * 值为 false 则指示反射的对象应该实施 Java 语言访问检查; * 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ; */ field.setAccessible(true); // 反射需要设置这个,不然无法赋值 // 用其属性名,这就意味着private RoleService roleService;roleService不能乱取 field.set(instance, getBean(field.getName())); } }

Aware回调机制与初始化

1|0Aware回调

那如果我们需要获取当前bean的名字呢?那就得通过Aware回调机制。我们需要创建一个BeanNameAware接口,里面提供一个setBeanName方法。



public interface BeanNameAware { public void setBeanName(String beanName); }

createBean方法中去编写回调机制,通过判断这个实例是否有BeanNameAware这个类,通过setName方法间接传递了beanName。



// 4 Aware回调机制 if (instance instanceof BeanNameAware) { ((BeanNameAware)instance).setBeanName(beanName); }

UserService方法中去实现这个BeanNameAware方法,这就能够在UserService里的beanName字段中得到这个bean对象的真实的beanName了。



@Component("userService") // 注入spring @Scope("property") public class UserService implements BeanNameAware, InitializingBean { @Autowired private RoleService roleService; // 依赖注入,加上@Autowired注解

private String beanName;

@Override public void setBeanName(String beanName) { this.beanName = beanName; }

public void test() { System.out.println("RoleService: " + roleService); }

@Override public void afterPropertiesSet() { // ...... System.out.println("初始化"); } }

1|0初始化

在spring容器中,不只是完成bean对象的创建,还需要能够对bean进行初始化。需要创建InitializingBean接口类。



public interface InitializingBean { public void afterPropertiesSet(); }

在需要进行初始化的bean对象去实现这个接口的方法,这里main可以进行一些操作。在createBean中,会根据判断是否有InitializingBean类,会在实例化之后调用这个方法进行初始化。



// 5 初始化 if (instance instanceof InitializingBean) { ((InitializingBean)instance).afterPropertiesSet(); }

BeanPostProcessor

BeanPostProcessor可以对spring中bean的创建去做一些操作。

1|0BeanPostProcessor接口

在spring中定义一个BeanPostProcessor接口,里面会有初始化前后操作的方法,并且将beanNamebean对象带入进行自定义的操作。



public interface BeanPostProcessor { public Object postBeforeProcessor(String beanName, Object bean); // 初始化前 public Object postAfterProcessor(String beanName, Object bean); // 初始化后 }

1|0自定义BeanPostProcessor

这里可以定义自己的类去实现spring中的BeanPostProcessor,对初始化进行一些相应操作。并且能够根据某个bean对象来做不同的操作。原理就是将MyBeanPostProcessor注入到容器中,在扫描的时候将这个对象保存起来,在创建bean的时候去遍历BeanPostProcessor集合,在去调用这个实例的方法。



@Component // 需要注入spring public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postBeforeProcessor(String beanName, Object bean) { System.out.println("初始化前的bean:" + beanName + " -- " + bean); return bean; }

@Override public Object postAfterProcessor(String beanName, Object bean) { System.out.println("初始化后的bean:" + beanName + " -- " + bean); return bean; } }

然而,在spring扫描的时候会进行操作,因为自己实现的BeanPostProcessor是通过Component注解注入spring容器的。因此可以通过判断有Component注解时候,进行判断是否含有BeanPostProcessor类,如果有生成BeanPostProcessor对象,并且将其实例添加到beanPostProcessorList容器中。在此就需要定义ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();来记录BeanPostProcessor。



// 6 判断是否是并加入beanPostProcessorList,这里不能使用instanceof if (BeanPostProcessor.class.isAssignableFrom(clazz)) { // 直接生成对象 BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance(); // 然后保存进去 beanPostProcessorList.add(instance); }

接着就可以在创建bean对象的时候,在初始化前后去遍历这个BeanPostProcessor链表,调用相应的方法,就能够调用自定义的MyBeanPostProcessor的方法。



// 6 BeanPostProcessor 初始化前 AOP 遍历beanPostProcessorList for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postBeforeProcessor(beanName, instance); }

// 5 初始化 if (instance instanceof InitializingBean) { ((InitializingBean)instance).afterPropertiesSet(); }

// 6 BeanPostProcessor 初始化后 AOP for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postAfterProcessor(beanName, instance); }

运行结果:.

AOP机制

需要通过代理对象。这样如果不进行操作,返回的对象还是原来的,如果是通过操作了,那么返回的就是代理的对象。这里就简单的打印一句话。在扫描的时候,扫到userService这个bean的时候,返回的实例就是代理对象。



@Override public Object postAfterProcessor(String beanName, Object bean) { System.out.println("初始化后的bean:" + beanName + " -- " + bean);

if (beanName.equals("userService")) { // 创建一个代理对象, 代理的是UserInterface这个 Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { @Override /** * proxy:代理对象 * method:代理对象当前正在执行的方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("切面逻辑"); return method.invoke(bean, args); } }); return proxyInstance; } return bean; }

源码备注

1|0spring

1|0LydApplicationContext



public class LydApplicationContext { private Class configClass; // 注入的配置类 private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // key是component名字 private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 单例池 private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>(); // 记录BeanPostProcessor,在扫描的时候判断,使用

public LydApplicationContext(Class configClass) { // 构造方法 this.configClass = configClass; // spring容器创建之后 // 1 扫描 // 通过配置文件的注解获取扫描路径 if (configClass.isAnnotationPresent(ComponentScan.class)) { ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class); // 1.1 扫描路径:只是个包名,扫描的是java的class文件,而并非源文件,com.lyd.service String path = componentScanAnnotation.value(); // 1.2 将路径文件替换成/的形式 path = path.replace(".","/"); // 1.3 通过类加载器获取资源路径 ClassLoader classLoader = LydApplicationContext.class.getClassLoader(); URL resource = classLoader.getResource(path); // 1.4 转成文件形式,主要是为了获取他的绝对地址 File file = new File(resource.getFile()); // System.out.println(file); if (file.isDirectory()) { // 如果是文件夹 File[] files = file.listFiles(); for (File f : files) { String absolutePath = f.getAbsolutePath(); // 获取绝对路径 System.out.println("绝对路径:" + absolutePath); // 1.5 对编译文件进行处理 if (absolutePath.endsWith(".class")) { // 判断是否为编译文件 /** * 需要拿到的是编译文件,通过类加载器去获取 * 需要将com\lyd\service\UserService转成com.lyd.service.UserService */ String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")); className = className.replace("\\", "."); System.out.println("类名:" + className); try { // 1.6 通过全限定名使用类加载器获取类 (利用反射机制) Class<?> clazz = classLoader.loadClass(className); // 1.7 在通过这个clazz(类)来判断是否有component注解,有则是bean if (clazz.isAnnotationPresent(Component.class)) { // 6 判断是否是并加入beanPostProcessorList,这里不能使用instanceof if (BeanPostProcessor.class.isAssignableFrom(clazz)) { // 直接生成对象 BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance(); // 然后保存进去 beanPostProcessorList.add(instance); } Component annotation = clazz.getAnnotation(Component.class); String beanName = annotation.value(); // * 默认生成bean,如果只使用Component注解,没有写上beanName的值,那么就需要自动生成 if (beanName.equals("")) { // 默认开头小字母 beanName = Introspector.decapitalize(clazz.getSimpleName()); } /** * 這就是一个bean了 * 然而在这里并不是直接就创建bean了,bean分为了单例bean和多例bean */ BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setType(clazz); if (clazz.isAnnotationPresent(Scope.class)) { // 判断是单例还是多例 Scope scope = clazz.getAnnotation(Scope.class); beanDefinition.setScope(scope.value()); } else { beanDefinition.setScope("singleton"); } beanDefinitionMap.put(beanName, beanDefinition); } } catch (Exception e) { e.printStackTrace(); } } } } } // 2 创建单例bean对象 for (String beanName : beanDefinitionMap.keySet()) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition.getScope().equals("singleton")) { // 那么,如何保证单例呢?就需要有个单例池,singletonObjects Object bean = createBean(beanName, beanDefinition); singletonObjects.put(beanName, bean); // 将单例bean存到单例池中 } } }

// 创建bean private Object createBean(String beanName, BeanDefinition definition) { // 利用反射获取实例,采用无参构造方法 Class clazz = definition.getType(); // 通过无参构造方法获取实例 try { Object instance = clazz.getConstructor().newInstance(); // 到这里直接返回,bean的对象也就创建完成

// 3 依赖注入 for (Field field : clazz.getDeclaredFields()) { // 判断字段上是否存在Autowried注解 if (field.isAnnotationPresent(Autowired.class)) { /** * 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。 * 值为 false 则指示反射的对象应该实施 Java 语言访问检查; * 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ; */ field.setAccessible(true); // 反射需要设置这个,不然无法赋值 // 用其属性名,这就意味着private RoleService roleService;roleService不能乱取 field.set(instance, getBean(field.getName())); } } // * instanceof:是针对某个对象去判断是否实现某个类 // 4 Aware回调机制 if (instance instanceof BeanNameAware) { ((BeanNameAware)instance).setBeanName(beanName); }

// 6 BeanPostProcessor 初始化前 AOP 遍历beanPostProcessorList for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postBeforeProcessor(beanName, instance); }

// 5 初始化 if (instance instanceof InitializingBean) { ((InitializingBean)instance).afterPropertiesSet(); }

// 6 BeanPostProcessor 初始化后 AOP for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { instance = beanPostProcessor.postAfterProcessor(beanName, instance); }

return instance;

} catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; }

// 获取bean对象 public Object getBean(String beanName) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition == null) { throw new NullPointerException("找不到bean名字为[" + beanName + "]的bean对象"); } else { // 找到就做相应的操作 String scope = beanDefinition.getScope(); if (scope.equals("singleton")) { // 通过单例池获取 Object bean = singletonObjects.get(beanName); if (bean == null) { // 单例池中如果没有bean,就需要去创建 bean = createBean(beanName, beanDefinition); singletonObjects.put(beanName, bean); } return bean; } else { // 多例的就不需要记录,每次都是通过创建 return createBean(beanName, beanDefinition); } } } }

1|0service

1|0test



public class Test { public static void main(String[] args) { LydApplicationContext applicationContext = new LydApplicationContext(AppConfig.class); // UserService userService = (UserService) applicationContext.getBean("userService"); UserInterface userService = (UserInterface) applicationContext.getBean("userService"); // System.out.println(applicationContext.getBean("userService")); // System.out.println(applicationContext.getBean("roleService")); userService.test(); } }

👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK