
一.前言spring是Java中庞大的框架但我们大多数人在使用的时候都是基于API层的调用很少从底层理解这个框架。这篇文章主要会实现spring中的BeanDefinition容器和singleton容器的管理依赖注入后置处理器BeanPostPeocessor的机制和简单的AOP实现。配置方式主要是基于注解的配置这篇文章学完后会对spring有更深的理解前置知识反射和注解集合spring框架的基本使用动态代理二.项目的搭建该项目使用maven完成。不在导入spring所需要的依赖导入基本的依赖完成spring框架的实现。需要的依赖:dependencies !--测试工具-- dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.1/version scopetest/scope /dependency !--工具类-- dependency groupIdcommons-lang/groupId artifactIdcommons-lang/artifactId version2.6/version /dependency /dependencies项目的包结构三.项目的实现1.创建entity创建实例类来注入到后续的spring容器中先创建暂时还没有业务。2.创建自己的spring容器任务创建自己的spring实现扫描包得到bean的class对象2.1 容器的配置信息思路:采用注解config类的方式模拟bean.xml文件在注解中标明扫描的包编写注解 ComponentScan——标明扫描包package mini_spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //标明注解的作用范围 Target(value ElementType.TYPE) //标明注解的生命周期 Retention(value RetentionPolicy.RUNTIME) public interface ComponentScan { String value() default ;//扫描的包 }完成MySpringConfig类用ComponentScan修饰指定容器的配置信息package mini_spring.ioc; import mini_spring.annotation.ComponentScan; //指明扫描的包 ComponentScan(value mini_spring.component) public class MySpringConfig { }2.2 创建容器类类似于spring框架中的ioc容器package mini_spring.ioc; public class MySpringApplicationContext { //通过MySpringConfig得到配置信息 private Class configClass; public MySpringApplicationContext(Class configClass) { this.configClass configClass; } }2.3 得到扫描包文件MySpringConfig有注解ComponentScan修饰可以从中得到包名在容器的构造器中实现。//得到注解ComponentScan ComponentScan componentScan (ComponentScan) this.configClass.getAnnotation(ComponentScan.class); String packagePath componentScan.value();//得到了扫描的包名 System.out.println(packagePath packagePath); //得到类加载器用于扫描文件 ClassLoader classLoader MySpringApplicationContext.class.getClassLoader(); //在得到包的资源路径的形式是mini_spring/component所以需要替换 packagePathpackagePath.replace(.,/); //得到资源路径 URL resource classLoader.getResource(packagePath); System.out.println(resource resource); //得到包的文件 File packageFile new File(resource.getFile());在测试类中运行 的确得到了包文件2.4 得到类路径得到需要扫描的包中的类路径//开始扫描包 if(packageFile.isDirectory()){ File[] files packageFile.listFiles(); for (File file : files) { System.out.println(); //需要得到类路径用于反射创建实例对象 String absolutePath file.getAbsolutePath(); System.out.println(absolutePath absolutePath); if(absolutePath.endsWith(.class)){//如果是类文件 //反射所需要得到的类路径是mini_spring.component.UserService, //所以需要处理absolutePath String className absolutePath.substring(absolutePath.lastIndexOf(\\) 1, absolutePath.indexOf(.class)); System.out.println(className className); String classFullPath packagePath.replace(/,.) . className; System.out.println(classFullPath classFullPath);运行结果得到了类的路径这样我们可以通过反射创建对象了2.5 创建component注解说明这里只以component注解为例可以自行添加其他注解该注解可以添加到类上经过扫描注入到ioc容器中package mini_spring.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; Target(value ElementType.TYPE) Retention(value RetentionPolicy.RUNTIME) public interface Component { String value() default ; }创建完注解后引入之前创建的entity类使其可以被扫描下面只是其中一个注意在Entity类上不标明用于区分。package mini_spring.component; import mini_spring.annotation.Component; Component public class UserAction { }2.6 识别bean将有注解Component 的类识别出来。try { Class? clazz classLoader.loadClass(classFullPath); //根据有无注解判断是否为bean在根据后续的业务注入 if(clazz.isAnnotationPresent(Component.class)){ System.out.println(className 是一个bean); }else { System.out.println( className 不是一个bean); } } catch (ClassNotFoundException e) { e.printStackTrace(); }运行检验得出有component注解修饰的都已被识别为bean。3.BeanDefinition容器的实现我们首先要明白BeanDefinition容器中储存的是需要注入ioc容器中的类信息这里并没有创建实例需要使用类信息利用反射创建对象。我们使用一个类BeanDefinition在声明需要注入类的信息将BeanDefinition对象放入Map中模拟BeanDefinition容器。3.1 创建BeanDefinition类思考这个类中需要有那些属性类信息和作用域在扫描时将bean的信息封装到BeanDefinition的对象中public class BeanDefinition { private Class clazz;//类信息 private String scope;//作用域 }篇幅有限需要有getter和setter方法3.2 创建作用域注解scope注解用于标明类的作用域(单例or多例)value有prototype和singleton两种Target(value ElementType.TYPE) Retention(value RetentionPolicy.RUNTIME) public interface Scope { String value(); }创建完注解后在UserDao上标明注解使其为多例单例不用标明后面进行识别Component Scope(prototype) public class UserDao { }3.3 创建BeanDefinition容器创建ConCurrentHashMap容器放入beanDefinition的对象String表示beanName这个属性是在MySpringApplicationContext中的private ConcurrentHashMapString,BeanDefinition beanDefinitionMap new ConcurrentHashMap();3.4 装载beanDefinitionMap向beanDefinitionMap中注入对象if(clazz.isAnnotationPresent(Component.class)){ System.out.println(className 是一个bean); Component component clazz.getAnnotation(Component.class); String beanName component.value();//得到beanName BeanDefinition beanDefinition new BeanDefinition(); if(clazz.isAnnotationPresent(Scope.class)){//如果有scope则判断内容 Scope scope clazz.getAnnotation(Scope.class); String scopeVal scope.value(); //如果Component没有指明value则使用类名首字母小写作为beanName if(.equals(beanName)){ //调用库方法实现首字母小写 beanName StringUtils.uncapitalize(className); } beanDefinition.setScope(scopeVal);//向beanDefinition加值 }else {//如果没有scope默认为单例 beanDefinition.setScope(singleton); } beanDefinition.setClazz(clazz); beanDefinitionMap.put(beanName,beanDefinition); }else { System.out.println( className 不是一个bean); }需要理解的是一个beanDefinition对象对应一个entity注入到beanDefinitionMap后就是每个entity类的信息和作用域都被保存下来了测试结果可以发现beanDefinitionMap中已经保存了相应信息3.5 整合将扫描包初始化beanDefinition的过程整合到一个方法中在构造器中调用方法中的代码就是上述代码复制粘贴。篇幅过长不展示。public MySpringApplicationContext(Class configClass) { beanDefinitionsByScan(configClass); }4.singleTon的初始化4.1 createBean()方法根据beanDefinition中存放的类信息创建对象这里可以体现为什么需要无参构造器private Object createBean(BeanDefinition beanDefinition){ //从beanDefinition中得到类信息 Class clazz beanDefinition.getClazz(); try { //利用无参构造器创建对象 Object instance clazz.getDeclaredConstructor().newInstance(); return instance; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; }4.2 singleTon容器创建singleTon容器储存的是单例对象如果使用单例对象时直接从该容器中获取。public ConcurrentHashMapString,Object singleTon new ConcurrentHashMap();单例对象都存放在该singleTon中4.3 初始化singleTon我们需要在beanDefinitionMap容器创建完毕之后才可以得到所有类信息之后才可以创建singleTon容器public MySpringApplicationContext(Class configClass) { beanDefinitionsByScan(configClass); EnumerationString keys beanDefinitionMap.keys(); //将beanDefinitionMap中的元素取出用于创建对象 while (keys.hasMoreElements()){ //得到beanName String beanName keys.nextElement(); //根据beanName去除类信息 BeanDefinition beanDefinition beanDefinitionMap.get(beanName); //如果是单例则放入singleTon中 if(singleton.equalsIgnoreCase(beanDefinition.getScope())){ //调用createBean方法创建对象 Object bean createBean(beanDefinition); singleTon.put(beanName,bean); } } }初始化后我们测试运行结果发现singleTon中已经装入实例对象了4.4 getBean()方法通过beanName从ioc容器中获取对象如果是单例则从singleTon容器中获取如果不是单例则重新创建。public Object getBean(String beanName){ //判断是否有该bean if(beanDefinitionMap.containsKey(beanName)){ BeanDefinition beanDefinition beanDefinitionMap.get(beanName); //如果是单例 if(singleton.equalsIgnoreCase(beanDefinition.getScope())){ //从singleTon中获取 return singleTon.get(beanName); }else { //如果不是单例重新创建 return createBean(beanDefinition); } }else { throw new NullPointerException(beanName is not found); } }测试getBean方法可以发现有scope修饰的UseDao的实例每次获取的对象都是不一样的而单例对象UseAction和UserService获取的对象和singleTon中是一样的。5.实现依赖注入5.1 AutoWired注解在spring中通常使用AutoWired注解来完成自动装配会根据相应的规则实现。创建AutoWired注解需要作用在属性上ElementType.FIELD//需要作用在属性上 Target({ElementType.FIELD,ElementType.METHOD}) Retention(value RetentionPolicy.RUNTIME) public interface AutoWired { }5.2 依赖注入设计在UserAction类中添加属性为UserService并创建相应的测试方法。Component(value userAction) public class UserAction { //添加注解 AutoWired private UserService userService; public void show1(){ System.out.println(自动装配完成); userService.show(); } public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService userService; } } Component(value userService) public class UserService { public void show(){ System.out.println(UserService的show()); } }5.3 完成依赖注入依赖注入是在创建实例之后扫描属性是否存在AutoWired注解然后进行装配作者思考历程第一阶段:在创建好bean之后直接进行专配。代码如下try { //利用无参构造器创建对象 Object instance clazz.getDeclaredConstructor().newInstance(); //在创建好对象后进行依赖注入 //扫描该类的所有属性 for (Field filed:clazz.getDeclaredFields()){ //如果有AutoWired修饰 if(filed.isAnnotationPresent(AutoWired.class)){ //通过属性名实现在ioc容器中查找bean进行装配 Object bean getBean(filed.getName()) //是私有属性需要破解 filed.setAccessible(true); //进行装配给该属性赋值 filed.set(instance,bean); } } return instance;思考有什么问题我们知道我们在在调用 Object bean getBean(filed.getName())时如果是单例的话是直接从singleTon中获取对象。这时问题来了如果我们需要注入的bean还没有被创建到singleTon容器中怎么办呢果不其然在调用测试方法时直接报了空指针异常....这里的原因就是UserService对象此时还没有被装到singleTon容器中获得的就是null继续改进第二阶段如果Object bean getBean(filed.getName()) 得到的是null我们直接设计先创建需要进行装配的对象UserService。代码如下Class clazz beanDefinition.getClazz(); try { //利用无参构造器创建对象 Object instance clazz.getDeclaredConstructor().newInstance(); //在创建好对象后进行依赖注入 //扫描该类的所有属性 for (Field filed:clazz.getDeclaredFields()){ //如果有AutoWired修饰 if(filed.isAnnotationPresent(AutoWired.class)){ //通过属性名实现在ioc容器中查找bean进行装配 Object bean getBean(filed.getName()); //如果是null说明还没有在singTon中直接创建 if(beannull){ beancreateBean(beanDefinitionMap.get(filed.getName())); } //是私有属性需要破解 filed.setAccessible(true); //进行装配给该属性赋值 filed.set(instance,bean); } } return instance;此时有什么问题发现的确没有空指针报错了也可以调用方法。但是我们知道在自动装配的时候是从容器中扫描获取的如果我们直接调用并没有将创建的bean放入到singleTon容器中这样我们获得的就不是单例的对象。我们测试查看发现的确不是同一个对象这样设计也不符合spring容器。第三阶段既然在创建过程中不能实现我们在初始化singleTon容器后执行//此时的singleTon容器已经初始化完毕了 EnumerationString keys1 singleTon.keys(); //直接扫描该容器 while (keys1.hasMoreElements()){ String beanName keys1.nextElement(); Object instance singleTon.get(beanName); Field[] declaredFields instance.getClass().getDeclaredFields(); //进行相同的业务 for (Field field : declaredFields) { if(field.isAnnotationPresent(AutoWired.class)){ Object bean getBean(field.getName()); field.setAccessible(true); try { field.set(instance,bean); } catch (IllegalAccessException illegalAccessException) { illegalAccessException.printStackTrace(); } } } }这样可以实现直接从singleTon容器中获取对象而且得到的还是同一个这里并不是采用原生的方式因为缓存机制的实现太复杂。这个方法的话会造成遍历时间的浪费我们可以简单理解。产生这个现象的原因是我们是边创建bean边初始化singleTon容器的这样就是有顺序的影响如果注入的bean不是单例的我们就可以直接使用方案1而方案3的话无论怎样都可以只是会有时间的损耗。原生的spring并不是采用作者的方案实现的而是使用一种缓存机制实现的这里不讲解。6.后置处理器的实现我们知道后置处理器的方法是在初始化方法的前后执行的我们就先完成初始化方法。6.1 初始化方法的设计需要初始化的类都需要实现一个接口InitializingBean,接口中可以指定初始化方法public interface InitializingBean { void afterPropertiesSet() throws Exception; }需要初始化的类实现该接口即可Component(value userService) public class UserService implements InitializingBean { public void show(){ System.out.println(UserService的show()); } Override public void afterPropertiesSet() throws Exception { System.out.println(UserService初始化完成afterPropertiesSet()); } }实现该接口后需要在完成属性设置之后调用该初始化方法所以就在依赖注入后调用//判断该instance是否实现了接口 if(instance instanceof InitializingBean){ try { //强转并调用初始化方法 ((InitializingBean) instance).afterPropertiesSet(); } catch (Exception e) { e.printStackTrace(); } }测试方法发现已经完成初始化6.2 后置处理器的设计先创建BeanPostProcessor接口实现该接口的类就是后置处理器可以有多个后置处理器public interface BeanPostProcessor { default Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } default Object postProcessAfterInitialization(Object bean, String beanName) { return bean; } }实现类 该类需要被注入到容器所以需要添加注解Component public class MyBeanPostProcessor implements BeanPostProcessor { Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println(postProcessBeforeInitialization()被调用..); return bean; } Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println(postProcessAfterInitialization被调用...); return bean; } }6.3 后置处理器的实现为了方便处理所有的后置处理器我们根据是否实现BeanPostProcessor 接口将后置处理器放入到集合中类似于一个容器private ListBeanPostProcessor beanPostProcessorListnew ArrayList();我们在将bean注入ioc容器时判断将后置处理器装入到集合中//判断clazz是否为BeanPostProcessor的实现类 if(BeanPostProcessor.class.isAssignableFrom(clazz)){ try { //如果是将BeanPostProcessor的实例beanPostProcessorList 中统一调用 BeanPostProcessor beanPostProcessor (BeanPostProcessor) clazz.newInstance(); beanPostProcessorList.add(beanPostProcessor); }catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }注入到容器之后我们在初始化方法前后执行后置处理器中的方法//进行前置处理从容器中取出对象 for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { Object obj; objbeanPostProcessor.postProcessBeforeInitialization(instance,beanName); if(obj!null){ instanceobj; } } //判断该instance是否实现了接口 if(instance instanceof InitializingBean){ try { //强转并调用初始化方法 ((InitializingBean) instance).afterPropertiesSet(); } catch (Exception e) { e.printStackTrace(); } } //进行后置处理从容器中取出对象 for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { Object obj; objbeanPostProcessor.postProcessAfterInitialization(instance,beanName); if(obj!null){ instanceobj; } }完成后发现的确完成后置处理器的功能7.AOP的简单实现7.1 AOP机制的设计在AOP编程中我们调用方法实际调用的是动态代理对象的方法。我们是在什么时候将对象转成动态代理的呢这就与我们的后置处理器相关我们在后置处理中如果判断出需要使用AOP机制我们就会引入动态代理对象。动态代理所需要的接口和类public interface ShowIn { void show(); } import mini_spring.BeanProcessor.InitializingBean; import mini_spring.annotation.Component; Component(value userService) public class UserService implements InitializingBean ,ShowIn{ Override public void show(){ System.out.println(UserService的show()); } Override public void afterPropertiesSet() throws Exception { System.out.println(UserService初始化完成afterPropertiesSet()); } }7.2 AOP的实现这里只进行简单的实现属于硬编码模式因为对切面类切面方法的判断只是对反射的应用与AOP机制没有太大的关系在后置处理器中实现代理对象的转化public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println(postProcessAfterInitialization被调用... beanName); //这里是硬编码 //如果这个类是需要使用切面编程的类可以通过反射识别出来这里简化指定为userService if (userService.equals(beanName)) { //创建代理对象 Object proxyInstance Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method method.getName()); Object result null; //假如我们进行前置通知返回通知 处理的方法是show //后面可以通过注解来做的更加灵活 if (show.equals(method.getName())) { System.out.println(前置通知); result method.invoke(bean, args);//执行目标方法 System.out.println(返回通知); //进行返回通知的处理 } else { //如果不是需要切入的方法就直接执行不用采用代理对象执行 result method.invoke(bean, args);//执行目标方法 } return result; } }); //如果bean是需要返回代理对象的, 这里就直接return proxyInstance return proxyInstance; } return bean; }在容器创建时体现//进行前置处理从容器中取出对象 for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { Object obj; objbeanPostProcessor.postProcessBeforeInitialization(instance,beanName); if(obj!null){ instanceobj; } singleTon.put(beanName,instance); } //判断该instance是否实现了接口 if(instance instanceof InitializingBean){ try { //强转并调用初始化方法 ((InitializingBean) instance).afterPropertiesSet(); } catch (Exception e) { e.printStackTrace(); } } //进行后置处理从容器中取出对象 for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) { Object obj; objbeanPostProcessor.postProcessAfterInitialization(instance,beanName); if(obj!null){ instanceobj; } singleTon.put(beanName,instance); }运行测试发现已经变为代理对象并且是代理对象执行的方法四.项目的总结该项目简单的实现了AOP的机制与原生的spring仍有很大的区别但对我们简单了解spring执行流程有很大的帮助在实现AOP机制我们采用了一些硬编码的形式这里可以自行进行完善希望这篇文章对大家有所帮助