Spring AOP 动态代理

动态代理

  • 动态代理分类
  • 实现一段程序执行时间的监测
    • 1.普通实现
    • 2.JDK动态代理实现
    • 3.CGLib动态代理实现
  • 小结
  • Spring中动态代理类

文章主要是《精通Spring 4.x企业应用开发实战》笔记

AOP是Aspect Oriented Programing,“面向方面编程”
Spring AOP底层使用动态代理技术在运行期织入增强代码,生成代理类或者子类,这个类中已经融合了逻辑代码和增强代码,在AOP中我们实际上调用的就是这个类

动态代理分类

使用了两种代理机制:
1.基于JDK的动态代理;
2.基于CGLib的动态代理。

实现一段程序执行时间的监测

1.普通实现

当我们在代码中进行一些监测时,可以这么写代码;先定义一个监测接口ForumService ,接口实现类ForumServiceImpl,监测实现类PerformanceMonitor,监测信息类MethodPerformance

这是监测接口ForumService

package noaop;

/**
 * 被监测类接口
 */
public interface ForumService {
    void removeTopic(int topicId);
    void removeForum(int forumId);
}

这是接口实现类ForumServiceImpl,在方法中前后分别调用了PerformanceMonitor监测类的begin和end方法进行监测执行时间

package noaop;

/**
 * 被监测类
 */
public class ForumServiceImpl implements ForumService {
    @Override
    public void removeTopic(int topicId) {
        PerformanceMonitor.begin("noaop.ForumServiceImpl.removeTopic");//执行前时间
        System.out.println("模拟删除topic记录:" + topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        PerformanceMonitor.end();//执行后时间
    }

    @Override
    public void removeForum(int forumId) {
        PerformanceMonitor.begin("noaop.ForumServiceImpl.removeForum");
        System.out.println("模拟删除forum记录:" + forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        PerformanceMonitor.end();
    }
}

这是监测实现类PerformanceMonitor,使用ThreadLocal实现线程安全

package noaop;

/**
 * 性能监测实现类
 */
public class PerformanceMonitor {
    //保存与当前线程相关的性能监测信息
    private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();

    public static void begin(String method) {
        System.out.println("begin monitor...");
        MethodPerformance mp = new MethodPerformance(method);
        performanceRecord.set(mp);
    }

    public static void end() {
        System.out.println("end monitor...");
        MethodPerformance mp = performanceRecord.get();
        mp.printPerformance();
    }
}

监测信息类MethodPerformance

package noaop;

/**
 * 记录想性能监测信息类
 */
public class MethodPerformance {
    private long begin;
    private long end;
    private String serviceMethod;

    public MethodPerformance(String serviceMethod) {
        this.serviceMethod = serviceMethod;
        this.begin = System.currentTimeMillis();
    }

    public void printPerformance() {
        end = System.currentTimeMillis();
        long elapse = end - begin;
        System.out.println(serviceMethod + "花费" + elapse + "毫秒");
    }
}

执行以上代码

public class TestForumService {
    public static void main(String[] args) {
        ForumService forumService = new ForumServiceImpl();
        forumService.removeForum(10);
        forumService.removeTopic(1012);
    }
}

结果为

begin monitor...
模拟删除forum记录:10
end monitor...
noaop.ForumServiceImpl.removeForum花费55毫秒
begin monitor...
模拟删除topic记录:1012
end monitor...
noaop.ForumServiceImpl.removeTopic花费21毫秒

Process finished with exit code 0

以上实现需要在每个监测的方法中都加入begin和end方法,对业务代码来说是没有用的,而且很繁琐

2.JDK动态代理实现

JDK动态代理主要使用java.lang.reflect包中的InvocationHandlerProxy两个类
创建PerformanceHandler实现InvocationHandler接口,target是监测目标类。proxy是最终生成的代理实例,method是被代理目标的某个方法,args是被代理实例某个方法的参数。invoke在调用PerformanceHandler类方法时被调用,并向其传递Method对象和原始的参数。在实现接口的invoke方法中监测代码和业务代码融合在一起,利用反射执行业务代码

package aop;

import noaop.PerformanceMonitor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PerformanceHandler implements InvocationHandler {
    private Object target;

    public PerformanceHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
        Object obj = method.invoke(target, args);
        PerformanceMonitor.end();
        return obj;
    }
}

调用上面的PerformanceHandler类使监测代码和业务代码融合在一起。使用Proxy.newProxyInstance创建一个代理类,第一个参数是一个类加载器,第二个参数是创建代理实例所需实现的一组接口,第三个参数是整合业务逻辑和横切逻辑的编织器对象

调用以上代理对象

package aop;

import noaop.ForumService;
import noaop.ForumServiceImpl;

import java.lang.reflect.Proxy;

public class TestForumService {
    public static void main(String[] args) {
        //希望被代理的目标类
        ForumService target = new ForumServiceImpl();
        //将业务类和横切带吗交织在一起
        PerformanceHandler handler = new PerformanceHandler(target);

        ForumService proxy = (ForumService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );
        proxy.removeForum(10);
        proxy.removeTopic(1012);
    }
}

结果为

begin monitor...
模拟删除forum记录:10
end monitor...
noaop.ForumServiceImpl.removeForum花费49毫秒
begin monitor...
模拟删除topic记录:1012
end monitor...
noaop.ForumServiceImpl.removeTopic花费22毫秒

Process finished with exit code 0

利用代理对象实现了监测

3.CGLib动态代理实现

使用JDK创建代理有个限制,只能为接口创建实例Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)第二个参数需要接口。如果非接口实现类,则使用CGLib代理。它采用底层字节码技术,生成目标类的子类,在子类中采用方法拦截的技术拦截父类所有方法的调用并织入横切逻辑。

