深入AOP原理与应用 – Vince的修炼之路 – 博客频道 – CSDN.NET

深入AOP原理与应用

分类: JAVA 设计模式 2015-01-06 18:33 56人阅读 评论(0) 收藏 举报

AOP(Aspect Oriented Programming)就是面向切面编程,也是一种编程思想,接触了JAVA是Spring框架后我才了解AOP,在我的工作中会经常用到,举个存储分层的例子,就像硬盘、内存和CPU中的寄存器,对应的高性能应用系统会有普通数据库、Redis和本地内存:

http://blog.weirong.li/wp-content/uploads/2016/09/4edbb9ec-b01a-43a6-955a-81d9a285fa7c

那么这里的缓存操作我们可以抽出来统一做,这里我们就用到了AOP,切点就是对数据的存取方法,还有就是调用外部系统的接口获取数据时,我们也可以用AOP来实现统一的缓存操作,我们通常用的AOP的框架是aspectj,实现的原理是动态代理,动态代理的方案有JDK Proxy、cglib等,cglib是代码的动态生成技术,用asm提供的动态生成JAVA字节码的技术,而JDK的动态代理是一种设计模式,依懒接口的实现。写一个AOP简单如下

  1. @Aspect
  2. @Component
  3. publicclass CacheUpdateProcessor {
  4. @Around”@annotation(com.xxx.xxx.cache.CacheUpdate)”
  5. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  6. Object result = joinPoint.proceed();
  7. // 获取被切方法的所有入参
  8. Object[] methodArgs = joinPoint.getArgs();
  9. Signature signature = joinPoint.getSignature();
  10. (signature instanceof MethodSignature) {
  11. } catch (Exception e) {
  12. return result;
@Aspect@Componentpublic class CacheUpdateProcessor {    @Around("@annotation(com.xxx.xxx.cache.CacheUpdate)")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {        Object result = joinPoint.proceed();                try {            // 获取被切方法的所有入参            Object[] methodArgs = joinPoint.getArgs();            Signature signature = joinPoint.getSignature();            if (signature instanceof MethodSignature) {            }        } catch (Exception e) {                    }        return result;    }}

我们还在spring的XML配置文件中加上

  1. aop:aspectj-autoproxyproxy-target-class”true”/>
<aop:aspectj-autoproxy proxy-target-class="true" />

表示使用cglib动态代理技术织入增强,利用cglib的好处是提高了系统的性能,利用注解让spring帮我们完成了自动代码织入,注解@Around中的参数就是织入的切点,around表示包围了整个方法,被切的代码的执行可以通过joinPoint(连接点)来调用,当然除了around的还有其他的织入方式,例如before和after,表示在被切方法前执行和被切方法后执行。

我们可以在around、before或after的方法里进行统一的缓存处理,而在需要进行此类操作的地方只需要加个被设置为切点的注解即可,如果还需要传方法参数以外的数据,可以对自定义的注解进行优化,例如我自定义的的作为切点的注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. @Documented
  4. public@interface CacheUpdate {
  5. CacheKeys key();
  6. Class<?> type();
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documentedpublic @interface CacheUpdate {        CacheKeys key();        Class<?> type();    }

获取注解中的参数方法如下:

  1. Method aopMethod = methodSignature.getMethod();
  2. CacheUpdate param = aopMethod.getAnnotation(CacheUpdate.class
  3. // 获取参数的类
  4. Class clazz = param.type();
  5. CacheKeys keys = param.key();
                Method aopMethod = methodSignature.getMethod();                CacheUpdate param = aopMethod.getAnnotation(CacheUpdate.class);                // 获取参数的类                Class clazz = param.type();                CacheKeys keys = param.key();

对于@around、@before和@after中value的值有个专业的名词:pointcut expression(切点表达式)

Aspectj的源码中是这样说明的:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public@interface Around {
  4. * The pointcut expression where to bind the advice
  5. String value();
  6. * When compiling without debug info, or when interpreting pointcuts at runtime,
  7. * the names of any arguments used in the advice declaration are not available.
  8. * Under these circumstances only, it is necessary to provide the arg names in
  9. * the annotation – these MUST duplicate the names used in the annotated method.
  10. * Format is a simple comma-separated list.
  11. String argNames() default
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Around {    /**     * The pointcut expression where to bind the advice     */    String value();        /**     * When compiling without debug info, or when interpreting pointcuts at runtime,     * the names of any arguments used in the advice declaration are not available.     * Under these circumstances only, it is necessary to provide the arg names in      * the annotation - these MUST duplicate the names used in the annotated method.     * Format is a simple comma-separated list.     */    String argNames() default "";}

pointcut expression的作用就是为了说明切点是什么,在哪,比如是某个注解、是某个方法或某个类中的所有方法等等。

接下来说明AOP是如何实现代码织入的,织入是在类加载前的环节,当某个类被加载时,如果发现有切面,这时会生成一个新的类,这个新的类会被动态地址增加一些JAVA的指令操作,然后加载这个新的类。这个织入的操作其实就像人为的修改class文件一样,如果对java虚拟机的汇编指令熟悉,完全可以手动修改,然而现在只是有专门的程序帮我们完成的这个操作而矣,这个专门的程序熟悉JAVA的指令集,它就是asm,而具体的要把代码加在什么位置,怎么加,则是由具体的上层代码指定,像@around、@before和@after。

ASM中有一个ClassReader和一个ClassWriter,见名就知其义了,前者是用来解析class文件的,而后者是用来写入class文件的,ASM主要的设计思想是Visitor访问者模式,与迭代器模式不同的是访问者模式的访问逻辑是由实现类来决定的,ASM的上层是ClassVisitor,而ClassWriter就是其的一个实现,还有其它的一些实现:

http://blog.weirong.li/wp-content/uploads/2016/09/239f9c45-11d6-4ddd-9bb4-21ae25dbb217

下面是我用ASM写的简单的代码动态生成的例子:

测试类,只有一个名为out的方法

  1. publicclass Test {
  2. public out(String data){
  3. System.out.println(“method out : ” + data);
public class Test {    public void out(String data){                System.out.println("method out : " + data);            }}

动态生成代码,先输出原字节码,分隔符下是新生成的代码

  1. publicclass AsmTest {
  2. publicstatic main(String[] args) throws Exception {
  3. ClassReader cr = ClassReader(Test.class.getName());
  4. ClassWriter cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
  5. System.out.println( String(cw.toByteArray(),”utf-8″
  6. cr.accept( ClassAdapter(cw){
  7. @Override
  8. public MethodVisitor visitMethod( access, String name, String desc,
  9. String signature, String[] exceptions) {
  10. “out”.equals(name)){
  11. MethodVisitor visitor = cv.visitMethod(Opcodes.ACC_PUBLIC, “out””(Ljava/lang/String;)V”
  12. visitor.visitLdcInsn(“Before execute”
  13. visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, “java/io/PrintStream””println””(Ljava/lang/String;)V”
  14. visitor.visitEnd();
  15. return visitor;
  16. return cv.visitMethod(access, name, desc, signature, exceptions);
  17. System.out.println(“———分隔符———”
  18. System.out.println( String(cw.toByteArray(),”utf-8″
public class AsmTest {    public static void main(String[] args) throws Exception {                ClassReader cr = new ClassReader(Test.class.getName());        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);                System.out.println(new String(cw.toByteArray(),"utf-8"));                cr.accept(new ClassAdapter(cw){                        @Override            public MethodVisitor visitMethod(int access, String name, String desc,                    String signature, String[] exceptions) {                                if("out".equals(name)){                                        MethodVisitor visitor = cv.visitMethod(Opcodes.ACC_PUBLIC, "out", "(Ljava/lang/String;)V", null, null);                    visitor.visitLdcInsn("Before execute");                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");                    visitor.visitEnd();                    return visitor;                }                                return cv.visitMethod(access, name, desc, signature, exceptions);            }                    }, 0);        System.out.println("———分隔符———");        System.out.println(new String(cw.toByteArray(),"utf-8"));    }}

测试的结果如下:

http://blog.weirong.li/wp-content/uploads/2016/09/26d6b771-c04f-4579-a731-3910bc6ca584

如果想要了解ASM的,可以要多看看JVM和其指令集了,也就是JAVA的汇编。

最后总结一下,其实AOP重要的是思想,至于如何实现AOP,可以有很多种方式,只是在众多方式中,利用ASM的JAVA字节码动态自动生成的方式可能性能是最好的。

此条目发表在Uncategorized分类目录,贴了, 标签。将固定链接加入收藏夹。