代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
从UML图中,可以看出代理类与真正实现的类都是继承了抽象的主题类,这样的好处在于代理类可以与实际的类有相同的方法,可以保证客户端使用的透明性。
1.静态代理
第一步:创建 UserService 接口
public interface UserService {
// 添加 user
public void addUser(User user);
// 删除 user
public void deleteUser(int uid);
}
第二步:创建 UserService的实现类
public class UserServiceImpl implements UserService {
public void addUser(User user) {
System.out.println("增加 User");
}
public void deleteUser(int uid) {
System.out.println("删除 User");
}
}
第三步:创建事务类
public class MyTransaction {
// 开启事务
public void before() {
System.out.println("开启事务");
}
// 提交事务
public void after() {
System.out.println("提交事务");
}
}
第四步:创建代理类 ProxyUser.java
public class ProxyUser implements UserService {
// 真实类
private UserService userService;
// 事务类
private MyTransaction transaction;
// 使用构造函数实例化
public ProxyUser(UserService userService, MyTransaction transaction) {
this.userService = userService;
this.transaction = transaction;
}
public void addUser(User user) {
transaction.before();
userService.addUser(user);
transaction.after();
}
public void deleteUser(int uid) {
transaction.before();
userService.deleteUser(uid);
transaction.after();
}
}
测试:
public class TestUser {
public void testOne() {
MyTransaction transaction = new MyTransaction();
UserService userService = new UserServiceImpl();
// 产生静态代理对象
ProxyUser proxy = new ProxyUser(userService, transaction);
proxy.addUser(null);
proxy.deleteUser(0);
}
}
运行结果:
这是一个很基础的静态代理,业务类UserServiceImpl 只需要关注业务逻辑本身,保证了业务的重用性,这也是代理类的优点,没什么好说的。我们主要说说这样写的缺点:
①、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
②、如果接口增加一个方法,比如 UserService 增加修改 updateUser()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2.使用JDK动态代理
动态代理就不要自己手动生成代理类了,我们去掉 ProxyUser.java 类,增加一个ObjectInterceptor.java 类
public class ObjectInterceptor implements InvocationHandler {
// 目标类
private Object target;
// 切面类(这里指事务类)
private MyTransaction transaction;
// 通过构造器赋值
public ObjectInterceptor(Object target, MyTransaction transaction) {
this.target = target;
this.transaction = transaction;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 开启事务
this.transaction.before();
// 调用目标类方法
method.invoke(this.target, args);
// 提交事务
this.transaction.after();
return null;
}
}
测试类
public class TestUser2 {
public void testOne() {
// 目标类
Object target = new UserServiceImpl();
// 事务类
MyTransaction transaction = new MyTransaction();
ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
/**
* 三个参数的含义: 1、目标类的类加载器 2、目标类所有实现的接口 3、拦截器
*/
UserService userService = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyObject);
userService.addUser(null);
userService.deleteUser(11);
}
}
运行结果:
那么使用动态代理来完成这个需求就很好了,后期在 UserService 中增加业务方法,都不用更改代码就能自动给我们生成代理对象。而且将 UserService 换成别的类也是可以的。也就是做到了代理对象能够代理多个目标类,多个目标方法。
查看JDK动态代理的生成的class文件:
/**
* 保存 JDK 动态代理生产的类
* @param filePath 保存路径,默认在项目路径下生成 $Proxy0.class 文件
*/
private static void saveProxyFile(String... filePath) {
if (filePath.length == 0) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
} else {
FileOutputStream out = null;
try {
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", IronManVIPMovie.class.getInterfaces());
String path=filePath[0] + "$Proxy0.class";
System.out.println(path);
out = new FileOutputStream(path);
out.write(classFile);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.使用CGLIB动态代理
使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)
中看的很清楚
第二个入参 interfaces就是需要代理实例实现的接口列表.
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺.
CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
创建创建CGLib代理器
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
// 切面类(这里指事务类)
private MyTransaction transaction;
public CglibProxy(MyTransaction transaction) {
this.transaction=transaction;
}
// 设置被代理对象
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 {
transaction.before();
Object invoke = methodProxy.invokeSuper(obj, objects);
transaction.after();
return invoke;
}
}
测试类
public void testOne() {
// 事务类
MyTransaction transaction = new MyTransaction();
CglibProxy cglibProxy = new CglibProxy(transaction);
UserServiceImpl userService = (UserServiceImpl) cglibProxy.getProxy(UserServiceImpl.class);
userService.addUser(null);
userService.deleteUser(11);
}
查看CGLIB动态代理的生成的class文件:
// 在指定目录下生成动态代理类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\classcglib");
System.out.println("=========CGLIB$CALLBACK_0==========");
Field h = userService.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object obj = h.get(userService);
System.out.println(obj.getClass());
4.JDK和CGLIB动态代理总结
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:
1)实现InvocationHandler
2)使用Proxy.newProxyInstance产生代理对象
3)被代理的对象必须要实现接口
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,
覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理;