引言:前面我们已经用代码简单实现了Spring基于注解配置的程序
下载地址:https://download.csdn.net/download/2401_83418369/90540102
手写简单的Spring基于注解配置的程序-CSDN博客
需求:
1、原生 Spring 如何实现依赖注入和 singleton 、prototype
2、Spring 底层 如何实现 Bean 后置处理器机制
3、原生 Spring 是如何实现 AOP
目标:
不用 Spring 框架, 模拟 Spring 底层实现, 也能完成相同的功能
Spring 整体架构分析:
配置环境:
先创建一个Maven项目,在原来的项目下面新建一个Maven模块
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.8</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.8</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.1</version><scope>compile</scope></dependency></dependencies>
实现1: 编写自己的 Spring 容器,实现扫描包,得到 bean的Class 对象
分析思路:
结合前面已经实现了 Spring基于注解配置的程序,所以这里可以直接复制一下前面的代码,修改的点有:
1、注入的bean对象全部都用@Component修饰,所以增加自定的注解@Component
代码如下:
编写自定义注解ComponentScan
package com.study.Spring.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 相当于扫描配置属性Component-Scan**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}
编写自定义注解Component
package com.study.Spring.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 这个注解相当于@Component组件注解,可以自定义bean的id名字*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {String value() default "";
}
编写扫描的配置类
package com.study.Spring.ioc;import com.study.Spring.annotation.ComponentScan;/*** 这个文件的作用相当于配置信息Component-scan*/
@ComponentScan("com.study.Spring.component")
public class SpringConfig {
}
编写要注入的bean对象UserDao
package com.study.Spring.component;import com.study.Spring.annotation.Component;@Component("userDao")
public class UserDao {
}
编写要注入的bean对象 UserService
package com.study.Spring.component;import com.study.Spring.annotation.Component;
import com.study.Spring.annotation.Scope;@Component("userService")
public class UserService {
}
编写类MySpringApplicationContext充当ioc容器
package com.study.Spring.ioc;import com.study.Spring.annotation.Component;
import com.study.Spring.annotation.ComponentScan;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;public class MySpringApplicationContext {//容器的属性:MySpringConfig是配置类的Class对象,用于反射创建注解读取类名//ioc属性相当于原生的SingleTonObjects用于存储单例bean对象private Class MySpringConfig;public MySpringApplicationContext(Class mySpringConfig) {beanDefinitionByScan(mySpringConfig);}//这是扫描beanDefinition的方法public void beanDefinitionByScan(Class mySpringConfig){this.MySpringConfig = mySpringConfig;//反射获取自定义的注解对象,再获取注解中的包名ComponentScan componentScan = (ComponentScan) MySpringConfig.getDeclaredAnnotation(ComponentScan.class);String path = componentScan.value();path = path.replace(".", "/");//根据包名获取实际工作目录的路径,再通过io流获取扫描的类的绝对路径
//file:/C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component//这里拿到类加载器用于获取实际工作目录ClassLoader classLoader = MySpringConfig.getClassLoader();URL realPath = classLoader.getResource(path);
// /C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/componentString filePath = realPath.getFile();File file = new File(filePath);//通过目录获取目录下的所有Class文件if (file.isDirectory()) {File[] files = file.listFiles();for (File item : files) {String name = item.getName();if (name.contains(".class")) {//通过字符串的拼接获取包下面的全类名path = path.replace("/", ".");String className = name.substring(0, name.indexOf("."));//获取全类名String fullPath = path + "." + className;
// 再根据Class文件进行一次筛选:判断是否有四种注解的类才进行创建实例对象并存储在容器中try {//通过classloader获取的Class对象时轻量级的,不会初始化时调用静态方法Class<?> aClass = classLoader.loadClass(fullPath);//根据不同类的Class对象判断是否有指定的注解if (aClass.isAnnotationPresent(Component.class)) {System.out.println("要注入的bean对象"+aClass);}else {System.out.println("该Class没有声明注解");}} catch (Exception e) {throw new RuntimeException(e);}}}}}
编写测试类
package com.study.Spring;import com.study.Spring.ioc.MySpringApplicationContext;
import com.study.Spring.ioc.SpringConfig;public class MySpringTest {public static void main(String[] args) {MySpringApplicationContext ioc = new MySpringApplicationContext(SpringConfig.class);System.out.println("ok");}
}
结果:
实现2:扫描将 bean 信息封装到 BeanDefinition 对象,并放入到 BeanDefinitionMap
分析思路:
切入点:判断是否存在注解@Component的条件下
beanDefinition要封装的信息有scope和Class对象,又因为BeanDefinition 这个bean对象又要存储到BeanDefinitionMap中,所以还需要获取Map的id(即:原生BeanDefinitionMap的id默认是首字母小写的对象名)但是如果自己声明了id,就是自己写的id
所以首先判断的是有没有自定义component属性value,这里先打通一条线(默认有该属性的情况),还需要判断是否制定了scope的属性,如果有就获取该属性并赋值到BeanDefinition 如果没有就设置该BeanDefinition的scope属性是singleton,因为默认没有声明注解@Scope的属性value的情况下是单例对象
新增自定义注解Scope:
package com.study.Spring.annotation;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 "";
}
MySpringApplicationContext类:
package com.study.Spring.ioc;import com.study.Spring.annotation.Component;
import com.study.Spring.annotation.ComponentScan;
import com.study.Spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;/***这个类相当于容器**/public class MySpringApplicationContext {//容器的属性:MySpringConfig是配置类的Class对象,用于反射创建注解读取类名//ioc属性相当于原生的SingleTonObjects用于存储单例bean对象private Class MySpringConfig;private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap=new ConcurrentHashMap<>();private ConcurrentHashMap<String,Object> singletonObjects=new ConcurrentHashMap<>();public MySpringApplicationContext(Class mySpringConfig) {beanDefinitionByScan(mySpringConfig);}//这是扫描beanDefinition的方法public void beanDefinitionByScan(Class mySpringConfig){this.MySpringConfig = mySpringConfig;//反射获取自定义的注解对象,再获取注解中的包名ComponentScan componentScan = (ComponentScan) MySpringConfig.getDeclaredAnnotation(ComponentScan.class);String path = componentScan.value();path = path.replace(".", "/");//根据包名获取实际工作目录的路径,再通过io流获取扫描的类的绝对路径
//file:/C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/component//这里拿到类加载器用于获取实际工作目录ClassLoader classLoader = MySpringConfig.getClassLoader();URL realPath = classLoader.getResource(path);
// /C:/Users/DELL/IdeaProjects/Spring/out/production/Spring/com/study/Spring/componentString filePath = realPath.getFile();File file = new File(filePath);//通过目录获取目录下的所有Class文件if (file.isDirectory()) {File[] files = file.listFiles();for (File item : files) {String name = item.getName();if (name.contains(".class")) {//通过字符串的拼接获取包下面的全类名path = path.replace("/", ".");String className = name.substring(0, name.indexOf("."));//获取全类名String fullPath = path + "." + className;
// 再根据Class文件进行一次筛选:判断是否有四种注解的类才进行创建实例对象并存储在容器中try {//通过classloader获取的Class对象时轻量级的,不会初始化时调用静态方法Class<?> aClass = classLoader.loadClass(fullPath);//根据不同类的Class对象判断是否有指定的注解if (aClass.isAnnotationPresent(Component.class)) {//先将beanDefinitionMap的id拿到//这里通过反射获取Component注解,根据注解再取得value,即自己指定的idComponent component = aClass.getDeclaredAnnotation(Component.class);String id = component.value();//如果没有设置Component的属性值,那么就使用首字母小写的类名代替if ("".equals(id)){id = StringUtils.uncapitalize(className);}BeanDefinition beanDefinition = new BeanDefinition();//不变的量beanDefinition.setClazz(aClass);if (aClass.isAnnotationPresent(Scope.class)){//存在Scope scope = aClass.getDeclaredAnnotation(Scope.class);beanDefinition.setScope(scope.value());}else {//不存在beanDefinition.setScope("singleton");}//存放到map中beanDefinitionMap.put(id,beanDefinition);}else {System.out.println("该Class没有声明注解");}} catch (Exception e) {throw new RuntimeException(e);}}}}}}
实现3:初始化 bean 单例池,并完成 getBean方法 , createBean 方法
分析思路:
在MySpringApplicationContext类中添加方法createBean,该方法用于创建对象,然后在初始化容器时,根据BeanDefinitionMap中的Scope情况,进行懒加载还是预加载,默认情况下单例对象是在容器初始化时完成初始化的。
在MySpringApplicationContext类中添加方法:
public Object createBean(BeanDefinition beanDefinition){//先获取beanDefinition中的Class对象Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();return instance;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}
MySpringApplicationContext类构造器
public MySpringApplicationContext(Class mySpringConfig) {beanDefinitionByScan(mySpringConfig);//这里拿到Map里面所有键的集合,通过遍历可以得到所有Bean的信息Enumeration<String> keys = beanDefinitionMap.keys();while (keys.hasMoreElements()){String id = keys.nextElement();BeanDefinition beanDefinition = beanDefinitionMap.get(id);//将单例Bean对象进行创建对象,并放入singletonObjects容器if ("singleton".equals(beanDefinition.getScope())){Object bean = createBean(beanDefinition);singletonObjects.put(id,bean);}}}
实现4:完成依赖注入
思路分析:
依赖注入有两种方式,一种是通过配置文件注入依赖,还有一种是通过注解来注入依赖,通过注解@Autowired注入依赖是一种自动注入依赖的方式,这里需要在创建Bean对象时增加注入依赖的代码即在createBean方法中增加注入依赖的代码,那么在哪里增加代码呢?注入依赖其实就是给对象设置属性,所以应该在创建好对象之后就可以进行依赖注入
代码如下:
Field[] declaredFields = clazz.getDeclaredFields();for (Field declaredField : declaredFields) {//遍历所有字段,并判断是否有Autowired注解修饰if (declaredField.isAnnotationPresent(Autowired.class)){String name = declaredField.getName();//根据注入的名字进行创建Bean对象,并将该Bean设置到该字段Object bean = getBean(name);//爆破declaredField.setAccessible(true);//给字段赋值,相当于注入依赖declaredField.set(instance,bean);}}
实现5:bean 后置处理器实现
思路分析:
根据原生的后置处理器对象需要实现BeanPostProcessor接口,所以可以根据原生的接口,自己实现两个重要的方法,然后再新建一个对象实现该接口
实现接口BeanPostProcessor
package com.study.Spring.process;/*** 这是自己写的后置处理器* 主要有两个方法*/
public interface BeanPostProcessor {default Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}default Object postProcessAfterInitialization(Object bean, String beanName) {return bean;}
}
编写MyBeanPostProcessor 类实现该接口BeanPostProcessor
package com.study.Spring.component;import com.study.Spring.annotation.Component;
import com.study.Spring.process.BeanPostProcessor;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("后置处理器的before方法被调用,Bean对象是"+bean);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器的after方法被调用,Bean对象是"+bean);return bean;}
}
后置处理器相当于一个组件,所以在beanDefinitionByScan类中判断如果有@Component注解就可以进行下一步的判断,如果存在该类实现了BeanPostProcessor,就相当于一个后置处理器对象,因为后置处理器对象可以有多个,这里直接将该对象放入list集合中
private List<BeanPostProcessor> beanPostProcessors=new ArrayList<>();
因为class是静态对象所以不能使用instance of方法判断该对象是否实现了该BeanPostProcessor接口,应该使用isAssignableFrom方法
//为了简化代码,后置处理器本身是一个组件//原生的容器是将后置处理器当做组件通过createBean和getBean//因为class是静态对象所以不能使用instance of方法判断该对象是否// 实现了该BeanPostProcessor接口if(BeanPostProcessor.class.isAssignableFrom(aClass)){BeanPostProcessor instance =(BeanPostProcessor) aClass.newInstance();beanPostProcessors.add(instance);}
因为后置处理器的before方法和after方法是在init初始化方法调用前和调用后执行,所以这里还需要在createBean方法中的init方法的前后加入before和after方法
//在初始化方法调用前调用before方法for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {instance = beanPostProcessor.postProcessBeforeInitialization(instance, "...");}
//在初始化方法调用后调用after方法for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {instance = beanPostProcessor.postProcessAfterInitialization(instance, "...");}
最后在测试类中调试得到singleton中也有后置处理器对象,因为在原生的容器中,后置处理器是被当作一个主键 经历createBean和getBean方法,相当于将该后置处理器对象存储在singleton对象中然后再从该对象取出进行调用,但是现在为了方便调用后置处理器的方法,就直接将该对象放入list集合中,并在createBean方法中的初始化方法前后调用before和after方法
所以增加一个continue跳过后边的代码
这样在singletonObjects中就没有该实现的对象了
测试:新建一个类Car使用@Component自动注入依赖,然后再实现InitializingBean接口
package com.study.Spring.component;import com.study.Spring.annotation.Component;
import com.study.Spring.process.InitializingBean;@Component
public class Car implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("car的初始化方法。。。");}
}
结果:发现后置处理器能够生效
后置处理器的作用相当于是aop思想,只要实现了Bean对象的创建就会调用before和after方法,相当于该方法作用于多个对象,如果想单独对某个对象进行处理,也可以使用instance of来筛选从而进行判断
解决问题:当后置处理器对象调用的方法返回的结果是空时怎么办呢?
原生的后置处理器即使你返回的结果是空,他也不影响原来的初始化的对象
所以需要在调用before和after方法的方法体中增加判断条件
实现6:AOP 机制实现
思路分析:
这里就采用硬编码方式:(后期再更新灵活方式)
编写一个接口Cal:
package com.study.Spring.aop;public interface Cal {void sum(int num);
}
编写一个实现类MyCal:
package com.study.Spring.aop;import com.study.Spring.annotation.Component;@Component
public class MyCal implements Cal{@Overridepublic void sum(int num) {int result = 0;for (int i = 0; i <= num; i++) {result += i;}System.out.println("累加的总和:" + result);}
}
编写一个切面类CalAspect:
package com.study.Spring.aop;import com.study.Spring.annotation.Component;@Component
public class CalAspect {public static void showLog(){System.out.println("前置通知。。。。");}public static void showResult(){System.out.println("返回通知。。。。");}
}
MyBeanPostProcessor 类:
package com.study.Spring.component;import com.study.Spring.annotation.Component;
import com.study.Spring.aop.CalAspect;
import com.study.Spring.process.BeanPostProcessor;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("后置处理器的before方法被调用,Bean对象是"+bean);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器的after方法被调用,Bean对象是"+bean);//这里相当于是切入点表达式的匹配方法类if ("myCal".equals(beanName)){Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object invoke = null;if ("sum".equals(method.getName())) {CalAspect.showLog();invoke = method.invoke(bean, args);CalAspect.showResult();} else {invoke = method.invoke(bean, args);}return invoke;}});//这里返回代理对象return proxyInstance;}//没有匹配的类就直接返回原来的对象return bean;}
}