(转载)AOP知识

基础知识

AOP的3个实现层面

AOP就是面向切面编程,可以从3个层面来实现AOP

  1. 编译期:修改源代码。
  2. 字节码加载前:修改字节码。
  3. 字节码加载后:动态创建代理类的字节码。

    AOP的3个实现层面

    实现机制比较

    AOP的基础

    Joinpoint

    拦截点,如某个业务方法。

    Pointcut

    Joinpoint的表达式,表示拦截哪些方法。一个Pointcut对应多个Joinpoint。

    Advice

    要切入的逻辑。
  • Before Advice:在方法前切入。
  • After Advice:在方法后切入,抛出异常时也会切入。
  • After Returning Advice:在方法返回后切入,抛出异常则不会切入。
  • After Throwing Advice:在方法抛出异常时切入。
  • Around Advice:在方法执行前后切入,可以中断或忽略原有流程的执行。

    JoinPoint、Pointcut、Advice之间的关系


    织入器通过在切面中定义pointcut来搜索目标(被代理类)的JoinPoint(切入点),然后把要切入的逻辑(Advice)织入到目标对象里,生成代理类。

    动态字节码生成

    原理

    运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中。所以使用Cglib实现AOP不需要基于接口,Cglib底层是实现ASM的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public class CglibAopDemo {

    public static void main(String[] args) {
    byteCodeGe();
    }

    public static void byteCodeGe() {
    // 创建一个织入器
    Enhancer enhancer = new Enhancer();
    // 设置父类
    enhancer.setSuperclass(Business.class);
    // 设置需要织入的逻辑-InvocationHandler
    enhancer.setCallback(new LogIntercept());
    // 创建子类对象
    IBusiness2 newBusiness = (IBusiness2) enhancer.create();
    newBusiness.doSomeThing2();
    }

    /**
    * 记录日志
    *
    * 类似InvocationHandler,织入新的逻辑
    *
    */
    public static class LogIntercept implements MethodInterceptor {

    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
    throws Throwable {
    // 执行原有逻辑
    Object rev = proxy.invokeSuper(target, args);
    // 执行织入的日志
    if (method.getName().equals("doSomeThing2")) {
    System.out.println("记录日志");
    }
    return rev;
    }
    }
    }

字节码修改

在类加载到JVM之前直接修改某些类的方法,并将切入逻辑织入到这个方法里,然后将修改后的字节码文件交给虚拟机运行。
Javassist是一个编辑字节码的框架,可以很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。这比使用Cglib实现AOP更加高效(不用生成代理类),并且没太多限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void aop() throws NotFoundException, CannotCompileException, InstantiationException,
IllegalAccessException {
// 获取存放CtClass的容器ClassPool
ClassPool cp = ClassPool.getDefault();
// 获取class
CtClass cc = cp.get("model.Business");
// 获得指定方法名的方法
CtMethod m = cc.getDeclaredMethod("doSomeThing");
// 在方法执行前插入代码
m.insertBefore("{ System.out.println(\"记录日志\"); }");
// 执行方法
((Business) cc.toClass().newInstance()).doSomeThing();
}

字节码转换器

使用Instrumentation,它是Java 5提供的新特性,使用Instrumentation,可以构建一个字节码转换器,在字节码加载前进行转换。本节使用Instrumentation和Javassist来实现AOP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* jdk自带的字节码转换器
*/
public class MyClassFileTransformer implements ClassFileTransformer {

/**
* 字节码加载到虚拟机前会进入这个方法
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println(className);
// 如果加载Business类才拦截
if (!"model/Business".equals(className)) {
return null;
}
// javassist的包名是用点分割的,需要转换下
if (className != null && className.indexOf("/") != -1) {
className = className.replaceAll("/", ".");
}
try {
CtClass cc = ClassPool.getDefault().get(className);
CtMethod m = cc.getDeclaredMethod("doSomeThing");
m.insertBefore("{ System.out.println(\"记录日志\"); }");
return cc.toBytecode();
} catch (NotFoundException e) {
} catch (CannotCompileException e) {
} catch (IOException e) {
// 忽略异常处理
}
return null;
}

/**
* 注册转换器
*/
public static void premain(String options, Instrumentation ins) {
// 注册我自己的字节码转换器
ins.addTransformer(new MyClassFileTransformer());
}

}

1
2
3
4
public static void main(String[] args) { 
new Business().doSomeThing();
new Business().doSomeThing2();
}

配置

需要告诉JVM在启动main()之前,需要先执行premain()(注册转换器)。首先需要将premain()所在的类打成jar。并修改该jar里的META-INF\MANIFEST.MF文件。

1
2
Manifest-Version: 1.0 
Premain-Class: bci.MyClassFileTransformer

然后在JVM的启动参数里加上:

1
-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar

执行结果

1
2
3
4
5
6
7
8
model/Business
model/IBusiness
model/IBusiness2
执行业务逻辑2
记录日志
执行业务逻辑1
java/lang/Shutdown
java/lang/Shutdown$Lock
# Spring

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×