Spring源码------手写体验AOP
目录
1、前言
本篇博客并非是对Spring源码的深入研究。过程中主要是体验Spring AOP的过程。那么这篇博客涉及到的知识点大致有以下几点:
- 如何自定义注解,如何通过反射机制去赋予注解强大的功能(说白了,就是体验在反射机制下,注解功能是多么的强大)
- Spring Ioc容器的实现原理
- Spring DI 注解注入
- SpringMVC AOP
- Java反射机制
- Java I/O流(加载配置文件,读取配置文件信息)
- 正则表达式
2、AOP的实现原理
相信大家或多或少的了解过AOP,都知道它是面向切面编程,在网上搜索可以找到很多的解释。这里一句话来总结j就是:AOP是能够让我们在不影响原有功能的前提下,为软件横向扩展功能。 那么横向扩展怎么理解呢,我们在WEB项目开发中,通常都遵守三层原则,包括控制层(Controller)->业务层(Service)->数据层(dao),那么从这个结构下来的为纵向,它具体的某一层就是我们所说的横向。我们的AOP就是可以作用于这某一个横向模块当中的所有方法。
我们在来看一下AOP和OOP的区别:AOP是OOP的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。
接下来介绍一下提到AOP就必须要了解的知识点:
- 切面(Aspect):拦截器类,其中会定义切点以及通知
- 切点(PointCut):具体拦截的某个业务点。
- 通知(Advice):切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:
- 前置通知:@Before 在目标业务方法执行之前执行
- 后置通知:@After 在目标业务方法执行之后执行
- 返回通知:@AfterReturning 在目标业务方法返回结果之后执行
- 异常通知:@AfterThrowing 在目标业务方法抛出异常之后
- 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行
其中SpringAop 的实现主要是通过动态代理思想实现的。主要有两种实现的方式:jdk动态代理和Cglib字节码代理
具体可以阅读我之前写的文章:《设计模式------代理模式详解》
3、核心代码
主要类结构图:
.......
//创建真正的实例对象
private Object instantiateBean(String beanName,ZPSBeanDefinition beanDefinition) {
String className = beanDefinition.getBeanClassName();
Object instance = null;
try {
if(this.factoryBeanObjectCache.containsKey(beanName)){
instance = this.factoryBeanObjectCache.get(beanName);
}else {
Class<?> clazz = Class.forName(className);
//默认的类名首字母小写
instance = clazz.newInstance();
//AOP是在这里开始进行的
//首先需要读取配置文件,然后进行解析
ZPSAdviceSupport config = instantionAopConfig(beanDefinition);
config.setTargetClass(clazz);
config.setTarget(instance);
//判断是否需要生成代理类,如果需要就覆盖原生对象
//如果不需要,就不做任何处理
if(config.pointCutMatch()){
instance = new ZPSJdkDynamicAopProxy(config).getProxy();
}
this.factoryBeanObjectCache.put(beanName, instance);
}
}catch (Exception e){
e.printStackTrace();
}
return instance;
}
.......
/**
* @description: 封装通知Advice信息
* @author: zps
* @create: 2020-05-09 22:51
**/
@Data
public class ZPSAdvice {
private Object aspect;
private Method adviceMethod;
private String throwName;
public ZPSAdvice(Object aspect , Method adviceMethod){
this.aspect = aspect;
this.adviceMethod = adviceMethod;
}
}
/**
* @description: 封装读取到的配置文件信息
* @author: zps
* @create: 2020-05-09 22:54
**/
@Data
public class ZPSAopConfig {
private String pointCut;
private String aspectClass;
private String aspectBefore;
private String aspectAfter;
private String aspectAfterThrow;
private String aspectAfterThrowingName;
}
/**
* @description: AOP配置文件解析工具类
*
* @author: zps
* @create: 2020-05-09 23:02
**/
public class ZPSAdviceSupport {
//从配置文件获取的AOP相关信息
private ZPSAopConfig config;
private Object target;
private Class targetClass;
private Pattern pointCutClassPattern;
//保存被代理方法的通知
//Mehhod:被代理的方法 Map<String , ZPSAdvice> String : 需要织入的通知类型,如before , ZSPAdvice:通知对象
private Map<Method , Map<String , ZPSAdvice>> methodCache;
public ZPSAdviceSupport(ZPSAopConfig config){
this.config = config;
}
private void parse(){
//把Spring的Excpress变成Java能够识别的正则表达式
String pointCut = config.getPointCut()
.replaceAll("\\.", "\\\\.")
.replaceAll("\\\\.\\*", ".*")
.replaceAll("\\(", "\\\\(")
.replaceAll("\\)", "\\\\)");
//保存专门匹配Class的正则
String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
//享元的共享池
methodCache = new HashMap<Method, Map<String, ZPSAdvice>>();
//保存专门匹配方法的正则
Pattern pointCutPattern = Pattern.compile(pointCut);
try {
//获取切面类
Class aspectClass = Class.forName(this.config.getAspectClass());
//保存切面的方法
Map<String , Method> aspectMethods = new HashMap<String, Method>();
for(Method method : aspectClass.getMethods()){
aspectMethods.put(method.getName() , method);
}
for(Method method : this.targetClass.getMethods()){
//将方法化为String
String methodString = method.toString();
if(methodString.contains("throws")){
methodString = methodString.substring(0 , methodString.lastIndexOf("throws")).trim();
}
Matcher matcher = pointCutPattern.matcher(methodString);
if(matcher.matches()){ //若匹配,保存一对一的映射关系
Map<String , ZPSAdvice> advices = new HashMap<String, ZPSAdvice>();
if(!(config.getAspectBefore() == null ||
config.getAspectBefore().equals(""))){
advices.put("before" , new ZPSAdvice(aspectClass.newInstance() ,
aspectMethods.get(config.getAspectBefore())));
}
if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))){
advices.put("after",new ZPSAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfter())));
}
if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))){
ZPSAdvice advice = new ZPSAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfterThrow()));
advice.setThrowName(config.getAspectAfterThrowingName());
advices.put("afterThrow",advice);
}
//跟目标代理类的业务方法和Advices建立一对多个关联关系,以便在Porxy类中获得
methodCache.put(method , advices);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
//根据一个目标代理类的方法,获得其对应的通知
public Map<String,ZPSAdvice> getAdvices(Method method, Object o) throws Exception {
//享元设计模式的应用
Map<String,ZPSAdvice> cache = methodCache.get(method);
if(null == cache){
Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
cache = methodCache.get(m);
this.methodCache.put(m,cache);
}
return cache;
}
//判断是否需要生成代理类
public boolean pointCutMatch() {
return pointCutClassPattern.matcher(this.targetClass.toString())
.matches();
}
public void setTargetClass(Class<?> clazz) {
this.targetClass = clazz;
parse();
}
public void setTarget(Object instance) {
this.target = instance;
}
public Class getTargetClass() {
return targetClass;
}
public Object getTarget() {
return target;
}
}
**
* @description: AOP工具类:主要是解析配置文件
* @author: zps
* @create: 2020-05-09 22:59
**/
public class ZPSJdkDynamicAopProxy implements InvocationHandler {
private ZPSAdviceSupport config;
public ZPSJdkDynamicAopProxy(ZPSAdviceSupport config) {
this.config = config;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader() , this.config
.getTargetClass().getInterfaces() , this);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Map<String, ZPSAdvice> advices = config.getAdvices(method,null);
Object returnValue;
try {
invokeAdivce(advices.get("before"));
returnValue = method.invoke(this.config.getTarget(),objects);
invokeAdivce(advices.get("after"));
}catch (Exception e){
invokeAdivce(advices.get("afterThrow"));
throw e;
}
return returnValue;
}
private void invokeAdivce(ZPSAdvice advice) {
try {
advice.getAdviceMethod().invoke(advice.getAspect());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
运行结果:
4、总结
手写体验Spring总结如下:
Spring初始化的大致流程:IOC—>AOP—>DI—>MVC
- IOC(控制反转)
1、创建整个容器的主体ApplicationContext。
2、BeanDefinitionReader读取配置文件(properties、xml、yml),扫描相关的类,将配置信息存储到BeanDefinition。
3、初始化IOC容器,将实例化对象存储到BeanWrapper。
4、ApplicationContext getBean()采用的单例bean,容器式(Lazy)延时加载。
- AOP(面向切面编程)
1、getBean()—>instantiateBean() 判断是否要生成Proxy代理对象。
(1)加载AOP的配置文件,调用instantionAopConfig(beanDefinition),生成AdvisedSupport。
AdvisedSupport Map<Method,Map<String,GPAdvice>> methodCache存储了目标代理类的业务方法和Advices建立的 一对多的关联关系。
AdvisedSupport getAdvices()可根据一个目标代理类的方法,获得其对应的通知。
(2)判断规则,要不要生成代理类GPJdkDynamicAopProxy,如果要就覆盖原生对象,如果不要就不做任何处理,返回原 生对象。
- DI(依赖注入)
1、getBean()—>instantiiateBean()用反射初始化Bean对象。
2、populateBean()完成依赖注入,用反射注入。
3、循环依赖注入:用缓存去解决依赖注入的问题。
- MVC
1、DisparcherServlet init()初始化9大组件。
2、initStrategers()初始化HandlerMapping,HandlerAdapter,ViewResolver。
(1)一个HandlerMapping对应一个HandlerAdapter。
(2)ViewResolver涉及模板,一个ModelAndView关联一个ViewResolver。
3、根据HandlerAdapter得到一个ModelAndView。
(1)如不返回页面则直接responser.getWriter().write()输出到浏览器上。
(2)如返回页面,启用模板引擎生成一个View,通过View render()渲染(即:读取模板文件内容,用正则替换占位符),最后输出到浏览器上。