Java 代理
Java的动态代理是基于接口的,它要求目标类必须实现一个或多个接口,而CGLib是基于类的,它不要求目标类实现任何接口。Java的动态代理是通过反射机制来创建代理对象和调用目标方法的,而CGLib是通过字节码生成技术来创建代理对象和调用目标方法的。Java的动态代理会生成一个实现了目标类所有接口的新类作为代理类,而CGLib会生成一个继承了目标类的子类作为代理类。
Java 代理
1. 概念
Java代理是一种设计模式,它允许在不改变原始类或接口的情况下对其进行增强或修改。在运行时通过创建一个代理对象来控制对原始对象的访问,并允许我们在访问原始对象之前或之后执行一些额外的逻辑,从而实现一些特定的功能,如日志记录、安全控制、缓存处理、远程调用、延迟加载和事务处理等。
1.1 应用场景
-
日志记录:通过代理对象在方法调用前后记录日志。
-
安全控制:通过代理对象控制对原始对象的访问权限。
-
缓存处理:通过代理对象对某些方法的调用结果进行缓存,避免重复计算。
-
远程调用:通过代理对象实现远程方法调用。
-
延迟加载:通过代理对象延迟加载某些对象,提高程序的性能。
-
事务处理:通过代理对象实现事务的提交和回滚。
……
1.2 案例解析
不使用代理的对象增强
假设我们有一个名为 UserService
的类,它提供了两个方法 addUser
和 deleteUser
,代码如下:
public class UserService{
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
public void deleteUser(String name) {
System.out.println("删除用户:" + name);
}
}
现在,我们需要在
addUser
和deleteUser
方法执行前后打印日志。一种实现方式是在每个方法的前后都打印日志,如下所示:
public class UserService{
public void addUser(String name) {
System.out.println("Before addUser");
userService.addUser(name);
System.out.println("After addUser");
}
public void deleteUser(String name) {
System.out.println("Before deleteUser");
userService.deleteUser(name);
System.out.println("After deleteUser");
}
}
在上面的代码中,我们直接在原有类中修改,并在每个方法的前后都打印了日志,这样就能够实现对原有对象的增强。
现在有个问题,当产品经理想加需求,比如在所有操作前(插入或删除)先认证你的权限,没有权限不让你操作。
所以我们需要在原有类中继续修改代码。
那如果再加十个需求呢? 这时候就有两种解决方案,一种是解决产品经理,一种是解决需求。
第一种我就不教了,第二种就是继续改原有类咯。
虽然上面的方式可以实现对象增强,但它缺点也很明显,增强逻辑与目标方法紧密耦合在一起,侵入性较高,不易扩展和维护。
1.3 为什么要使用代理
为了避免上述问题,我们可以使用代理模式,它将对象的访问控制和附加操作分离出来,可以在不修改原有代码的情况下,增加额外的功能或者对原有功能进行增强。代理模式能够更好的解耦:代理对象和被代理对象是相互独立的,这意味着我们可以在不影响被代理对象的情况下,对代理对象进行修改和优化。
2.静态代理
静态代理是在编译时创建的代理对象,需要手动编写代理类。它需要实现与原始类相同的接口,并在代理类中调用原始类的方法。
以下是一个简单的静态代理实现示例,用于在调用原始对象之前和之后记录日志:
// 定义接口
public interface UserService {
void addUser(String name);
}
// 原始类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 代理类
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(String name) {
System.out.println("添加用户之前的日志记录");
userService.addUser(name);
System.out.println("添加用户之后的日志记录");
}
}
// 使用代理
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userService);
proxy.addUser("张三");
}
在上面的示例中,我们定义了一个接口
UserService
和一个实现类UserServiceImpl
。我们创建了一个代理类UserServiceProxy
,它实现了UserService
接口并持有一个UserService
对象。在代理类的addUser
方法中,我们先记录日志,然后调用原始对象的addUser
方法,最后再记录一次日志。在使用代理时,我们创建了一个原始对象
UserServiceImpl
,然后将它传递给代理类UserServiceProxy
的构造函数。我们最终使用代理对象proxy
调用addUser
方法。
相比之下,使用静态代理实现方法增强的主要优点是增强逻辑与目标方法分离,代理对象负责将增强逻辑织入到目标方法中。
静态代理有一些缺点,主要包括:
-
静态代理类和被代理类实现相同的接口,导致接口的数量增加,系统更加臃肿。
-
每一个代理类只能代理一个接口,如果要代理多个接口就需要创建多个代理类。
-
代理类需要手动编写,代码量较大,且容易出错。
3. 动态代理
为了解决静态代理的问题,我们使用动态代理。动态代理可以通过Java的反射机制在运行时动态地生成代理类,无需手动编写代理类。动态代理可以代理多个接口,且可以实现通用的代理逻辑,避免了代码重复,同时也避免了静态代理中需要手动编写代理类的缺点。
Java提供了java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现动态代理。
以下是一个简单的动态代理示例,用于在调用原始对象之前和之后记录日志:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
public interface UserService {
void addUser(String name);
}
// 原始类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 实现InvocationHandler接口
public class UserServiceProxy implements InvocationHandler {
private Object target;
public UserServiceProxy(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("添加用户之前的日志记录");
Object result = method.invoke(target, args);
System.out.println("添加用户之后的日志记录");
return result;
}
}
// 使用代理
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
InvocationHandler handler = new UserServiceProxy(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class<?>[]{UserService.class}, handler);
proxy.addUser("张三");
}
在上面的示例中,我们使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现了动态代理。我们定义了一个接口UserService
和一个实现类UserServiceImpl
,然后创建了一个实现了InvocationHandler
接口的代理类UserServiceProxy
。在代理类的
invoke
方法中,我们先记录日志,然后调用method.invoke(target, args)
来执行原始对象的方法,最后再记录一次日志。在使用代理时,我们使用Proxy.newProxyInstance
方法来创建代理对象,传递了UserService
接口和代理类UserServiceProxy
的实例。
相比静态代理,动态代理有以下优点:
- 可以动态代理多个接口:动态代理使用的是接口代理,而不是类代理,因此可以代理多个接口。这对于需要实现多个接口的类来说非常方便。
- 可以实现通用的代理逻辑:动态代理可以通过
InvocationHandler
接口实现通用的代理逻辑,避免了代码重复。例如,我们可以实现一个记录方法执行时间的代理逻辑,然后用它来代理多个类的方法。 - 避免了静态代理中需要手动编写代理类的缺点:动态代理是在运行时生成代理类的技术,可以避免静态代理中需要手动编写代理类的缺点。
- 代码更简洁:由于动态代理可以实现通用的代理逻辑,因此代码更加简洁。
- 可以支持 AOP 编程:动态代理可以实现 AOP 编程,即在不修改原有代码的情况下,增加日志、事务等功能。
总之,动态代理相比静态代理更加灵活、方便、简洁,可以避免代码重复,实现通用的代理逻辑,支持 AOP 编程。因此,在实际开发中,我们应该尽量使用动态代理,避免使用静态代理。
在使用代理时,需要注意代理类和原始类必须实现同样的接口,以便代理类可以代理原始类的方法。此外,代理类也可以继承原始类,从而直接调用原始类的方法,但这样可能会破坏代理的透明性和可移植性。
3.1实现动态代理的两种方法
- JDK动态代理
- CGLib代理
3.2 JDK动态代理
jdk动态代理是通过2个类:java.lang.reflect.Proxy
、java.lang.reflect.InvocationHandler
来实现的。具体实现步骤如下:
-
定义一个实现了
InvocationHandler
接口的代理类,这个类中需要实现invoke
方法,在该方法中实现对目标对象方法的调用和增强逻辑的织入。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 执行增强逻辑 System.out.println("before method"); // 调用目标方法 Object result = method.invoke(target, args); // 执行增强逻辑 System.out.println("after method"); return result; } }
-
在代理类中,通过调用Proxy.newProxyInstance()方法动态生成代理对象,并将代理对象返回。
import java.lang.reflect.Proxy;
public class MyProxy {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyInvocationHandler(target));
}
}
- 调用代理对象的方法时,实际上是调用了MyInvocationHandler的invoke()方法,在该方法中实现了对目标方法的调用和增强逻辑的织入。
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) MyProxy.getProxy(userService);
proxy.addUser();
}
}
java.lang.reflect.Proxy
类中常用静态方法:
getProxyClass方法
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces);
为指定的接口创建代理类,返回代理类的Class对象
参数说明:
loader:定义代理类的类加载器
interfaces:指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口
newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
创建代理类的实例对象
这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例。当调用代理对象的方法时,会被
InvocationHandler
接口的invoke
方法处理。
getInvocationHandler方法
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException;
获取代理对象的
InvocationHandler
对象
JDK动态代理的优点在于可以代理任意实现了接口的类,不需要针对特定的类编写代理代码。但是,它只能代理实现了接口的类,无法代理没有实现接口的类。
3.3 CGLib代理
CGLib是一个开源的高性能代理框架,底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类,它可以在运行期动态地生成某个类的子类,且不需要编写字节码,它广泛地被许多AOP的框架使用。
相比于JDK动态代理,CGLib可以对任意类进行代理,而不仅仅是接口。
使用CGLib实现动态代理的步骤如下:
- 引入CGLib的依赖库
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
- 定义一个被代理的类
UserServiceImpl
,它实现了一个接口UserService
,并且包含一个方法save
,用于保存用户信息。
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户信息");
}
}
- 定义一个
MethodInterceptor
接口的实现类,实现intercept()
方法,在该方法中实现对目标对象方法的调用和增强逻辑的织入。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 执行增强逻辑
System.out.println("before method");
// 调用目标方法
Object result = proxy.invokeSuper(obj, args);
// 执行增强逻辑
System.out.println("after method");
return result;
}
}
- 通过CGLib库中的Enhancer类,动态生成代理对象。
import net.sf.cglib.proxy.Enhancer;
public class MyProxy {
public static Object getProxy(Class<?> clazz) {
//1.创建Enhancer对象
Enhancer enhancer = new Enhancer();
//2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
enhancer.setSuperclass(clazz);
/*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
此处使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
enhancer.setCallback(new MyMethodInterceptor());
//4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型.
return enhancer.create();
}
}
调用代理对象的方法时,实际上是调用了MyMethodInterceptor的intercept()方法,在该方法中实现目标类方法的调用和增强
- 创建了一个UserService实现类,然后创建了一个CGLibProxy代理对象,最后使用cgLibProxy.getProxy方法获取代理对象,调用代理对象的saveUser方法。
public class CGLibProxyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
CGLibProxy cgLibProxy = new CGLibProxy();
//方法返回的是Object类型,需要转型
UserService userServiceProxy = (UserService) cgLibProxy.getProxy(userService.class);
userServiceProxy.saveUser();
}
}
使用CGLIB代理的缺点是,由于它是通过生成目标类的子类来完成代理,所以它会比JDK动态代理更加耗费内存和时间。另外,CGLIB代理只能对非final类进行代理。
代码示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 定义一个目标类
class Target {
public void sayHello() {
System.out.println("Hello");
}
}
// 定义一个拦截器
class Interceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before invoke " + method);
Object result = proxy.invokeSuper(obj, args); // 调用目标方法
System.out.println("After invoke " + method);
return result;
}
}
// 测试类
public class CglibDemo {
public static void main(String[] args) {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置目标类为父类
enhancer.setSuperclass(Target.class);
// 设置拦截器为回调函数
enhancer.setCallback(new Interceptor());
// 创建代理对象
Target proxy = (Target) enhancer.create();
// 调用代理对象的方法
proxy.sayHello();
}
}
输出结果
Before invoke public void Target.sayHello()
Hello
After invoke public void Target.sayHello()
3.4 总结
JDK动态代理和CGLib代理有以下几个主要区别:
- Java的动态代理是基于接口的,它要求目标类必须实现一个或多个接口,而CGLib是基于类的,它不要求目标类实现任何接口。
- Java的动态代理是通过反射机制来创建代理对象和调用目标方法的,而CGLib是通过字节码生成技术来创建代理对象和调用目标方法的。
- Java的动态代理会生成一个实现了目标类所有接口的新类作为代理类,而CGLib会生成一个继承了目标类的子类作为代理类。
- Java的动态代理使用InvocationHandler接口来实现方法拦截,而CGLib使用MethodInterceptor接口来实现方法拦截。
更多推荐
所有评论(0)