什么是代理
代理是指在不改变目标对象代码的情况下,可以控制对目标对象的访问,可以在其前后增加自己的业务处理代码,甚至阻止对目标对象的方法的访问。
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动态代理,被代理类无需实现任何接口,就可以得到功能增强。