架构探险笔记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() { @Override public 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); /** * 执行链式代理 */ @Override public 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 目标方法参数 */ @Override public 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 目标方法返回结果 */ @Override public 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,且注解不为Aspect targetClassSet.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){ //遍历代理类(切面),如ControllerAspect if (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键值对的map for (Map.Entry<Class<?>,Set<Class<?>>> proxyEntry:proxyMap.entrySet()){ //遍历cls - set键值对的map Class<?> proxyClass = proxyEntry.getKey(); //获取代理cls Set<Class<?>> targetClassSet = proxyEntry.getValue(); //获取目标Set for (Class<?> targetClass:targetClassSet){ //遍历目标Set Proxy 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://images.cnblogs.com/cnblogs_com/aeolian/1679458/o_wechat_gzh_qrcode.jpg)