自定义Aspect风格的AOP框架
本篇博客参考《架构探险--从零开始写java web框架》4.3章节
1代理接口:
package smart.myaop.framework;
public interface Proxy {
/**
* 执行链式调用
*/
Object doProxy(ProxyChain proxyChain) throws Throwable;
}
2代理链(责任链模式,同一个对象可以被多个Proxy层层代理):
package smart.myaop.framework;
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; //方法代理,cglib提供的方法代理对象
private final Object[] methodParams; //方法参数
private List<Proxy> proxyList = new ArrayList<>(); //代理列表
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;
}
/**
* 在Proxy接口的实现中提供相应横切逻辑并调用doProxyChain方法
* methodProxy的invokeSuper方法执行目标对象的业务逻辑
* @return
* @throws Throwable
*/
public Object doProxyChain() throws Throwable {
Object methodResult;
if(proxyIndex < proxyList.size()) {
methodResult = proxyList.get(proxyIndex++).doProxy(this);
} else {
methodResult = methodProxy.invokeSuper(targetObject, methodParams);
}
return methodResult;
}
}
3创建代理对象的工具类:
package smart.myaop.framework;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.List;
/**
* 代理管理器,输入目标类和一组proxy接口实现,创建一个代理对象并输出
* 由切面类来调用ProxyManager创建代理链,切面类在目标方法调用前后进行增强
*
* 在框架里使用ProxyManager创建代理对象并放入ioc容器,然后将代理对象注入到其它对象中
*/
public class ProxyManager {
public static <T> T createProxy(final Class<?> 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);
}
});
}
}
4Proxy接口的抽象实现,模板方法模式:
package smart.myaop.framework;
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();
/**
* 从proxyChain中获取目标类,目标方法和目标参数,通过try ... catch ...finally代码块调用代理框架
*/
begin();
try {
if(intercept(cls, method, params)) {
before(cls, method, params);
result = proxyChain.doProxyChain();
after(cls, method, result);
}
} catch (Exception e) {
logger.error("proxy failure", e);
error(cls, method, params, e);
} finally {
end();
}
return result;
}
/**
* 下面几个都是钩子方法,可在子类中有选择性的实现,可以有选择性的实现,所以不定义成抽象方法
*/
public boolean intercept(Class<?> cls, Method method, Object[] params) {
return true;
}
public void before(Class<?> cls, Method method, Object[] params) {}
public void after(Class<?> cls, Method method, Object result) {}
public void begin() {}
public void end() {}
public void error(Class<?> cls, Method method, Object[] params, Exception e) {}
}
5举例某个具体的Proxy实现:
package smart.myaop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import smart.myaop.framework.AspectProxy;
import java.lang.reflect.Method;
/**
* 该示例类继承AspectProxy类,指定拦截Controller所有方法,并在方法前后加日志并记录执行时间
*/
@Aspect(Controller.class)
public class ControllerAspect extends AspectProxy {
private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
private long begin;
@Override
public void before(Class<?> cls, Method method, Object[] params) {
logger.debug("----------begin----------");
logger.debug(String.format("class: %s"), cls.getName());
logger.debug(String.format("method: %s"), method.getName());
begin = System.currentTimeMillis();
}
@Override
public void after(Class<?> cls, Method method, Object result) {
logger.debug(String.format("time: %dms"), System.currentTimeMillis() - begin);
logger.debug("----------end----------");
}
}
6自定义注解@Aspect,作为代理标记:
package smart.myaop;
import java.lang.annotation.*;
/**
* 切面注解
*/
@Target(ElementType.TYPE) //只能用在类上
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
/**
* 注解,注解类,用来定义注解
* @return
*/
Class<? extends Annotation> value();
}
7初始化框架并创建代理对象放入ioc容器:
package smart.myaop;
import smart.myaop.framework.AspectProxy;
import smart.myaop.framework.Proxy;
import smart.myaop.framework.ProxyManager;
import java.lang.annotation.Annotation;
import java.util.*;
/**
* 初始化时获取所有目标类和其被拦截的切面类实例,获取AspectProxy抽象类的所有子类和@Aspect注解的所有类
* 调用ProxyManager#createProxy方法创建代理对象放入ioc容器
* AopHelper在框架初始化时调用,要在初始化bean之后,ioc容器初始化之前调用,这样ioc容器做属性注入时候才能拿到相应的代理对象
*/
public class AopHelper {
private static Set<Class<?>> allClassSet = new HashSet<>(); //项目启动时把指定路径下的所有class文件加载为class对象集合,过程略
static {
try {
Map<Class<?>, Set<Class<?>>> proxyMap = createProxyMap();
Map<Class<?>, List<Proxy>> targetMap = createTargetMap(proxyMap);
for (Map.Entry<Class<?>, List<Proxy>> targetEntry : targetMap.entrySet()) {
Class<?> targetClass = targetEntry.getKey();
List<Proxy> proxyList = targetEntry.getValue();
Object proxy = ProxyManager.createProxy(targetClass, proxyList);
//todo 放入targetClass为键,proxy为值放入ioc容器,在ioc容器做属性注入的时候通过class对象拿到的就是代理对象了
}
} catch (Exception e) {
System.out.println("aop failure");
e.printStackTrace();
}
}
/**
* 目标类与代理对象列表之间的映射关系,如一个业务类被多个@Aspect注解修饰的AspectProxy子类代理,这里得到这样的1对n映射关系
* @param proxyMap
* @return
* @throws Exception
*/
private static Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {
Map<Class<?>, List<Proxy>> targetMap = new HashMap<>();
for (Map.Entry<Class<?>, Set<Class<?>>> proxyEntry : proxyMap.entrySet()) {
Class<?> proxyClass = proxyEntry.getKey();
Set<Class<?>> targetClassSet = proxyEntry.getValue();
for (Class<?> targetClass : targetClassSet) {
Proxy proxy = (Proxy) proxyClass.newInstance();
if(targetMap.containsKey(targetClass)) {
targetMap.get(targetClass).add(proxy);
} else {
List<Proxy> proxyList = new ArrayList<>();
proxyList.add(proxy);
targetMap.put(targetClass, proxyList);
}
}
}
return targetMap;
}
/**
* 给createTargetMap方法用
* 代理类(切面类)与目标类集合之间的一对多映射关系
* 在全部class对象集合中搜索满足1是AspectProxy子类,2被@Aspect注解,这样的类(代理类),根据@Aspect注解指定的注解属性去获取该注解对应的目标类集合
* 然后建立代理类与目标类集合之间的映射关系,据此分析出目标类与代理对象列表之间的映射关系
* @return
* @throws Exception
*/
private static Map<Class<?>, Set<Class<?>>> createProxyMap() throws Exception {
Map<Class<?>, Set<Class<?>>> proxyMap = new HashMap<>();
Set<Class<?>> proxyClassSet = new HashSet<>();
for (Class<?> aClass : allClassSet) {
if(AspectProxy.class.isAssignableFrom(aClass) && !AspectProxy.class.equals(aClass)) {
proxyClassSet.add(aClass); //获取AspectProxy子类class对象集合
}
}
for (Class<?> aClass : proxyClassSet) {
if(aClass.isAnnotationPresent(Aspect.class)) {
Aspect aspect = aClass.getAnnotation(Aspect.class);
Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
proxyMap.put(aClass, targetClassSet);
}
}
return proxyMap;
}
/**
* 给createProxyMap方法用
* 获取被指定aspect注解的所有类对象
* @param aspect
* @return
* @throws Exception
*/
private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception {
Set<Class<?>> targetClassSet = new HashSet<>();
Class<? extends Annotation> annotation = aspect.value();
if(annotation != null && annotation.equals((Aspect.class))) {
for (Class<?> aClass : allClassSet) {
if(aClass.isAnnotationPresent(annotation)) {
targetClassSet.add(aClass); //这里从所有的class集合中挑出被annotation类型注解的class对象集合
}
}
}
return targetClassSet;
}
}
注意:代码从1写到7,从7到1理解有助于了解整体工作流程,整个用了责任链模式、模板方法模式,CGLIB动态代理。书中叫Proxy和ProxyChain叫做代理和代理链,改叫增强和增强链更容易理解,每一个Proxy就是对目标类的方法的一次功能增强。
使用举例:
可以提供一个@Login注解,可以用在方法或类上,然后实现一个AuthzAnnotationAspect继承AspectProxy,用@Aspect(Controller.class)注解,在before方法中判断目标方法或目标类是否被@Login注解了,如果是,使用Shiro的代码判断用户是否登录:
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
if(principals == null || principals.isEmpty()) {
throw new Exception(“当前用户未登录!”);
}
自定义Aspect风格的AOP框架的更多相关文章
- 带你学习AOP框架之Aspect.Core[1]
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的 ...
- Android平台免Root无侵入AOP框架Dexposed使用详解
Dexposed是基于久负盛名的开源Xposed框架实现的一个Android平台上功能强大的无侵入式运行时AOP框架. Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者 ...
- Android免Root无侵入AOP框架Dexposed
Dexposed框架是阿里巴巴无线事业部近期开源的一款在Android平台下的免Root无侵入运行期AOP框架,该框架基于AOP思想,支持经典的AOP使用场景,可应用于日志记录,性能统计,安全控制,事 ...
- JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架
1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...
- 仿写一个简陋的 IOC/AOP 框架 mini-spring
讲道理,感觉自己有点菜.Spring 源码看不懂,不想强行解释,等多积累些项目经验之后再看吧,但是 Spring 中的控制反转(IOC)和面向切面编程(AOP)思想很重要,为了更好的使用 Spring ...
- 动手造轮子:实现一个简单的 AOP 框架
动手造轮子:实现一个简单的 AOP 框架 Intro 最近实现了一个 AOP 框架 -- FluentAspects,API 基本稳定了,写篇文章分享一下这个 AOP 框架的设计. 整体设计 概览 I ...
- Spring 08: AOP面向切面编程 + 手写AOP框架
核心解读 AOP:Aspect Oriented Programming,面向切面编程 核心1:将公共的,通用的,重复的代码单独开发,在需要时反织回去 核心2:面向接口编程,即设置接口类型的变量,传入 ...
- 设计 REST 风格的 MVC 框架
http://www.ibm.com/developerworks/cn/java/j-lo-restmvc/ 传统的 JavaEE MVC 框架如 Struts 等都是基于 Action 设计的后缀 ...
- Dora.Interception, 一个为.NET Core度身打造的AOP框架[3]:Interceptor的注册
在<不一样的Interceptor>中我们着重介绍了Dora.Interception中最为核心的对象Interceptor,以及定义Interceptor类型的一些约定.由于Interc ...
随机推荐
- Rsync安装部署
Rsync安装部署 1.Rsync 简介 Rsync 是一款开源的.快速的 多功能的 可以实现全量以及增量的本地或者是远程的数据同步备份的优秀工具,并且可以不进行改变原有的数据属性信息,实现数据的 ...
- 【串线篇】spring boot配置嵌入式servlet容器
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器 问题? 一.如何定制和修改Servlet容器的相关配置 1.方法1修改和server有关的配置(ServerProperties ...
- C指针,&,*,指针的指针
C指针: 指向变量的地址,想象成房间号 &: 取地址符号 *:间接访问符号, 访问p所存地址的内容 #include <iostream> int main(int argc, c ...
- spring boot整合WebSocket示例
1.运行环境 开发工具:intellij idea JDK版本:1.8 项目管理工具:Maven 4.0.0 2.GITHUB地址 https://github.com/nbfujx/springBo ...
- [CSP-S模拟测试]:trade(反悔贪心)
题目传送门(内部题62) 输入格式 第一行有一个整数$n$.第二行有$N$个整数:$a_1\ a_2\ a_3\cdot\cdot\cdot a_n$. 输出格式 一行一个整数表示最大收益. 样例 样 ...
- Android 一键分享功能简单实现
import java.io.File;import java.util.ArrayList;import java.util.List; import android.content.Context ...
- 万能的gitignore文件模版
## .gitignore for Grails 1.2 and 1.3 # .gitignore for maven target/ *.releaseBackup # web applicatio ...
- BLOB类型的字段用于存储二进制数据
MySQL中,BLOB是个类型系列,包括:TinyBlob.Blob.MediumBlob.LongBlob,这几个类型之间的唯一区别是在存储文件的最大大小上不同. MySQL的四种BLOB类型类型 ...
- P3956棋盘
传送 这看起来有点像个搜索,那我们就用搜索试试. dfs?bfs? 其实都可以,但是窝只会dfs.. 既然这里要用dfs,那么就要把每次搜到(m,m)时,使用的金币数量进行比较,取最小值. 在搜索过程 ...
- P1063能量项链
传送 这又是一道经典的区间DP题. 复习一下区间DP的做法. 三重循环,第一层枚举区间长度,第二层枚举起点,第三层枚举断点. 区间长度是从1到n-1(因为如果是从1到n的话,1+n≠n,所以是1到n- ...