Spring AOP(动态代理动态字节码)精华一页纸

    xiaoxiao2021-04-15  65

    1、AOP


    AOP作为一种设计理念, 拦截方法执行前后, 提供一些辅助功能。实际上, 在AOP火爆起来之前, 已经存在了很多AOP实现的理念

    比如一些设计模式就体现了AOP的思想 Decorator(装饰者) Observer(观察者) Chain of Responsibility(责任链) ...

    一些现有的使用场景, 比如 Servlet 拦截器;比如 Java 集合中 Collections 提供了 并发 | 只读 等等拦截的功能。 除了常说的 鉴权、日志等用途, 实际使用过程中, 事务和缓存的应用意义非常大, 在框架设计上, 把缓存和事务的功能从应用功能中独立出来。从而实现 职责单一的思想。还能对调用堆栈、性能分析。比如, 在不破坏现有代码的情况下,统计方法的执行时间。

    2、动态代理


    针对每一种类型,都可以采用 Collections的做法, 实现多个静态代理的类. 这么做的优点是性能较好, 而且代码结构相对清晰;缺点是代码层次多, 冗余代码多。java提供了另一种机制 - 动态代理。

    接口 public interface AOP { public String aop(); }

    目标类 public class AOPObject implements AOP{ @Override public String aop() { return this.getClass().getName(); } }

    代理类 public class AOPProxy implements InvocationHandler{ private AOP target;

    public AOPProxy(AOP target){ this.target = target; }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("aop---"); return method.invoke(target, args); } } 使用

    AOP aop = (AOP)Proxy.newProxyInstance(AOP.class.getClassLoader(), new Class[]{AOP.class}, new AOPProxy(new AOPObject())); aop.aop();

    核心在于Proxy类通过反射把所有的方法调用都转换成 InvocationHandler 的invoke调用。 使用动态代理的注意点: 只能拦截有接口的对象 和接口要在同一个类加载器下

    如果没有的对象, 如何拦截?

    3、动态字节码


    因为Class的设计特点, 可以动态加载Class;所以产生了很多, 动态生成字节码-动态加载的技术,实现拦截。

    I、Instrumentation 指令改写 Instrumentation(指令改写), Java提供的这个功能开口, 可以向java类文件添加字节码信息。可以用来辅助日志信息,记录方法执行的次数,或者建立代码覆盖测试的工具。通过该方式,不会对现有应用功能产生更改。 a、静态方式 PreMain manifest文件 指定 Premain-Class 实现类 实现 ClassFileTransformer 接口的transform 方法

    b、动态方式 AgentMain manifest文件 指定Agent-Class 实现类 Attach Tools API 把代码绑定到具体JVM上 (需要指定绑定的进程号)

    -- 这个功能正常的用途是JVM管理的一个功能,可以监控系统运行的;原理上也可以用在系统增强上面,不过premain和agentmain是针对整个虚拟机的钩子(hook),每个类装载进来都会执行,所以很少使用在功能增强上

    II、asm  原理是 读入 现有Class 的字节码, 生成结构树, 通过二次开发接口, 提供增强功能, 然后再把字节码写入 a、继承 ClassAdapter 获取Class内域和方法的访问器 ClassVisitor b、继承 MethodAdapter 实现方法的覆盖 c、通过 ClassReader 和 ClassWriter 修改原先的实现类源码

    III、CGLib (Code Generation Library) 因为 asm可读性较差, 产生了更易用的框架 cglib,其底层使用的是 asm 接口;这个技术被很多AOP框架使用,例如 Spring AOP 和dynaop ,为她们提供方法的 interception(拦截) hibernate使用 cglib 来代理单端 single-ended(多对一和一对一)关联 EasyMoc和jMock通过使用moke 对象来测试java代码包

    a、Enhancer 类似于 动态代理的 Proxy 提供代理的对象 b、MethodInterceptor 类似于 动态代理的 InvocationHandler 提供拦截逻辑

    对应动态管理的例子, 就是类似如下代码 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(AOPObject.class); enhancer.setCallback(new AOPProxy()); AOPObject aop = (AOPObject)enhancer.create(); aop.aop();

    真实的接口和架构要比者复杂很多,最基本调用就是如此

    IV、javasist 这是另外一种字节码生成技术, 通过java 文本方式, 来实现字节码的替换;不像 asm 是字节层面的替换, javasist 封装了底层细节, 直接把java 代码文本翻译替换。

    -- 一个经典的打印执行时间的例子 CtClass clas = ClassPool.getDefault().get("StringBuilder");

    CtMethod method = cls.getDeclaredMethod(methodName); String replaceMethodName = methodName + "$impl"; method.setName(replaceMethodName); CtMethod oldMethod = CtNewMethod.copy(method, methodName, cls, null);

    String type = method.getReturnType().getName(); StringBuffer body = new StringBuffer(); body.append("{\nlong start = System.currentTimeMillis();\n"); if (!"void".equals(type)) { body.append(type + " result = "); } body.append(replaceMethodName + "($$);\n"); // 第一个$符号是关键字,第二个是参数

    body.append("System.out.println(\"Call to method " + methodName + " took \" +\n (System.currentTimeMillis()-start) + " + "\" ms.\");\n"); if (!"void".equals(type)) { body.append("return result;\n"); } body.append("}");

    oldMethod.setBody(body.toString()); cls.addMethod(oldMethod);

    System.out.println("Interceptor method body:"); System.out.println(body.toString());

    V、Bcel/Serl 还有很多动态字节码技术, 不做介绍, 有需要的可以去了解一下

    4、Spring AOP


    有了动态代理 + 动态字节码增强 技术, AOP 拦截的实现技术已经完备。 是时候介绍一下 AOP的一些概念了。

    I、aopalliance -- AOP联盟的API接口规范 通知(Advice): 何时(Before,After,Around,After还有几个变种) 做什么 连接点(JoinPoint): 应用对象提供可以切入的所有功能(一般是方法,有时也是参数) 切点(PointCut): 通过指定,比如指定名称,正则表达式过滤, 指定某个/些连接点, 切点描绘了 在何地 做 切面(Aspect): 通知 + 切点 何时何地做什么 引入(Introduction):向现有类添加新的属性或方法 织入(Weaving): 就是将切面应用到目标对象的过程

    -- aopalliance 包含了一些常用的接口, 实现 AOP 功能的框架, 一般都会实现这些接口

    II、Spring AOP

    需要有依赖的, 继承自 Spring AOP 体系的用法

    扩展以下接口 org.aopalliance.intercept.MethodInterceptor -- 标准AOP接口,可以对方法做任意增强 org.springframework.aop.MethodBeforeAdvice -- 继承自标准接口Advice,对方法做前增强 org.springframework.aop.AfterReturningAdvice -- 继承自标准接口Advice,对方法做后增强 org.springframework.aop.ThrowsAdvice -- 继承自标准接口Advice,对方法做异常增强,该接口是一个空方法接口,约定方法是 afterThrowing ,参数是任意定义的异常。

    可以自己编写代码, 编码式来实现 AOP 的调用过程。

    a、手工代理 - FactoryBean 适配 代理 方式 org.springframework.aop.framework.ProxyFactoryBean <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref local="xxx"/> </property> <property name="proxyInterfaces"> <value>xxx</value> </property> <property name="interceptorNames"> <list> <value>beforeAdive</value> <value>afterAdive</value> </list> </property> </bean>

    b、自动代理 <bean id="autoProxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean id="pointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="pointcutBeforeAdive"/> </property> <property name="patterns"> <list> <value>.*add.*</value> -- 只匹配名称中含有add方法的 </list> </property> </bean> Spring自动去搜索拦截

    利用Aspect , 不需要继承 Spring AOP 体系, 普通类POJO 能实现的拦截, 非注解方式下, 实现完全解耦的用法(AspectJ + Spring AOP)

    c、基于Aspect 注解 配置 <aop:aspectj-autoproxy />,目的是启用Spring的 AnnotationAwareAspectJAutoProxyCreator 类,由该类来扫描注解,把代理的通知和切入点等等载入,(代替上下文加载载入),实现代理的过程 每个通知的 实现类,通过 Aspecj 的标注,实现 切入点定义 和 方法 @Aspect public class POJOAdvisor { // 定义切入点,此处 doActionPoint 即切点的名字,是一个空方法 主要目的给Pointcut注解使用,不然Poingcut注解无法使用 @Pointcut("execution(* com.aop.annotation.AopInterface.doAction(..)) ") public void doActionPoint(){

    }

    @Before("doActionPoint()") public void beforePointCut(){ System.out.println(getClass().getName() + ":beforePointCut() by annotation"); }

    @After("doActionPoint()") public void afterPointCut(){ System.out.println(getClass().getName() + ":afterPointCut() by annotation"); }

    d、基于Aspect 配置 - 此方式为最常用方式 <bean id="pointcutBeforeAdive" class="com.aop.POJOAdvisor"/> <aop:config> <aop:aspect ref="pointcutBeforeAdive"> <!-- within 可以指定包下的类,bean 可以指定具体类 --> <aop:before pointcut="execution(* com.aop.AOP.aop(..))" method="beforePointCut"/> </aop:aspect> </aop:config >

    III、ApsectJ 增强功能 Aspect 不仅能拦截方法, 还能扩充方法和功能

    <!-- 依赖 asectj.jar aspectjwear.jar --> <aop:config> <aop:aspect ref="pojoadvisor"> <!-- within 可以指定包下的类,bean 可以指定具体类 --> <aop:before pointcut="execution(* com.aop.AopInterface.doAction(..)) and within(com.aop.*)" method="beforePointCut"/> <!-- after-returning/after-throwing --> <aop:after pointcut="execution(* com.aop.AopInterface.doAction(..)) and bean(targetaop)" method="afterPointCut"/> </aop:aspect> <!-- 如果pointcut 是一样的,可以抽取出来单独定义 -->

    <aop:aspect ref="pojoadvisor"> <aop:pointcut id="doActionPoint" expression="execution(* com.aop.AopInterface.doAction(..))"/> <aop:before pointcut-ref="doActionPoint" method="beforePointCut"/> <aop:after pointcut-ref="doActionPoint" method="afterPointCut"/> </aop:aspect> <!-- 环绕相对于普通 JAVA对象而言,多了一个 ProceedingJoinPoint 的传入参数,依赖了AspectJ 功能 -->

    <aop:aspect ref="aroundAdvisor"> <aop:around pointcut-ref="doActionPoint" method="around"/> </aop:aspect>

    <!-- 给通知 传递参数, 主要用在 可以检查传入的参数,被拦截的方法调用中,传入的参数是否合法 --> <aop:aspect ref="intercept"> <aop:pointcut id="action" expression="execution(* com.aop.AopParaInterface.doAction(..)) and args(msg)"/> <aop:before pointcut-ref="action" method="intercept" arg-names="msg"/> </aop:aspect>

    <!-- 利用拦截功能,为对象增加新的功能,其实就是动态代理,在invoke时,发现是注册的新方法,改调用新方法 --> <aop:aspect> <aop:declare-parents types-matching="旧的对象+" implement-interface="新增接口" default-impl="委托对象" />

    </aop:aspect>

    </aop:config>

    Aspect 拦截 语法 execution(* 方法路径(..)) within( 指定方法) args 传入参数

    AOP的概念和知识点很多, 只要能理解其用途, 无非就是两个部分 需要拦截哪些模块 -- 定位 (从包-类-方法, Spring最小粒度就到方法) 增加哪些功能 -- 增加 (普通POJO类、或者继承扩展Spring AOP拦截器)

    转载请注明原文地址: https://ju.6miu.com/read-671103.html

    最新回复(0)