Java动态代理

180 阅读2分钟

代理模式

概念:代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。

java动态代理的应用在Spring中处处皆是,例如

  • Spring Aop
  • Dubbo 远程调用代理请求
  • @Transactional 实现日志

截取一个项目error日志可以看到Spring @Transction的代理是通过cglib实现的

	at com.robbendev.app.service.impl.AppService.checkDataSource(AppService.java:526)
	at com.robbendev.app.service.impl.AppService$$FastClassBySpringCGLIB$$6a31b7b.invoke(<generated>)

下面Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理。

Java原生动态代理

现在有一个接口Hello和他的实现类HelloImpl

// 接口
interface Hello{
    String sayHello(String str);
}
// 实现
class HelloImp implements Hello{
    @Override
    public String sayHello(String str) {
        return "HelloImp: " + str;
    }
}

现在要通过日志记录对sayHello()的调用

静态代理

使用静态代理的话

// 静态代理方式
class StaticProxiedHello implements Hello{
    ...
    private Hello hello = new HelloImp();
    @Override
    public String sayHello(String str) {
        logger.info("You said: " + str);
        return hello.sayHello(str);
    }
}

动态代理

Java原生的动态代理是基于接口的。

  1. 首先实现一个InvocationHandler,方法调用会被转发到该类的invoke()方法。
class LogInvocationHandler implements InvocationHandler{
    ...
    private Hello hello;
    public LogInvocationHandler(Hello hello) {
        this.hello = hello;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("sayHello".equals(method.getName())) {
            logger.info("You said: " + Arrays.toString(args));
        }
        return method.invoke(hello, args);
    }
}
  1. 然后在需要使用Hello的时候,通过JDK动态代理获取Hello的代理对象。
Hello hello = (Hello)Proxy.newProxyInstance(
    // 1. 类加载器
    getClass().getClassLoader(), 
    // 2. 代理需要实现的接口,可以有多个
    new Class<?>[] {Hello.class}, 
    // 3. 方法调用的实际处理者
    new LogInvocationHandler(new HelloImp()));
    
System.out.println(hello.sayHello("I love you!"));

Cglib动态代理

CGLIB代理可以不用实现接口

现在有一个没有实现任何接口的类HelloConcrete

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}
  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
  1. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

结语

  • Jdk原生动态代理,只能支持接口实现的方式。
  • Cglib无论目标对象有没有实现接口都可以。
  • final方法不能重仔,所以Cglib不能处理这种情况。