引言:前面我们已经用代码简单实现了Spring基于注解配置的程序
下载地址:https://download.youkuaiyun.com/download/2401_83418369/90540102
手写简单的Spring基于注解配置的程序-优快云博客
需求:
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/component
String 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/component
String 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,即自己指定的id
Component 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 {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("后置处理器的before方法被调用,Bean对象是"+bean);
return bean;
}
@Override
public 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 {
@Override
public 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{
@Override
public 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 {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("后置处理器的before方法被调用,Bean对象是"+bean);
return bean;
}
@Override
public 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() {
@Override
public 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;
}
}