架构探险笔记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一定要最后加载)。
架构探险笔记5-使框架具备AOP特性(下)的更多相关文章
- 架构探险笔记4-使框架具备AOP特性(上)
对方法进行性能监控,在方法调用时统计出方法执行时间. 原始做法:在内个方法的开头获取系统时间,然后在方法的结尾获取时间,最后把前后台两次分别获取的系统时间做一个减法,即可获取方法执行所消耗的总时间. ...
- 架构探险笔记3-搭建轻量级Java web框架
MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦. 上一章我们使用Servlet来充当MVC模式中的Controller ...
- 架构探险笔记12-安全控制框架Shiro
什么是Shiro Shiro是Apache组织下的一款轻量级Java安全框架.Spring Security相对来说比较臃肿. 官网 Shiro提供的服务 1.Authentication(认证) 2 ...
- 架构探险笔记11-与Servlet API解耦
Servlet API解耦 为什么需要与Servlet API解耦 目前在Controller中是无法调用Servlet API的,因为无法获取Request与Response这类对象,我们必须在Di ...
- 架构探险笔记6-ThreadLocal简介
什么是ThreadLocal? ThreadLocal直译为“线程本地”或“本地线程”,如果真的这么认为,那就错了!其实它就是一个容器,用于存放线程的局部变量,应该叫ThreadLocalVariab ...
- Android 学习笔记之AndBase框架学习(六) PullToRefrech 下拉刷新的实现
PS:Struggle for a better future 学习内容: 1.PullToRefrech下拉刷新的实现... 不得不说AndBase这个开源框架确实是非常的强大..把大部分的东西 ...
- Spring框架中AOP特性
1.AOP介绍 即:面向切面编程,在不改变原有方法的定义与使用.也不改变原程序流程的情况下,可以改变原有方法的功能{增加一些附加的功能,在指定的地方添加其他函数方法:} 2.其他的方法:[需要的四个接 ...
- 读《架构探险——从零开始写Java Web框架》
内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...
- 【EatBook】-NO.3.EatBook.3.JavaArchitecture.2.001-《架构探险:从零开始写Java Web框架》-
1.0.0 Summary Tittle:[EatBook]-NO.3.EatBook.3.JavaArchitecture.2.001-<架构探险:从零开始写Java Web框架>- S ...
随机推荐
- 翻硬币|2013年蓝桥杯B组题解析第八题-fishers
翻硬币 小明正在玩一个"翻硬币"的游戏. 桌上放着排成一排的若干硬币.我们用 * 表示正面,用 o 表示反面(是小写字母,不是零). 比如,可能情形是:oooooo 如果同时翻转左 ...
- Unity3D代码动态修改材质球的颜色
代码动态修改材质球的颜色: gameObject.GetComponent<Renderer>().material.color=Color.red;//当材质球的Shader为标准时,可 ...
- .psl脚本介绍
.ps1文件是PowerShell写好的脚本文件 可以在记事本中写一段PowerShell代码,然后将其保存为“xxx.ps1”,后面要使用它的时候,双击即可运行了.这有点像批处理的“.bat”文件, ...
- 【BZOJ】3576: [Hnoi2014]江南乐
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3576 很显然,这是一个multi-nim游戏. 注意:1.一个点的SG值就是一个不等于它的 ...
- P4001 [BJOI2006]狼抓兔子
传送门 思路: 不少题解都是用网络流来做最小割(网络流是什么),但对于一个不会网络流的蒟蒻来做这题相当困难. 听机房daolao说可以重构图做最短路.然后就baidu将平面图转换成一个对偶图,因为网络 ...
- red hat7 系统可以ping通ip地址但是不能ping通域名
在red hat7中ifconfig后出现这样的情况,ens33是物理网卡,与eth0一样只是不同的名字.但是只能ping通ip地址不能ping通域名. 解决方法: 在文件 /etc/resolv.c ...
- Android JNI 传递对象
JNI初步入门后,在传递数据的时候,遇到一个需求:有多个数据需要在Java与C代码之间进行传递.如果都做为函数参数传入,则函数很长很难看,并且多个数据的返回也不好实现.所以想到了把数据打包后传递.这在 ...
- DRF框架固定配置
1.认证Authentication 可以在配置文件中配置全局默认的认证方案 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_ ...
- python return dict bug?
def to_dict(self): para = OrdererDict() para['dd'] = self.XXX ... return para 这样一个简单函数 para1 = obj.t ...
- Qt解析Json数据
1 JSON数据简介 JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式.它基于 ECMAScript (欧洲计算机协会制定的js规范) ...