架构探险笔记5-使框架具备AOP特性(下)
开发AOP框架
借鉴SpringAOP的风格,写一个基于切面注解的AOP框架。在进行下面的步骤之前,确保已经掌了动态代理技术。
定义切面注解
/*** 切面注解*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Aspect {/*注解*/Class<? extends Annotation> value(); }
通过@Target(ElementType.TYPE)来设置该注解只能应用在类上。该注解中包含一个名为value的属性,它是一个注解类,用来定义Controller这类注解。
在使用切面注解之前,我们需要先搭建一个代理框架。
搭建代理框架
继续在框架中添加一个名为Proxy的接口
/*** 代理接口*/ public interface Proxy {/*** 执行链式代理*/Object doProxy(ProxyChain proxyChain) throws Throwable; }
这个接口中包括了一个doProxy方法,传入一个ProxyChain,用于执行“链式代理”操作。
所谓链式代理,也就是说,可将多个代理通过一条链子串起来,一个个地区执行,执行顺序取决于添加到链上的先后顺序。
package com.autumn.aop;import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List;/*** 代理链*/ public class ProxyChain {private final Class<?> targetClass; //代理类private final Object targetObject; //目标对象private final Method targetMethod; //目标方法private final MethodProxy methodProxy; //方法代理private final Object[] methodParams; //方法参数private List<Proxy> proxyList = new ArrayList<Proxy>(); //代理列表private int proxyIndex = 0; //代理索引public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<Proxy> proxyList) {this.targetClass = targetClass;this.targetObject = targetObject;this.targetMethod = targetMethod;this.methodProxy = methodProxy;this.methodParams = methodParams;this.proxyList = proxyList;}public Class<?> getTargetClass() {return targetClass;}public Method getTargetMethod() {return targetMethod;}public Object[] getMethodParams() {return methodParams;}public Object doProxyChain() throws Throwable{Object methodResult;if (proxyIndex<proxyList.size()){ //如果代理索引小于代理列表大小//从列表中取出相应的Proxy对象,调用器doProxy方法methodResult = proxyList.get(proxyIndex++).doProxy(this);}else { //所有代理遍历完后methodResult = methodProxy.invokeSuper(targetObject,methodParams); //执行目标对象业务 }return methodResult;} }
在ProxyChain类中,我们定义了一系列的成员变量,包括targetClass(目标类)、targetObject(目标对象)、targetMethod(目标方法)、methodProxy(方法代理)、methodParams(方法参数),此外还包括proxyList(代理列表)、proxyIndex(代理索引),这些成员变量在构造器中进行初始化,并提供了几个重要的获值方法。
需要注意的是MethodProxy这个类,它是CGLib开源项目为我们提供的一个方法代理对象,在doProxyChain方法中被使用。
doProxyChain方法中,proxyIndex来充当对象的计数器,若尚未达到proxyList的上限,则从proxyList中取出相应的Proxy对象,并调用其doProxy方法。在Proxy接口中的实现中会提供相应的横切逻辑,并调用doProxyChain方法,随后将再次调用当前ProxyChain对象的doProxyChain方法,直到proxyIndex达到proxyList的上限为止,最后调用methodProxy的invokeSuper方法,执行目标对象的业务逻辑。
在pom.xml中添加CGLib的Maven依赖:
<!--不能超过3.0版本,这里用2.2--><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2</version></dependency>
现在我们需要写一个类,让它提供一个创建代理对象的方法,输入一个目标类和一组Proxy接口实现,输出一个代理对象,将该类命名为ProxyManager,让它来创建爱你所有的代理对象,代码如下:
/*** 代理管理器*/ public class ProxyManager {public static <T> T createProxy(final Class<T> targetClass, final List<Proxy> proxyList){return (T) Enhancer.create(targetClass, new MethodInterceptor() {@Overridepublic Object intercept(Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy) throws Throwable {return new ProxyChain(targetClass,targetObject,targetMethod,methodProxy,methodParams,proxyList).doProxyChain();}});} }
使用CGLib提供的Enhancer.create()方法来创建代理对象,将intercept的参数传入ProxyChain的构造器中即可。
谁来调用ProxyManager呢?当然是切面类了,因为在切面类中,需要在目标方法被调用的前后增加相应的逻辑。我们有必要写一个抽象类,让它提供一个模板方法,并在该抽象类的具体实现中扩展相应的抽象方法。我们将该抽象命名为AspectProxy。代码如下
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method;/*** 切面代理*/ public abstract class AspectProxy implements Proxy{private static final Logger logger = LoggerFactory.getLogger(AspectProxy.class);/*** 执行链式代理*/@Overridepublic Object doProxy(ProxyChain proxyChain) throws Throwable {Object result = null;Class<?> cls = proxyChain.getTargetClass();Method method = proxyChain.getTargetMethod();Object[] params = proxyChain.getMethodParams();begin(); //代理开始时执行begin方法try {if (intercept(cls,method,params)){ //是否拦截此方法before(cls,method,params); //目标方法执行前代理方法before执行result = proxyChain.doProxyChain(); //执行目标方法after(cls,method,result); //目标方法执行后代理方法after执行}else {result = proxyChain.doProxyChain(); //执行目标方法 }}catch(Exception e){logger.error("proxy failure",e);error(cls,method,params,e); //抛出异常时代理方法error执行throw e;}finally{end(); //代理结束时执行方法end }return null;}/*方法开始前执行*/public void begin(){}/*** 拦截* @param cls 目标类* @param method 目标方法* @param params 目标方法参数* @return 返回是否拦截*/public boolean intercept(Class<?> cls, Method method, Object[] params) throws Throwable {return true;}/*** 前置增强* @param cls 目标类* @param method 目标方法* @param params 目标方法参数*/public void before(Class<?> cls, Method method, Object[] params) throws Throwable {}/*** 后置增强* @param cls 目标类* @param method 目标方法* @param result 目标方法返回结果*/public void after(Class<?> cls, Method method, Object result) throws Throwable {}/*** 抛出增强* @param cls 目标类* @param method 目标方法* @param params 目标方法参数* @param e 异常* @throws Throwable*/public void error(Class<?> cls, Method method, Object[] params, Exception e) throws Throwable {}/*方法结束后执行*/public void end(){} }
注意这里的AspectProxy类中的doProxy方法,我们从proxyChain参数中获取了目标类、目标方法与方法参数,随后通过一个try...catch...finally代码块来实现调用框架,从框架中抽象出一系列的“钩子方法”,这些抽象方法可在AspectProxy的子类中有选择性的实现,例如ControllerAspect
/*** 拦截所有Controller方法*/ @Aspect(Controller.class) public class ControllerAspect extends AspectProxy{private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAspect.class);private long begin; //方法开始时间/*** 前置增强* @param cls 目标类* @param method 目标方法* @param params 目标方法参数*/@Overridepublic void before(Class<?> cls, Method method, Object[] params) throws Throwable {LOGGER.debug("---------begin---------");LOGGER.debug(String.format("class: %s",cls.getName()));LOGGER.debug(String.format("method: %s",method.getName()));begin = System.currentTimeMillis();}/*** 后置增强* @param cls 目标类* @param method 目标方法* @param result 目标方法返回结果*/@Overridepublic void after(Class<?> cls, Method method, Object result) throws Throwable {LOGGER.debug(String.format("time: %ds", System.currentTimeMillis()-begin));LOGGER.debug("---------end---------");} }
这里只实现了before与after方法,就可以在目标方法执行前后添加其他需要执行的代码了。
那么这样就结束了吗?当然不是。我们需要在整个框架里使用ProxyManager来创建代理对象,并将该代理对象放入框架底层的BeanMap中,随后才能通过IOC将被代理的对象注入到其他对象中。
加载AOP框架
按照之前的套路,为了加载AOP框架,我们需要一个名为AopHelper的类,然后将其添加到HelperLoader类中。
在AOPHelper中我们需要获取所有的目标类及其被拦截的切面类实例,并通过ProxyManager.createProxy方法来创建代理对象,最后将其放入BeanMap中。
首先,需要为BeanHelper类添加一个setBean方法,用于将Bean实例放入Bean Map中(将实例替换为代理类用),代码如下:
package org.smart4j.framework.helper;import org.smart4j.framework.util.ReflectionUtil;import java.util.HashMap; import java.util.Map; import java.util.Set;/*** Bean助手类**/ public class BeanHelper {/*** 定义bean映射(用于存放Bean类与Bean实例的映射关系)*/private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>();static{//获取所有Controller和Service将类和实例放入Map中 }/*** 获取Bean映射* @return*/public static Map<Class<?>, Object> getBeanMap() { return BEAN_MAP; }/*** 根据Class获取bean实例* @param cls bean实例所属的类* @param <T> 类的实例对象* @return*/public static <T> T getBean(Class<T> cls){}/*** 设置Bean实例 - 手动将cls - obj放入到BEANMAP中去(更多用于将实例替换为代理对象)* @param cls 类* @param obj 实例*/public static void setBean(Class<?> cls,Object obj){BEAN_MAP.put(cls,obj);} }
然后,我们需要扩展AspectProxy抽象类的所有具体类getClassSetBySuper(),此外,还需要获取带有Aspect注解的所有类(即切面),因此需要在ClassHelper中添加一下两个方法:
public class ClassHelper {/*** 定义类集合*/private static final Set<Class<?>> CLASS_SET;static {String basePackage = ConfigHelper.getAppBasePackage();CLASS_SET = ClassUtil.getClassSet(basePackage);}/*** 获取应用包名下的所有类* @return*/public static Set<Class<?>> getClassSet(){}/*** 获取所有Controller类* @return*/public static Set<Class<?>> getControllerClassSet(){}/*** 获取所有Service类* @return*/public static Set<Class<?>> getServiceClassSet(){}/*** 获取应用包名下的所有bean类(Controller和Service)* @return*/public static Set<Class<?>> getBeanClassSet(){}/*** 获取应用包名下某父类(接口)的所有子类(或实现类)* @param superClass* @return*/public static Set<Class<?>> getClassSetBySuper(Class<?> superClass){Set<Class<?>> classSet = new HashSet<Class<?>>();for (Class<?> cls:CLASS_SET){if (superClass.isAssignableFrom(cls)&&superClass.equals(cls)){classSet.add(cls);}}return classSet;}/*** 获取应用包名下带有注解的所有类* @param annotationClass* @return*/public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass){Set<Class<?>> classSet = new HashSet<Class<?>>();for (Class<?> cls : CLASS_SET){if (cls.isAnnotationPresent(annotationClass)){classSet.add(cls);}}return classSet;} }
有了上面连个方法后,createTargetClassSet方法获取Aspect注解中设置的注解类,若该注解不是Aspect类,则可调用ClassHelper.getClassSetByAnnotation方法获取相应的类,并把这些类放入目标类集合中,最终返回这个集合。
然后我们用createProxyMap方法获取代理类及其目标类直接按的映射关系,一个代理类可对应一个或多个目标类。需要强调的是,这里所说的代理类指的是切面类。
AOP顺序:
代理类需要扩展AspectProxy抽象类(通过getClassSetBySuper获取所有子类(即切面)),还需要带有Aspect注解,只有满足这两个条件,才能根据Aspect注解中所定义的注解属性去获取该注解所对应的目标类集合(通过createTargetClassSet获取目标类集合),然后才能建立代理类与目标类集合之间的映射关系,最终返回这个映射关系。
一旦获取了代理类与目标类集合之间的映射关系,createTargetMap方法就能根据这个关系分析出目标类与代理对象列表之间的映射关系。
最后通过AOPHelper的静态代码块初始化整个AOP框架,获取代理类及其目标类集合的映射关系,进一步获取目标类与代理对象列表的映射关系,进而遍历这个映射关系,从中获取目标类与代理对象列表,调用ProxyManager.createProxy方法获取代理对象,调用BeanHelper.setBean方法,将该代理对象重新放入BeanMap中。
import org.smart4j.framework.aop.Aspect; import org.smart4j.framework.aop.AspectProxy; import org.smart4j.framework.aop.Proxy; import org.smart4j.framework.aop.ProxyManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.lang.annotation.Annotation; import java.util.*;/*** 方法拦截助手类*/ public class AopHelper {private static final Logger LOGGER = LoggerFactory.getLogger(AopHelper.class);static{try{Map<Class<?>,Set<Class<?>>> proxyMap = createProxyMap(); //获取Map<代理类,Set<目标类>>Map<Class<?>,List<Proxy>> targetMap = createTargetMap(proxyMap); //获取Map<目标类,List<代理实例>>for (Map.Entry<Class<?>,List<Proxy>> targetEntry:targetMap.entrySet()){ //遍历Map<目标类,List<代理实例>>Class<?> targetClass = targetEntry.getKey(); //目标类List<Proxy> proxyList = targetEntry.getValue(); //代理类Object proxy = ProxyManager.createProxy(targetClass,proxyList); //根据目标类和代理集合创建一个代理BeanHelper.setBean(targetClass,proxy); //将Bean容器中目标类对应的实体替换成代理 }}catch (Exception e){LOGGER.error("aop failure",e);}}/*** 根据Aspect注解(切点)获取所有的代理目标类集合(目标类为Controller等注解的类)* @param aspect 代理类注解,用来指定目标类的注解 例如:@Aspect(Controller.class)* @return 返回Aspect注解中指定value注解的目标类 例如:带Controller注解的所有类* 例如Aspect(Controller.class)是指获取所有Controller注解的类*/private static Set<Class<?>> createTargetClassSet(Aspect aspect){Set<Class<?>> targetClassSet = new HashSet<Class<?>>();Class<? extends Annotation> annotation = aspect.value(); //获取值(也是注解)if (annotation!=null && !annotation.equals(Aspect.class)){ //获取的value注解不为null,且注解不为AspecttargetClassSet.addAll(ClassHelper.getClassSetByAnnotation(annotation)); //加入所有value(切点)指定的注解的类 }return targetClassSet; //返回所有目标类 }/*** 创建所有Map<代理类,Set<代理目标类>>的映射关系* @return Map<代理类,Set<代理目标类>>* @throws Exception*/private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception{Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<Class<?>, Set<Class<?>>>(); //结果集<代理类,Set<代理目标类>>//获取所有的AspectProxy的子类(代理类集合),即切面,/*这个是入口,根据基类来查找所有的切面(代理类),获取所有的切面!!!*/Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);for (Class<?> proxyClass : proxyClassSet){ //遍历代理类(切面),如ControllerAspectif (proxyClass.isAnnotationPresent(Aspect.class)){ //验证基类为AspectProxy且要有Aspect注解的才能为切面。如果代理类的的注解为Aspect(也就是说代理类一定要都切点(注解)才能是切面),例如ControllerAspect代理类的注解为@Aspect(Controller.class)Aspect aspect = proxyClass.getAnnotation(Aspect.class); //获取代理类(切面)的注解/*根据注解获取所有的目标类*/Set<Class<?>> targetClassSet = createTargetClassSet(aspect); //获取所有的代理目标类集合 proxyMap.put(proxyClass,targetClassSet); //加入到结果集Map<代理类,Set<代理目标类>>中 }}return proxyMap;}/*** 将Map<代理类,Set<目标类>> proxyMap转为Map<目标类,List<代理类>> targetMap* @param proxyMap Map<代理类,Set<目标类>>* @return Map<目标类,List<代理类实例>>* @throws Exception*/private static Map<Class<?>,List<Proxy>> createTargetMap(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception{Map<Class<?>,List<Proxy>> targetMap = new HashMap<Class<?>,List<Proxy>>(); //class - list键值对的mapfor (Map.Entry<Class<?>,Set<Class<?>>> proxyEntry:proxyMap.entrySet()){ //遍历cls - set键值对的mapClass<?> proxyClass = proxyEntry.getKey(); //获取代理clsSet<Class<?>> targetClassSet = proxyEntry.getValue(); //获取目标Setfor (Class<?> targetClass:targetClassSet){ //遍历目标SetProxy proxy = (Proxy) proxyClass.newInstance(); //实例化代理类if (targetMap.containsKey(targetClass)){ //如果Map<Class<?>,List<Proxy>>包含该目标类targetMap.get(targetClass).add(proxy); //直接将代理类添加到对应目标类的Map中}else{List<Proxy> proxyList = new ArrayList<Proxy>(); //如果没有 proxyList.add(proxy);targetMap.put(targetClass,proxyList);}}}return targetMap;} }
最后,需要注意的是,AOPHelper要在IOCHelper之前加载,因为首先需要通过AopHelper获取代理对象,然后才能通过IOCHelper进行依赖注入。否则的话,IOCHelper先加载就会导致Bean容器中的是代理,而Bean中注入的是原本的实例对象。所以注入一定要最后进行(IOCHelper一定要最后加载)。
源码
转载于:https://www.cnblogs.com/aeolian/p/9931922.html
架构探险笔记5-使框架具备AOP特性(下)相关推荐
- 架构探险笔记10-框架优化之文件上传
确定文件上传使用场景 通常情况下,我们可以通过一个form(表单)来上传文件,就以下面的"创建客户"为例来说明(对应的文件名是customer_create.jsp),需要提供一个 ...
- 架构探险笔记11-与Servlet API解耦
Servlet API解耦 为什么需要与Servlet API解耦 目前在Controller中是无法调用Servlet API的,因为无法获取Request与Response这类对象,我们必须在Di ...
- 架构探险笔记7-事务管理简介
什么是事务 事务(Transaction)通俗的理解为一件事,要么做完,要么不做,不能做一半留一半.也就是说,事务必须是一个不可分割的整体,就像我们在化学课上学到的原子,原子是构成物质的最小单位.于是 ...
- 使用Zookeeper实现服务注册中心-《架构探险-从零开始写分布式服务框架》读书笔记
前言 最近在看<架构探险-从零开始写分布式服务框架>,对于分布式框架的入门级选手还是挺合适的,扫盲.对分布式服务框架中的基本概念:RPC.SOA.序列化.Spring集成RPC.ZooKe ...
- 读书杂谈-《架构探险:从零开始写Java Web框架》
爱买书,虽然读书懒惰的很,但最近还是陆陆续续的买了五六本书的样子,包括:核心技术.深入虚拟机.并发编程等,这些书中我能读的进去,且通俗易懂的当属这本黄勇写的<架构探险:从零开始写Java Web ...
- Reading Club·Beijing第1期DeepQA框架与Siri架构会场笔记(PPT下载)
读书会·北京第1期DeepQA框架与Siri架构会场笔记(含PPT下载) 前言 读书会以"革新学术,砥厉品行,共同探讨,相互学习"为宗旨,不限主题,不限形式,重在创造或营造一种良好 ...
- 荐书:《架构探险:从零开始写分布式服务框架》
荐书:<架构探险:从零开始写分布式服务框架> 一线技术专家 全方位解析 分布式服务框架底层技术细节 手把手教你 搭建一个完整的符合自身需求的 分布式服务框架 随着互联网浪潮风起云涌,互联网 ...
- 分布式服务框架原理与实践pdf_阿里架构师的架构探险之路:从零开始写分布式服务框架...
围绕实现分布式服务框架所需的知识点,进行了比较详尽细致的介绍.包括常见的RPC框架.常见的序列化/反序列化方案及选型.分布式服务框架服务的发布引入实现细节.软负载实现.底层通信方案实现.服务注册与发现 ...
- 阿里巴巴中台战略思想与架构实战笔记
阿里巴巴中台战略思想与架构实战笔记 序言一 序言二 第一部分 引子 第1章 阿⾥巴巴集团中台战略引发的思考 1.1 阿⾥巴巴共享业务事业部的发展史 1.2 企业信息中心发展的症结 "烟囱式& ...
最新文章
- linux 修改java版本_Linux 有问必答:如何在 Linux 中改变默认的 Java 版本
- Linux(centos6.0)下安装Node.js以及使用
- The conversion of a varchar data type to a datetime data type resulted in an out-of-range value
- 概率论-4.2中心极限定理(待补充)
- 面向切面编程应用_应用面向方面的编程
- java知识点3(null、引用相关知识(自己理解))
- windows环境下unicode编程总结
- WebRTC-Android硬编码流程详解
- python中33个保留字的含义_Python的保留字。这是什么意思?
- 2019年美赛获奖分享经验
- 计算机网络配置——静态路由的配置
- mac拷贝图片window打不开
- selenium设置chrome代理
- 完成计算机的界面并实现其功能,计算机组成原理-第1章-马永强.pdf
- 富文本编辑器 Kindeditor 的使用和 常见错误
- Kali Linux 与 BackTrack Linux
- 网络时代,如何增进亲情
- BeanDefinition的概述及使用
- json-server Error: EPERM: operation not permitted, mkdir ‘C:\Progra m Files\nodejs\node_
- Office_Word的学习