Java静态代理和动态代理

什么是代理

代理是指在不改变目标对象代码的情况下,可以控制对目标对象的访问,可以在其前后增加自己的业务处理代码,甚至阻止对目标对象的方法的访问。

Java中代理的实现主要包括静态代理和动态代理,其中,动态代理又主要有JDK动态代理和cglib动态代理两种方式。

静态代理

静态代理就是使用代理设计模式,代理类由程序员自己编写,在编译期就已经确定好了。

代理模式类图如下:

举个例子,

public interface HelloService {
    public void say();
}

public class HelloServiceImpl implements HelloService{

    @Override
    public void say() {
        System.out.println("hello world");
    }
}

这段代码定义了一个接口及其实现类,这就是代理模式中的目标对象和目标对象的接口。接下来定义代理对象。

public class HelloServiceProxy implements HelloService{

    private HelloService target;

    public HelloServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public void say() {
        System.out.println("记录日志");
        target.say();
        System.out.println("清理数据");
    }
}

上面就是一个代理类,他也实现了目标对象的接口,并且扩展了say方法。下面是一个测试类:

public class Main {

    @Test
    public void testProxy(){
        //目标对象
        HelloService target = new HelloServiceImpl();
        //代理对象
        HelloServiceProxy proxy = new HelloServiceProxy(target);
        proxy.say();
    }

}
记录日志
hello world
清理数据

Process finished with exit code 0

这就是一个简单的静态的代理模式的实现。代理模式中的所有角色(代理对象、目标对象、目标对象的接口)等都是在编译期就确定好的。

通过静态代理,我们可以实现:

  • 控制真实对象的访问权限,通过代理对象控制对真实对象的使用权限。
  • 避免创建大对象,通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
  • 增强真实对象的功能。

虽然静态代理模式很好用,但是静态代理还是存在一些局限性的,比如使用静态代理模式需要程序员手写很多代码,这个过程是比较浪费时间和精力的。一旦需要代理的类中方法比较多,或者需要同时代理多个对象的时候,这无疑会增加很大的复杂度。

有没有一种方法,可以不需要程序员自己手写代理类呢。这就是动态代理啦。

动态代理中的代理类并不要求在编译期就确定,而是可以在运行期动态生成,从而实现对目标对象的代理功能。

动态代理

Java中实现动态代理主要有两种方式:JDK动态代理和cglib动态代理。

JDK动态代理

java.lang.reflect包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。

Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler)方法创建一个代理对象,其中,各参数的含义分别是:

  • ClassLoader,定义代理类的类加载器,null表示使用默认的类加载器。
  • Class<?>[],代理类需要实现的接口列表。
  • InvocationHandler,指派方法调用的调用处理程序。

所生成的代理类会具有所有指定接口所需要的方法,以及Object类的全部方法(如equals, toString等)。然而,并不是在运行时定义这些方法的代码实现,而是这些方法都会调用InvocationHandler的invoke方法。InvocationHandler.invoke(Object, Method, Object[])各参数含义如下:

  • Object,代理对象。
  • Method,对应于在代理实例上调用的接口方法的 Method 实例。
  • Object[],传给Method对象的参数对象数组。

下面这个例子借助JDK动态代理,在被代理对象方法调用之前,打印被调方法的名字和入参:

// 业务接口类
public interface UserService {

    void add(User user);
}

// 业务实现类
public class UserServiceImpl implements UserService {
    @Override
    public void add(User user) {
        System.out.println("begin to add user: " + user.getId() + " " + user.getName() + ".");
    }

    @Override
    public String toString(){
        return "userserivce impl";
    }
}

// pojo类
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class User {
    private String id;
    private String name;
}

// InvocationHandler实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Optional;

public class TraceHandler implements InvocationHandler {

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print("开始调用" + method.getName() + "...参数是: ");
        Optional.ofNullable(args).ifPresent(params -> {
            for (Object param : params) {
                System.out.print(param.toString() + " ");
            }
        });
        System.out.println();
        return method.invoke(target, args);
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
    }

}

// 测试类
public class Main {

    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        UserService userService = new UserServiceImpl();
        TraceHandler traceHandler = new TraceHandler(userService);
        Object proxy = traceHandler.getProxy();
        UserService userServiceProxy = (UserService) proxy;
        userServiceProxy.add(new User("121250102", "nlz"));
        System.out.println(userServiceProxy.toString());
    }

}
开始调用add...参数是: User(id=121250102, name=nlz) 
begin to add user: 121250102 nlz.
开始调用toString...参数是: 
userserivce impl

Process finished with exit code 0

可以看到,无论是调用代理对象所实现的接口的方法,还是从Object继承的toString()方法,都会转由InvocationHandler.invoke去处理。

