一、代理模式
1.代理模式定义
- 代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
- 定义可以比较难以理解,咱们把这个抽象的概念换成咱们现实中的代理:
火车票代理点
房屋中介
小王的好朋友小宋
2.开发术语
-
① 抽象主题角色:
- 声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题。
-
② 代理主题(Proxy)角色:
- 代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;
- 代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主题控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);
- 代理角色通常在将客户端调用传递给真实的主题之前或之后(前置增强/通知,后置增强/通知),都要执行某个操作,而不是单纯地将调用传递给真实主题对象。
-
③ 真实主题角色:
- 定义了代理角色所代表地真实对象
3.静态代理
- 保存的对象:
public class Person {
private String name;
private IRent zj;
// getter,setter略
}
- 抽象主题角色
/**
* 抽象主题角色
*/
public interface IRent {
/**
* 看房子
*/
void look();
/**
* 签合同
*/
void sign();
/**
* 收租金
*/
void rent();
}
- 代理主题角色
/**
* 代理主题角色【中介】
*/
public class Intermediary implements IRent {
private String name;
private Fangdong fd;
@Override
public void look() {
fd.look();
}
@Override
public void sign() {
fd.sign();
}
@Override
public void rent() {
System.out.println("租金 涨了200");
fd.rent();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Fangdong getFd() {
return fd;
}
public void setFd(Fangdong fd) {
this.fd = fd;
}
}
- 真实主题角色
/**
* 真实主题角色
*/
public class Fangdong implements IRent {
private String name;
@Override
public void look() {
System.out.println("预约 " + name + "看江与城13栋12-8的房子");
}
@Override
public void sign() {
System.out.println("与 " + name + " 签订租房合同");
}
@Override
public void rent() {
System.out.println(name + " 收取租金");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 测试:
public class StaticProxyTest {
@Test
public void test() throws Exception {
Fangdong fd = new Fangdong();
fd.setName("Baby");
Intermediary zj = new Intermediary();
zj.setName("张淑珍");
zj.setFd(fd);
Person p = new Person(zj);
p.setName("刘伟");
p.look();
p.sign();
p.rent();
}
}
图解:
- 总结
- 静态代理咱们已经完成,但是大家仔细想一下,会觉得它的作用其实不大,它就像是一个中介公司,只代理一个房东(类)的一间房子(方法)。如果这个房东(类)有多间房子(方法),我们还需要继续添加代码,而如果要代理多个房东(类)的房子(方法),我们也还需要添加相应的代理类。 试想一下,这样用代理模式的话代码反而增多。 解决方案:使用动态代理。
4.动态代理
- CGLIB动态代理:没有接口的类使用CGLIB动态代理(类有没有接口都可以支持)
- JDK动态代理:有接口的类使用JDK的动态代理(JDK动态代理不支持没有接口的类)
- 1.JDK动态代理
- jdk的动态代理只允许完成有接口的代理
测试:
package cn.itsource._08_DynamicProxy_JDK;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
/**
* JDK自带的动态代理技术
* Proxy.newProxyInstance
* InvocationHandler
* @throws Exception
*/
@Test
public void test() throws Exception {
//被代理对象
IRent fd = new Fangdong();
fd.setName("张三丰");
//另一个被代理对象
ITicket ds = new TicketDS();
/**
* ClassLoader loader 类加载器
* Class<?>[] interfaces 被代理对象实现过的所有接口【数组】
* InvocationHandler h 处理器
*/
//类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//被代理对象实现过的所有接口
Class<?>[] interfaces = ds.getClass().getInterfaces();
//创建代理对象(强转的是接口)
IRent irent = (IRent)Proxy.newProxyInstance(classLoader, interfaces, new MyInvocationHandler(fd));
System.out.println(irent.getClass());
//测试调用方法
// proxy.look();
// proxy.sign();
// proxy.rent();
irent.sale();
}
}
自定义的一个处理代理功能类(这个类中我们加了一个方法可以直接创建代理对象)
package cn.itsource._08_DynamicProxy_JDK;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* InvocationHandler:是代理实例的调用处理程序 实现的接口。
*/
public class MyInvocationHandler implements InvocationHandler {
//定义真实主题角色:目标对象
//持有一个被代理对象
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 代理对象执行指定方法的代码【反射执行方法】
* @param proxy 经过jdk的代理对象【一般不用】
* @param method 方法【调用的被代理的方法】
* @param args arguments 参数列表数组
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;// //返回的结果
try {
System.out.println("执行目标方法之前添加的业务逻辑。。。。开启事务");
obj = method.invoke(target, args);// //执行直接对象的方法
System.out.println("执行目标方法之后添加的业务逻辑。。。。提交事务");
} catch (IllegalAccessException e) {
e.printStackTrace();
System.out.println("发生异常了,事务回顾");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
System.out.println("发生异常了,事务回顾");
}
return obj;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
@Override
public String toString() {
return "MyInvocationHandler{" +
"target=" + target +
'}';
}
}
抽象主题角色
package cn.itsource._08_DynamicProxy_JDK;
/**
* 抽象主题角色
*/
public interface IRent {
/**
* 看房子
*/
void look();
/**
* 签合同
*/
void sign();
/**
* 收租金
*/
void rent();
}
真实主题角色
package cn.itsource._08_DynamicProxy_JDK;
/**
* 真实主题角色
*/
public class Fangdong implements IRent {
private String name;
@Override
public void look() {
System.out.println("预约 " + name + "看江与城13栋12-8的房子");
}
@Override
public void sign() {
System.out.println("与 " + name + " 签订租房合同");
}
@Override
public void rent() {
System.out.println(name + " 收取租金");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 2.CGLIB动态代理
- cglib类似于javassist-3.18.1-GA.jar功能字节码增强,
原来Hibernate3.2之前就是使用cglib来进行字节码增强 - ①.下面是完成CGLIB的类:
- org.springframework.cglib.proxy.Enhancer; 增强器
- org.springframework.cglib.proxy.MethodInterceptor; 方法切面(代理实例处理方法功能的接口)
- org.springframework.cglib.proxy.MethodProxy;
- ②.CGLIB的实现代码
- CGLIBProxy类代码:
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor{
//定义参数,接收真实的目标对象
private Object targetObject;
//事务对象
private TxManager txManager;
public CglibProxy(Object targetObject,TxManager txManager) {
this.targetObject = targetObject;
this.txManager = txManager;
}
/**
* proxyObject:CGLIB代理后的对象,一般不用
* method:真实对象的方法
* args:方法的参数
* methodProxy:CGLIB代理后的方法,一般不用
*/
@Override
public Object intercept(Object proxyObject, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
Object result = null; //返回的结果
try {
txManager.begin();
result = method.invoke(targetObject, args); //执行直接对象的方法
txManager.commit();
} catch (Exception e) {
txManager.rollback();
e.printStackTrace();
}finally{
txManager.close();
}
return result;
}
/**
* 创建一个代理对象
* @return
*/
public Object createProxy(){
//创建增强器
Enhancer enhancer = new Enhancer();
//创建的代理就是咱们真实目标对象的子类
enhancer.setSuperclass(targetObject.getClass());
//MethodInterceptor就是一个Callback回调
enhancer.setCallback(this);
//创建一个代理对象并返回
return enhancer.create();
}
}
测试代码
@Test
public void testProxy() throws Exception {
User user = new User();
//真实主题角色对象
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
//事务管理器
TxManager txManager = new TxManager();
//创建Cglib代理对象
CglibProxy cglibProxy = new CglibProxy(employeeService, txManager);
//拿到代理对象
EmployeeServiceImpl proxy = (EmployeeServiceImpl)cglibProxy.createProxy();
proxy.save(user);
}
- 总结
- Spring使用JDK与CGLIB两种动态代理
- JDK动态代理:
- 有接口的类使用JDK的动态代理(JDK动态代理不支持没有接口的类)
- 如果有n个接口,必然有n个实现,只用写1个代理类Proxy就可以对所有有接口进行处理
- 如果有代理主题角色存在,必须修改调用方才能实现代理
- CGLIB动态代理:
- 没有接口的类使用CGLIB动态代理(类有没有接口都可以支持)
- 只用写1个代理类CglibProxy就可以对所有没有接口的不能是final类都进行处理
- 如果有代理主题角色存在,必须修改调用方才能实现代理