场景
本文主要是封装AOP,为指定的模块提供二次开发入口。同时也可以实现AOP的统一管理。比如一个模块,我们需要在其
页面查询的时候和保存的时候都要进行一些处理,这样子我们可能需要同时去写两个AOP分别来拦截查询和保存的请求。
其实换一个思路,我们其实可以将查询,保存等功能的5个通知全部封装起来,编上执行顺序,然后通过继承该类,就可以
按照指定模块来实现二次开发了,而不是按照功能来实现。
1.封装 各个功能 的5个AOP通知函数
这个需要根据自己项目的业务来总结,这里只以保存功能为例。
import com.aopmanager.aopmanager.annotation.OrderMethod;
import org.aspectj.lang.ProceedingJoinPoint;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* 所有二次开发类的父类,也就是具有 ExtAop 注解的类的父类
* 内部封装的是所有的 通知方法
* 接口实现类需要实现所有接口的方法,但实际业务大部分情况不需要实现所有方法,所以定义成类
*/
public class ExtMeta {
//通知前缀
public static final String[] PREFIXS = {};
//保存前(主要做参数处理)
@OrderMethod(order = 1)
public Map<String, Object> beforeSave(Map<String, Object> params){return params;}
//环绕通知---需要放行操作 环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的.
@OrderMethod(order = 2)
public void aroundSave(ProceedingJoinPoint pjp){}
//保存后(不论保存方法是否有异常,都会执行该方法)-->等于是 saveAfterReturn + saveAfterThrow
@OrderMethod(order = 3)
public void afterSave(Map<String, Object> params){}
//保存后(保存方法执行后,无异常,执行该方法) 后置成功通知---------拦截的方法没油抛出异常就会执行--拦截到的方法必须正确返回;
@OrderMethod(order = 4)
public void afterReturnSave(Map<String, Object> params){}
//保存后(保存方法执行后,有异常,执行该方法) 后置异常通知----拦截的方法如果抛出异常则会执行----有异常执行它没油异常不执行
@OrderMethod(order = 5)
public void afterThrowSave(Map<String, Object> params){}
/**
* 按顺序获取使用 OrderMethod 排序后的 方法
* @param methods
* @return
*/
public static List<Method> getOrderedMethods(Method[] methods){
// 用来存放所有的属性域
List<Method> methodList = new ArrayList<Method>();
// 过滤带有注解的Field
for(Method m:methods){
if(m.getAnnotation(OrderMethod.class)!=null){
methodList.add(m);
}
}
// sort 就是Java专门用于书写 排序规则的方法,形参就是 一个 Comparator 类的实例对象
//实例化 Comparator 类的时候,必须要重写其 compare 方法
methodList.sort(new Comparator(){
@Override
public int compare(Object o1, Object o2) {
Method m1 = (Method) o1;
Method m2 = (Method) o2;
if(m1.getAnnotation(OrderMethod.class).order() > m2.getAnnotation(OrderMethod.class).order()) return 1;
if(m1.getAnnotation(OrderMethod.class).order() < m2.getAnnotation(OrderMethod.class).order()) return -1;
return 0;
}
});
return methodList;
}
public static void main(String[] args){
Class cls = ExtMeta.class;
//Method[] methods = cls.getDeclaredMethods();
Method[] methods = getOrderedMethods(cls.getDeclaredMethods()).toArray(new Method[getOrderedMethods(cls.getDeclaredMethods()).size()]);
for(Method method : methods){
System.out.println(method.getName());
}
}
}
这里有一个问题就是,我们如何像AOP一样,指定这几个环绕函数的执行顺序呢?首先,需要自定义一个注解OrderMethod,
用来给方法排序然后通过 getOrderedMethods方法来获取到排序后的方法。自定义排序注解OrderMethod代码如下:
/**
* 该注解标注在方法上,用于为方法排序,使用它的原因:
* 在JDK的API文档里明确标注了:不能保证getDeclaredFields()/getDeclaredMethods()返回的Fields[] 和 Methods[] 的顺序。
* 注意是不能保证返回顺序,而不是返回是乱序:它完全可能是乱序,也还可能是按照声明顺序排布。
* 这是因为,JVM有权在编译时,自行决定类成员的顺序,不一定要按照代码中的声明顺序来进行编译。因此返回的顺序其实是class文件中的成员正向顺序,只不过在编译时这个顺序不一定等于声明时的顺序。
*/
//此注解只能修饰方法
@Target(ElementType.METHOD)
//当前注解如何去保持
@Retention(RetentionPolicy.RUNTIME)
//生成到API文档
@Documented
public @interface OrderMethod {
/**
* 标注当前方法的顺序
* @return 当前方法的顺序
*/
int order();
}
2. 创建两个注解
一个是用来修饰方法,声明这个方法是需要被二次 开发的方法。一个是用来修饰继承了ExtMeta类的二次开发类,并且
需要再请求中指定一个参数,为这两个注解都设置上这个参数值,通过这个参数值就可以找到方法对应的二次开发类了
/**
* 自定义注解
* 该注解修饰继承了ExtMeta的类,表示该类为 自定义的 AOP 类
*
* 先使用spring的aop,在项目启动的时候,获取项目中所有被 ExtAop 修饰的类,以map的形式存放在内存中(就是static aopClass中),
* 其中, key 与 请求中的 identify 参数的值保持一致,value就是对应的 ExtAop注解修饰的类的 class
* 然后该Aop还会拦截标注有该注解的所有方法,获取该方法 参数中的 identify 参数,然后与所有的 ExtAop的key做对比,
* 有的话,就执行 自定义的 通知函数,没得的话,就通过
*/
//此注解只能修饰类或者接口
@Target(ElementType.TYPE)
//当前注解如何去保持
@Retention(RetentionPolicy.RUNTIME)
//生成到API文档
@Documented
public @interface ExtAop {
//使用该注解的时候会有这个属性---使用: @ext( value = "注解式拦截的add操作Service")
String value() default "";
}
/**
* 自定义注解
* 该注解修饰 需要被 二次开发的AOP拦截的方法.
*
* 先使用spring的aop,在项目启动的时候,获取项目中所有被 ExtAop 修饰的类,以map的形式存放在内存中(就是static aopClass中),
* 其中, key 与 请求中的 identify 参数的值保持一致,value就是对应的 ExtAop注解修饰的类的 class
* 然后该Aop还会拦截标注有该注解的所有方法,获取该方法 参数中的 identify 参数,然后与所有的 ExtAop的key做对比,
* 有的话,就执行 自定义的 通知函数,没得的话,就通过
*/
//此注解只能修饰方法
@Target(ElementType.METHOD)
//当前注解如何去保持
@Retention(RetentionPolicy.RUNTIME)
//生成到API文档
@Documented
public @interface ExtAopMethod {
//使用该注解的时候会有这个属性---使用: @ext( value = "注解式拦截的add操作Service")
String value() default "";
}
3. 创建一个AOP,拦截所有有ExtAopMethod注解的方法,进入二次开发
/**
* AOP 编程中重要的 6 个注解:
* @Aspect //标注在某个类上,表示这个类是AOP类
* @Around //环绕增强(目标方法执行前后,分别执行一些代码)
* @Before //前置增强(目标方法执行之前,执行注解标注的内容)
* @Method //
* @After //Final增强(不管是抛出异常还是正常退出,该增强都会得到执行。一般用于释放资源,相当于try{}finally{})
* @AfterReturning //执行方法正常返回后调用,(目标方法发生异常,不执行)
* AOP 编程获取目标方法的信息:
* 访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。
* JoinPoint里包含了如下几个常用的方法:
* Object[] getArgs:返回目标方法的参数
* Signature getSignature:返回目标方法的签名
* Object getTarget:返回被织入增强处理的目标对象
* Object getThis:返回AOP框架为目标对象生成的代理对象
* 注意:当使用@Around处理时,我们需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。
*/
//@Aspect 定义切面:切面由切点和增强(引介)组成(可以包含多个切点和多个增强),它既包括了横切逻辑的定义,也包括了连接点的定义,
//SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的链接点中。
@Aspect
@Component
public class ExtAopMethodAop {
//静态变量,存放二次开发类,key 与 请求中的 identify 参数的值保持一致,value就是对应的 ExtAop注解修饰的类的 class
private static Map<String, Class<?>> aopClassMap = new HashMap<String, Class<?>>();
static {
try {
//反射获取指定包中所有的 类
List<Class<?>> classes = ClassUtil.getClassByPackage("com.aopmanager");
//遍历所有的类
for(Class<?> cls : classes){
//如果类上面包含 ExtAop 注解,则为二次开发入口
if (cls.getAnnotation(ExtAop.class) != null) {
//获取该类上的 ExtAop 注解上的值
String extAopValue = cls.getAnnotation(ExtAop.class).value();
aopClassMap.put(extAopValue, cls);
}
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
/**
* 就是定义拦截条件:
* 每一个拥有 Ext 注解的方法
*/
//@Pointcut 定义切点:切点是一组连接点的集合。AOP通过“切点”定位特定的连接点。
//通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。
@Pointcut("@annotation(com.aopmanager.aopmanager.annotation.ExtAopMethod)")
public void extAopMethodAopCut() {}
@Around("extAopMethodAopCut()")
public void aroundSave(ProceedingJoinPoint pjp) throws InvocationTargetException {
//获取指定注解 ExtAopMethod 修饰的方法的参数
//请求的参数
Object[] args = pjp.getArgs();
//如果该方法没有参数,则肯定不会有对应的 自定义 AOP
if(args.length == 0){
return;
}
//获取方法参数中 名为 identify 的参数的值
String methodIdentify = (String) ((HashMap) args[0]).get("identify");
//遍历所有具有 二次开发注解的类Map,按顺序执行其继承的父类(ExtMeta)方法
Set<String> keySet = aopClassMap.keySet();
for(String key : keySet){
//如果该方法中的 identify 参数值有对应的二次开发 aop
if(methodIdentify.equals(key)){
//获取其对应的 二次开发AOP
Class extClass = aopClassMap.get(key);
//获取当前class所有自定义方法(不包括父类方法)
Method[] methods = extClass.getDeclaredMethods();
//java 数组按照指定顺序排序 // saveBefore -> saveAround -> saveAfter saveAfterReturn saveAfterThrow
Method[] aopMethods = ExtMeta.getOrderedMethods(ExtMeta.class.getDeclaredMethods()).toArray(new Method[ExtMeta.getOrderedMethods(ExtMeta.class.getDeclaredMethods()).size()]);
//Method[] aopMethods = sortArrByRule(methods, extMetaMethods());
//Spring AOP通知的执行顺序:around中的before-》before-》原有方法-》around中的after-》after
//我们现在将二次开发的AOP 的通知的执行顺序修改为:before-》around中的before-》原有方法-》around中的after-》after
for(Method method : aopMethods){
String methodName = method.getName();
try{
//最先执行的是
if(methodName.toLowerCase().indexOf("before") != -1){
method.invoke(extClass.newInstance(), args);
}
if(methodName.toLowerCase().indexOf("around") != -1){
method.invoke(extClass.newInstance(), pjp);
}
if(methodName.toLowerCase().indexOf("after") != -1){
method.invoke(extClass.newInstance(), args);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 按顺序获取 ExtMeta 类中指定前缀的方法,就是通知执行顺序
* @return
*/
private Method[] extMetaMethods(){
List<Method> methodList = new ArrayList<Method>();
//获取通知前缀
String[] prefixs = ExtMeta.PREFIXS;
//获取 二次开发AOP中所有父类(二次开发AOP中,通知的执行顺序,就按照父类中的方法顺序执行)
Class extMetaClass = ExtMeta.class;
Method[] extMetaMethods = extMetaClass.getDeclaredMethods();
for(String prefix : prefixs){
for(Method extMetaMethod : extMetaMethods){
String methodName = extMetaMethod.getName();
if(!methodName.startsWith(prefix)){
continue;
}
methodList.add(extMetaMethod);
}
}
//list 集合转数组
return methodList.toArray(new Method[methodList.size()]);
}
//给 当前二次开发AOP类中的通知排序,就是在其执行顺序
private Method[] sortArrByRule(Method[] methods, Method[] sorts){
//我们现在将二次开发的AOP 的通知的执行顺序修改为:before-》around中的before-》原有方法-》around中的after-》after
// saveBefore -> saveAround -> saveAfter saveAfterReturn saveAfterThrow
List<Method> sortMethodList = new ArrayList<Method>();
//遍历顺序数组
for(Method sort : sorts){
String sortName = sort.getName();
//遍历要被排序的数组
for(Method method : methods){
//获取方法名
String methodName = method.getName();
if(!methodName.toUpperCase().equals(sortName.toUpperCase())){
continue;
}
sortMethodList.add(method);
}
}
//list 转数组 String[] strings = new String[list.size()]; list.toArray(strings);
return sortMethodList.toArray(new Method[sortMethodList.size()]);
}
public static void main(String[] args){
String[] arr = {"ab", "cd", "ef", "ee"};
System.out.println(arr);
}
}
在这里处理二次开发函数的执行顺序,还需要获取到所有的二次开发类,方便对比
import sun.net.www.protocol.jar.URLJarFile;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* 类的公共类
*/
public class ClassUtil {
/**
* 获取当前的类加载器
* @return
*/
public static ClassLoader getClassLoader() {
//获取当前线程的Context类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader == null) {
//获取当前类的类加载器
contextClassLoader = ClassUtil.class.getClassLoader();
}
return contextClassLoader;
}
public static List<Class<?>> getClassByPackage(String packageName) throws IOException {
// 第一个class类的集合
List<Class<?>> classes = new ArrayList<Class<?>>();
//将指定包名中的 . 全部替换成 /
String packagePath = packageName.replace(".", "/");
//根据当前线程的类加载器,获取到指定包中的所有资源(这个资源指定是各个文件的URL)
Enumeration<URL> resources = ClassUtil.getClassLoader().getResources(packagePath);
//遍历所有的URL资源
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
//文件类型(class,java,txt,js,json等等)
String protocol = url.getProtocol();
//获取当前URL资源的全路径,并转换成UTF-8,file:/C:/Users/21454/.IntelliJIdea2018.2/system/captureAgent/debugger-agent-storage.jar!/com
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
if ("file".equals(protocol)) {
classes = getClassByFilePath(packageName, filePath, classes);
}
if ("jar".equals(protocol)) {
//截取文件的路径:/C:/Users/21454/.IntelliJIdea2018.2/system/captureAgent/debugger-agent-storage.jar
filePath = filePath.substring(filePath.indexOf(":") + 1, filePath.indexOf("!"));
classes = getClassByJarPath(packageName, filePath);
}
}
return classes;
}
/**
* 根据类的全路径,获取对应的class类对象
* @param className 类的全路径:com.intellij.execution.TestDiscoveryListener
* @return
*/
public static Class forName(String className) {
try {
Class<?> aClass = Class.forName(className);
return aClass;
} catch (NoClassDefFoundError e) {
} catch (ClassNotFoundException e) {
}
return null;
}
/**
* 在文件夹中递归找出该文件夹中在package中的class
*
* @param packageName
* @param filePath
* @return classes
*/
static List<Class<?>> getClassByFilePath(String packageName, String filePath, List<Class<?>> classes ) {
// List<Class<?>> classes = new ArrayList<Class<?>>();
File targetFile = new File(filePath);
if (!targetFile.exists()) {
return classes;
}
if (targetFile.isDirectory()) {
File[] files = targetFile.listFiles();
for (int i = 0; i < files.length; i++) {
String path = files[i].getPath().replace("\\", "/");
classes = getClassByFilePath(packageName, path, classes);
}
} else {
//如果是一个class文件
boolean trueClass = filePath.endsWith(".class");
if (trueClass) {
//提取完整的类名
filePath = filePath.replace("/", ".");
int i = filePath.indexOf(packageName);
String className = filePath.substring(i, filePath.length() - 6);
//不是一个内部类
boolean notInnerClass = className.indexOf("$") == -1;
if (notInnerClass) {
//根据类名加载class对象
Class aClass = ClassUtil.forName(className);
if (aClass != null) {
classes.add(aClass);
}
}
}
}
return classes;
}
/**
* 在jar文件中找出该文件夹中在package中的class
* @param packageName 需要获取类全路径的包名(如:com , com.lang 等等)
* @param filePath 当前资源的路径 /C:/Users/21454/.IntelliJIdea2018.2/system/captureAgent/debugger-agent-storage.jar
* @return classes 一个(空的)set集合,会将指定包中的所有class装入该set集合中
*/
static List<Class<?>> getClassByJarPath(String packageName, String filePath) throws IOException {
List<Class<?>> classes = new ArrayList<Class<?>>();
//获取指定文件下的jar包
JarFile jarFile = new URLJarFile(new File(filePath));
//获取jar包中的所有文件资源
Enumeration<JarEntry> entries = jarFile.entries();
//遍历指定jar包中的所有资源
while (entries.hasMoreElements()) {
//路径 com/
JarEntry jarEntry = entries.nextElement();
//将路径中的 / 换成 .
String jarEntryName = jarEntry.getName().replace("/", ".");
//如果是在package下的class文件(因为除了.class文件还会有 文件夹,.xml文件等其他文件,而我们只需要.class文件)
boolean trueClass = jarEntryName.endsWith(".class") && jarEntryName.startsWith(packageName);
//不是一个内部类(内部类都是以 $ 开头的.class文件)
boolean notInnerClass = jarEntryName.indexOf("$") == -1;
if (trueClass && notInnerClass) {
//com.intellij.execution.TestDiscoveryListener.class换成com.intellij.execution.TestDiscoveryListener
String className = jarEntryName.substring(0, jarEntryName.length() - 6);
System.out.println(className);
//根据类名加载class对象
Class aClass = ClassUtil.forName(className);
if (aClass != null) {
classes.add(aClass);
}
}
}
return classes;
}
public static void main(String[] args) throws IOException {
List<Class<?>> list = getClassByPackage("com.manage.fcz.commonPackages.aopSecond");
for(Class c : list){
System.out.println(c);
}
}
}
4.测试
@Controller
public class TestController {
@Autowired
private TestService testService;
@RequestMapping("/aoptest")
@ResponseBody
public String test(){
Map<String, String> params = new HashMap<String, String>();
params.put("identify", "com.test");
testService.doSave(params);
return "二次开发AOP测试";
}
}
public interface TestService {
public void doSave(Map<String, String> params);
}
@Service("testService")
public class TestServiceImpl implements TestService {
@ExtAopMethod("开始执行二次开发AOP")
@Override
public void doSave(Map<String, String> params) {
System.out.println(params.get("identify"));
String extName = params.get("identify");
}
}
@ExtAop("com.test")
public class TestExt extends ExtMeta {
@Override
public void aroundSave(ProceedingJoinPoint pjp) {
System.out.println("around");
try {
//环绕通知才有的一个方法,用于启动目标方法执行
pjp.proceed();
System.out.println("aroundafter");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
super.aroundSave(pjp);
}
@Override
public Map<String, Object> beforeSave(Map<String, Object> params) {
System.out.println("before");
return super.beforeSave(params);
}
@Override
public void afterSave(Map<String, Object> params) {
System.out.println("after");
super.afterSave(params);
}
@Override
public void afterReturnSave(Map<String, Object> params) {
System.out.println("afterReturn");
super.afterReturnSave(params);
}
@Override
public void afterThrowSave(Map<String, Object> params) {
System.out.println("afterThrow");
super.afterThrowSave(params);
}
@Override
public void around(ProceedingJoinPoint pjp) {
super.around(pjp);
}
}
来源:CSDN
作者:zoujiayu
链接:https://blog.csdn.net/zoujiayu/article/details/103799142