借鉴SpringAOP的风格,写一个基于切面注解的AOP框架。在进行下面的步骤以前,确保已经掌了动态代理技术。java
/** * 切面注解 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Aspect { /*注解*/ Class<? extends Annotation> value(); }
经过@Target(ElementType.TYPE)来设置该注解只能应用在类上。该注解中包含一个名为value的属性,它是一个注解类,用来定义Controller这类注解。git
在使用切面注解以前,咱们须要先搭建一个代理框架。github
继续在框架中添加一个名为Proxy的接口框架
/** * 代理接口 */ public interface Proxy { /** * 执行链式代理 */ Object doProxy(ProxyChain proxyChain) throws Throwable; }
这个接口中包括了一个doProxy方法,传入一个ProxyChain,用于执行“链式代理”操做。ide
所谓链式代理,也就是说,可将多个代理经过一条链子串起来,一个个地区执行,执行顺序取决于添加到链上的前后顺序。this
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(代理索引),这些成员变量在构造器中进行初始化,并提供了几个重要的获值方法。spa
须要注意的是MethodProxy这个类,它是CGLib开源项目为咱们提供的一个方法代理对象,在doProxyChain方法中被使用。debug
doProxyChain方法中,proxyIndex来充当对象的计数器,若还没有达到proxyList的上限,则从proxyList中取出相应的Proxy对象,并调用其doProxy方法。在Proxy接口中的实现中会提供相应的横切逻辑,并调用doProxyChain方法,随后将再次调用当前ProxyChain对象的doProxyChain方法,直到proxyIndex达到proxyList的上限为止,最后调用methodProxy的invokeSuper方法,执行目标对象的业务逻辑。代理
在pom.xml中添加CGLib的Maven依赖:code
<!--不能超过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框架,咱们须要一个名为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必定要最后加载)。