AOP统一管理,统一二次开发入口

本秂侑毒 提交于 2020-01-02 13:40:43

场景

本文主要是封装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);
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!