使用前需要导入cglib和asm的jar包
创建CglibProxy代理类
getProxy方法中创建子类,intercept(Object obj, Method method, Object[] args, MethodProxy proxy)拦截所有目标类方法的调用。由于以创建子类的方式生成代理对象,所以不能对目标类中的final和private方法进行代理
obj 表示目标类实例
method 目标类方法的反射对象
args方法的动态入参
proxy代理类实例
intercept方法中,增强代码和业务代码融合在一起,通过代理类调用父类方法

package cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import noaop.PerformanceMonitor;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);//设置需要创建子类的类
        enhancer.setCallback(this);
        return enhancer.create();//通过字节码技术动态创建子类实例
    }

    /**
     * 拦截所有目标类方法的调用
     * @param o 目标类实例
     * @param method 目标类方法的反射对象
     * @param objects 方法的动态入参
     * @param methodProxy 代理类实例
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
        Object result = methodProxy.invokeSuper(o, objects);//通过代理类调用父类方法
        PerformanceMonitor.end();
        return result;
    }
}

运行代码

package cglib;

import noaop.ForumServiceImpl;

public class TestForumService {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
        forumService.removeForum(101);
        forumService.removeTopic(1023);
    }
}

结果为:


begin monitor...
模拟删除forum记录:101
end monitor...
noaop.ForumServiceImpl$$EnhancerByCGLIB$$4d48892f.removeForum花费59毫秒
begin monitor...
模拟删除topic记录:1023
end monitor...
noaop.ForumServiceImpl$$EnhancerByCGLIB$$4d48892f.removeTopic花费22毫秒

Process finished with exit code 0

小结

上述例子只是为了理解Spring AOP的动态代理原理,很多地方需要改进

  1. Spring AOP通过Pointcut(切点)指定哪些类的哪些方法织入横切逻辑
  2. 通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法前后等)
  3. Spring通过Advisor(切面)将Pointcut和Advice组装起来。利用动态代理技术采用统一方式为目标Bean创建织入切面的代理对象。
  4. CGLib创建的动态代理对象比JDK创建的动态代理对象性能高不少。但CGLib在创建动态代理对象时花费的时间却比JDK多。对于singleton的代理对象,比较适合CGLib动态代理技术,反之JDK适合。

Spring中动态代理类

org.springframework.aop.framework中定义了AopProxy,两个实现类是JdkDynamicAopProxyCglibAopProxy。通过ProxyFactorysetInterfaces(Class... interfaces)方法来指定接口,则会调用JdkDynamicAopProxy代理,设置setOptimize(boolean optimize)方法为true,则会调用CglibAopProxy代理

你可能感兴趣的:(Spring,java,spring)