目录
一、代理设计模式
一、静态代理
1、第一步:创建统一接口
2、第二步:创建接口的实现类
3、第三步:创建代理类
二、JDK动态代理
1、第一步:创建统一接口
2、第二步:创建接口的实现类
3、实现InvocationHandler 接口
4、通过Proxy类使用动态代理
JDK动态代理为什么必须针对接口?
三、GCLib代理
1、第一步:创建被代理类
2、第二步:实现MethodInterceptor,创建Enhancer增强器
一、代理设计模式
代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

关于代理模式:
http://c.biancheng.net/view/1359.html
一、静态代理
?
1、第一步:创建统一接口
// 统一接口
public interface UserService {
// 添加 user
public void addUser();
// 删除 user
public void deleteUser();
}
2、第二步:创建接口的实现类
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("增加 User");
}
public void deleteUser() {
System.out.println("删除 User");
}
}
3、第三步:创建代理类
public class ProxyUser implements UserService {
// 被代理类
private UserService userService;
// 使用构造函数实例化
public ProxyUser() {
this.userService = new UserServiceImpl();
}
public void addUser() {
System.out.println("开启事务》》》》》");
userService.addUser(); // 被代理的方法
System.out.println("关闭事务");
}
public void deleteUser() {
System.out.println("开启事务》》》》》");
userService.deleteUser();
System.out.println("关闭事务");
}
}
测试:
public class TestUser {
public static void main(String[] args) {
// 直接使用代理类
ProxyUser proxy = new ProxyUser();
proxy.addUser();
proxy.deleteUser();
}
}
// 输出结果:
开启事务》》》》》
增加 User
关闭事务
开启事务》》》》》
删除 User
关闭事务
这是一个很基础的静态代理,业务类UserServiceImpl 只需要关注业务逻辑本身,保证了业务的重用性,但是,这样写的缺点:
1、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2、如果接口增加一个方法,比如 UserService 增加修改 updateUser()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式
二、JDK动态代理
java动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心;
1、第一步:创建统一接口
略,同上
2、第二步:创建接口的实现类
略,同上
3、实现InvocationHandler 接口
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ObjectInterceptor implements InvocationHandler {
// 目标类
private Object target;
// 通过构造器赋值
public ObjectInterceptor(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 开启事务
System.out.println("开启事务》》》》》");
// 调用目标类方法
method.invoke(this.target, args);
// 提交事务
System.out.println("提交事务");
return null;
}
}
想进一步了解这个类吗?看看这篇文章
InvocationHandler和Proxy(Class)的动态代理机制详解
https://www.cnblogs.com/shoshana-kong/p/9041485.html
4、通过Proxy类使用动态代理
Proxy这个类的作用就是用来动态创建一个代理对象的类
import java.lang.reflect.Proxy;
public class TestUser {
public static void main(String[] args) {
// 目标类
Object target = new UserServiceImpl();
ObjectInterceptor proxyObject = new ObjectInterceptor(target);
/**
* 三个参数的含义: 1、目标类的类加载器 2、目标类所有实现的接口 3、拦截器
*/
UserService userService = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
proxyObject);
userService.addUser();
userService.deleteUser();
}
}
// 打印结果
开启事务》》》》》
增加 User
提交事务
开启事务》》》》》
删除 User
提交事务
当然,你可以把程序封装得更加完美,但是这里为了方便理解,我就不做这一步了,想封装的可以看下这篇文章
Java动态代理之InvocationHandler最简单的入门教程
https://www.jianshu.com/p/e575bba365f8
查看jdk的动态代理源码发现:
动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的$Proxy0.class文件发现:class类定义为:
public final class $Proxy0 extends Proxy implements Interface {
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
}
由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。
另外,为何调用代理类的方法就会自动进入InvocationHandler 的 invoke()方法呢?
其实是因为在动态代理类的定义中,构造函数是含参的构造,参数就是我们invocationHandler 实例,而每一个被代理接口的方法都会在代理类中生成一个对应的实现方法,并在实现方法中最终调用invocationHandler 的invoke方法,这就解释了为何执行代理类的方法会自动进入到我们自定义的invocationHandler的invoke方法中,然后在我们的invoke方法中再利用jdk反射的方式去调用真正的被代理类的业务方法,而且还可以在方法的前后去加一些我们自定义的逻辑。比如切面编程AOP等。
JDK动态代理为什么必须针对接口
https://www.cnblogs.com/WeidLang/p/9857495.html
三、GCLib代理
使用JDK创建代理有一个限制,它只能为接口创建代理实例。这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚
第二个入参 interfaces就是需要代理实例实现的接口列表。
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺。
GCLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑。
CGLIB缺点:对于final方法,无法进行代理。
1、第一步:创建被代理类
注意,这里不再实现接口
public class UserServiceImpl {
public void addUser() {
System.out.println("增加 User");
}
public void deleteUser() {
System.out.println("删除 User");
}
}
2、第二步:实现MethodInterceptor,创建Enhancer增强器
Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,可以用来为无接口的类创建代理。它的功能与java自带的Proxy类挺相似的。它会根据某个给定的类创建子类,并且所有非final的方法都带有回调钩子。
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
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();
}
public Object intercept(Object obj,
Method method,
Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务》》》》》");
Object invoke = methodProxy.invokeSuper(obj, objects);
System.out.println("关闭事务");
return invoke;
}
}
测试
public class TestUser {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserServiceImpl userService = (UserServiceImpl) cglibProxy.getProxy(UserServiceImpl.class);
userService.addUser();
userService.deleteUser();
}
}
// 测试结果
开启事务》》》》》
增加 User
关闭事务
开启事务》》》》》
删除 User
关闭事务
CGLIB包的底层
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。除了CGLIB包,脚本语言例如 Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。所以cglib包要依赖于asm包,需要一起导入。下图为cglib与一些框架和语言的关系(CGLIB Library and ASM Bytecode Framework)

Spring AOP和Hibernate同时使用JDK的动态代理和CGLIB包。Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。
Enhancer类
Enhancer允许为非接口类型创建一个Java代理。Enhancer动态创建了给定类型的子类但是拦截了所有的方法。和Proxy不一样的是,不管是接口还是类他都能正常工作。
下边是两篇还不错的文章:
CGLib之Enhancer
https://blog.csdn.net/jiaotuwoaini/article/details/51675684
cglib之Enhancer
https://www.cnblogs.com/micrari/p/7565632.html
|