我们可以通过设置系统属性sun.misc.ProxyGenerator.saveGeneratedFiles为true来生成动态代理类的class文件。上面的示例中已经打开了此开关,在IJ工程的com/sun/proxy下可以看到生成了一个$Proxy0.class。用luyten反编译查看其内容:

package com.sun.proxy;

import java.lang.reflect.*;
import com.example.demo.jdkdynamicproxy.*;

public final class $Proxy0 extends Proxy implements UserService
{
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    
    public $Proxy0(final InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    
    public final boolean equals(final Object o) {
        try {
            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final String toString() {
        try {
            return (String)super.h.invoke(this, $Proxy0.m2, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final void add(final User user) {
        try {
            super.h.invoke(this, $Proxy0.m3, new Object[] { user });
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    public final int hashCode() {
        try {
            return (int)super.h.invoke(this, $Proxy0.m0, null);
        }
        catch (Error | RuntimeException error) {
            throw;
        }
        catch (Throwable t) {
            throw new UndeclaredThrowableException(t);
        }
    }
    
    static {
        try {
            $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
            $Proxy0.m3 = Class.forName("com.example.demo.jdkdynamicproxy.UserService").getMethod("add", Class.forName("com.example.demo.jdkdynamicproxy.User"));
            $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
        }
        catch (NoSuchMethodException ex) {
            throw new NoSuchMethodError(ex.getMessage());
        }
        catch (ClassNotFoundException ex2) {
            throw new NoClassDefFoundError(ex2.getMessage());
        }
    }
}

可以看到,动态生成的代理类继承了Proxy类,同时实现了UserService接口,也就是我们在Proxy.newProxyInstance时所指定的接口列表里的接口。然后可以看到,该代理类除了实现了UserService的add方法外,还覆盖了从Object继承的equals、hashCode、toString等三个方法,具体实现为调用InvocationHandler的invoke方法,印证了我们上面所说的。

JDK动态代理中,被代理对象必须要实现一个或多个接口,如果想代理没有实现任何接口的类,我们可以使用cglib来实现。

cglib动态代理

cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的拦截。

cglib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

Maven项目添加cglib依赖:

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Spring核心包中已经包括了cglib的功能,所以也可以直接引入spring-core。

cglib动态代理demo:

// 被代理的类

public class AirlineCompany {

    /**
     * 卖票
     *
     * @return true表示出票成功, false表示出票失败
     */
    public boolean saleTickets() {
        System.out.println("航空公司出票");
        return true;
    }
}

// 代理类

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 机票代理商
 */
public class PlaneTicketAgent implements MethodInterceptor {

    /**
     * 拦截被代理对象中除final之外的所有方法.
     * 所有生成的代理方法(除过final修饰的方法)都调用此方法而不是原始方法,
     * 原始方法可以由使用方法对象的普通反射调用, 或者使用MethodProxy对象调用(这种方式更快).
     * 之所以final修饰的方法不能被cglib代理, 是因为cglib采用extends被代理对象的方式来重写相应的方法,
     * 以此来拦截目标方法并在目标方法前后加上自己的业务处理代码. 而final修饰的方式是不能被重写的.
     *
     * @param proxyObj    代理对象
     * @param method      被拦截的方法
     * @param param       方法参数
     * @param methodProxy 代理方法
     * @return 与代理方法的签名兼容的返回值
     */
    @Override
    public Object intercept(Object proxyObj, Method method, Object[] param, MethodProxy methodProxy) throws Throwable {
        // 可以在此处添加前置业务处理代码
        System.out.println("由机票代理商来卖票");
        Object result = methodProxy.invokeSuper(proxyObj, param);
        // 可以在此处添加后置业务处理代码
        if ((boolean) result) {
            System.out.println("已顺利出票");
        } else {
            System.out.println("出票遇到一点麻烦");
        }
        return result;
    }

}

// 测试类

import org.springframework.cglib.proxy.Enhancer;

public class Main {
    public static void main(String[] args) {
        // 创建Enhancer实例,也就是cglib中的一个class generator
        Enhancer enhancer = new Enhancer();
        // 设置目标类
        enhancer.setSuperclass(AirlineCompany.class);
        // 设置拦截对象
        enhancer.setCallback(new PlaneTicketAgent());
        // 生成代理类并返回代理类的实例
        AirlineCompany airlineCompany = (AirlineCompany) enhancer.create();
        boolean result = airlineCompany.saleTickets();
        if (result) {
            System.out.println("出票成功");
        } else {
            System.out.println("出票失败");
        }
    }
}
由机票代理商来卖票
航空公司出票
已顺利出票
出票成功

Process finished with exit code 0

可以看到,通过cglib动态代理,被代理类无需实现任何接口,就可以得到功能增强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值