项目背景
由于项目中会需要进行入参日志打印、处理结果打印、权限控制等业务逻辑处理,而这些业务逻辑功能又是多变的,如果都将这些业务逻辑放在主要逻辑之中,那么随着时间的增加,代码会越来越杂。<br />为了降低代码的耦合度,提高代码逻辑辨识。可以使用代理模式去抽离业务逻辑,将业务逻辑独立出来。<br />但是业务逻辑有时候也不会只有一种,例如某接口即需要入参打印,又需要权限处理,那么这类情况最好将不同的业务逻辑也进行拆分。
构建cglib代理
1. 目标方法
假设目标方法长这样:
public class ToolService {public void run(String[] args) {System.out.println("执行目标对象方法;args="+ Arrays.toString(args));try {Thread.sleep(100); // 模拟调用时长} catch (InterruptedException e) {e.printStackTrace();}}}
2. 子类代理回调的拦截器
如果使用cglib代理,那么需要先引用cglib的依赖。
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.10</version></dependency>
那么创建一回调拦截器,实现 MethodInterceptor接口,用于处理子类代理调用方法时回调的逻辑,也即真正代理的逻辑。
public class ToolProxyCallback implements MethodInterceptor {// 目标对象private Object target;public ToolProxyCallback(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 入参日志打印System.out.println("准备执行目标方法;参数信息为="+args);// 权限拦截if(args.length <= 2) { // 假设这里的权限是参数不能小于2个return null;}// 通过反射执行目标方法Object resultObj = method.invoke(target,args);// 处理接口打印System.out.println("目标方法请求结果为;"+resultObj);return resultObj;}}
3. 创建子类代理的工厂
还需要一个工厂来创建子类代理的实例。
public class ToolProxyFactory {/*** 获取子类代理实例* @param target 目标对象* @param callback 回调接口* @return*/public static Object getProxyInstance(Object target, Callback callback) {// 工具类Enhancer enhancer = new Enhancer();// 设置父类enhancer.setSuperclass(target.getClass());// 设置回调函数。enhancer.setCallback(callback);return enhancer.create(); // 创建子类代理对象}}
4. 客户端执行调用
那么调用目标方法就变为这样。
@Testpublic void testProjectProxy() {ToolService service = new ToolService();Callback toolProxyCallback = new ToolProxyCallback(service);ToolService serviceProxy = (ToolService)ToolProxyFactory.getProxyInstance(service,toolProxyCallback);/*** 使用 ToolProxyFactory 构建出 ToolCglibService的代理子类,通过代理子类去调用接口方法*/String[] args = new String[]{"-s","test","-n","zhangsan"};serviceProxy.run(args);}
这样就可以实现将业务逻辑与核心逻辑抽离开,通过cglib提供的接口实例化出目标对象的子类实例,然后用这个子类实例调用父类的方法,也即我们需要代理的目标方法。
优化一:将代理的职责拆分
但是~这样就ok了嘛?
有木有发现 回调拦截器这个方法 ToolProxyCallback#intercept,存在职责不单一的情况。假设这里的权限拦截有着很复杂的逻辑,又或是这个权限拦截在其他接口需要用到的,那么是不是就需要把这个逻辑给抽离出来呢。
业务小的话是没关系的,但是如果考虑到后续前置/后置逻辑会不断累积,那么就需要将回调拦截器中的业务逻辑按照职责拆分单独出来了。
针对代理拦截的逻辑可以知道,有前置逻辑和后置逻辑需要处理。那么抽象为父类
public abstract class AbstractInterceptor {/*** 前置逻辑* @param target 目标对象* @param method 目标方法* @param args 参数* @throws Exception*/public abstract void before(Object target, Method method,Object[] args) throws Exception;/**** 后置逻辑* @param target 目标对象* @param method 目标方法* @param result 处理结果* @throws Exception*/public abstract void after(Object target,Method method,Object result) throws Exception;}
自定义注解Order,用于拦截器调用的先后顺序;
/*** order 值越小 表示优先级越高* -1 为默认值,可表示系统拦截*/@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Order {int value() default -1;}
定义LogInterceptor,继承AbstractInterceptor,用于关键日志打印;
@Order(1)public class LogInterceptor extends AbstractInterceptor {private long startTime;private long endTime;private static Logger logger = Logger.getLogger(LogInterceptor.class.getName());@Overridepublic void before(Object target, Method method,Object[] args) {startTime = System.currentTimeMillis();logger.info("代理对象方法执行。参数args="+args);}@Overridepublic void after(Object target,Method method,Object result) {endTime = System.currentTimeMillis();logger.info("方法执行结束。耗时:" + (endTime - startTime) + "ms");}}
定义AuthInterceptor拦截器,用于接口权限拦截;
@Order(2)public class AuthInterceptor extends AbstractInterceptor {@Overridepublic void before(Object target, Method method, Object[] args) throws Exception {System.out.println("run start..."+ this.getClass());}@Overridepublic void after(Object target, Method method, Object result) throws Exception {System.out.println("run end..."+this.getClass());}}
修改下前面的回调拦截器的方法,新增interceptorList列表,用于存储拦截器,注意这里是先后顺序存储,先后调用。
public class ToolProxyCallback implements MethodInterceptor {private Object target;public ToolProxyCallback(Object target) {this.target = target;}private static List<AbstractInterceptor> interceptorList = new LinkedList<>();// 注册拦截器public static void registerInterceptor(AbstractInterceptor interceptor) {interceptorList.add(interceptor);}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 执行拦截器的前置方法for(AbstractInterceptor interceptor : interceptorList) {interceptor.before(target,method,args);}System.out.println("准备执行目标方法;interceptorList.size="+interceptorList.size());Object resultObj = method.invoke(target,args);// 执行拦截器的后置方法for(AbstractInterceptor interceptor : interceptorList) {interceptor.after(target,method,resultObj);}return resultObj;}}
添加拦截器,那么调用ToolProxyCallback#registerInterceptor 方法,将拦截器注册到interceptorList即可。
优化二:自动化注册拦截器
后续如果想要新增拦截器,那么只需要继承AbstractInterceptor就行。但是每次添加一个拦截器都要主动注册一下,还要判断下先后顺序,那就有点麻烦。
新增拦截器注解,通过扫描包,将有该注解的拦截器都注册进来,就可以实现自动化注册了。
/*** 有该注解表示为自定义拦截器*/@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ToolAspect {}
通过扫描包,将拦截器统统注册进来。
public class AnnotationScanner {private static final String packageName = "agent.project";/*** 自动注册工具拦截器*/public static void registerToolInterceptorList() {List<Class> list = ClassUtil.getAllClassByAnnotation(ToolAspect.class,packageName);// 从小到大排序Collections.sort(list, new Comparator<Class>() {@Overridepublic int compare(Class clazz1, Class clazz2) {Order order1 = (Order)clazz1.getAnnotation(Order.class);Order order2 = (Order)clazz2.getAnnotation(Order.class);return order1.value() - order2.value();}});for(Class clazz : list) {AbstractInterceptor interceptor = null;try {interceptor = (AbstractInterceptor)clazz.newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}// 注册拦截器ToolProxyCallback.registerInterceptor(interceptor);}}}
还记得前面的代理工厂吗 ToolProxyFactory。由于我们每次获取子类代理实例的时候都会使用这个类,那么将自动注册的调用放在这里就合适了。
public class ToolProxyFactory {static {AnnotationScanner.registerToolInterceptorList(); // 注册拦截器}/*** 获取子类代理实例* @param target 目标对象* @param callback 回调接口* @return*/public static Object getProxyInstance(Object target, Callback callback) {// 工具类Enhancer enhancer = new Enhancer();// 设置父类enhancer.setSuperclass(target.getClass());// 设置回调函数。enhancer.setCallback(callback);return enhancer.create(); // 创建子类代理对象}}
通过上述的优化一、二,实现了将代理职责拆分,同时自动化注册拦截器。在这基础上,客户端调用的方法不变,依旧通过ToolProxyFactory#getProxyInstance方法获取到子类代理,然后执行目标方法。
再扩展优化思考
针对现有的实现架构,留下几个Q:
- 自动化注册拦截器会为所有目标方法实现了所有拦截器,但并不是每个目标方法所需要的拦截器都是相同的,那么就需要为目标方法实现特定的拦截器;
- 获取子类代理实例的逻辑略为繁琐,需要认识的类有点多,能不能实现自动代理;(通过注解或者其它方式)
- 能不能搞成即可使用cglib实现,又可自动切换为接口代理实现;(根据判断目标方法有没目标接口?)
- 对比下Spring Aop有何区别与相同之处;
