二月份的时候看MyBatis书籍,知道它用到了代理模式,所以选择代理模式作为设计模式的开篇之作
1.为什么需要代理模式
网上搜索了好多关于WHY,但是大多都是一些所谓的来自生活的解释,感觉没有从代码的角度去解释。比如每个明星会有自己的经纪人,明星只要专注演戏唱歌,经纪人则负责安排出席活动啥的,这是一种代理模式。
近官方一点的是这样的:
官方版+OWN理解:
通过代理,可以控制如何访问真正的服务对象,提供额外服务,有机会通过重写一些类来满足特定需要。例如:MyBatis的Dao接口(有的叫Mapper),作为一个接口没有办法执行,它却可以直接通过接口调用xml中的方法,显然这是一种代理。具体后面会有更详细的介绍。
2.简介
代理,中介,给一个对象提供一个代理,由代理对象控制对原对象的引用。
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
3.分类
3.1 静待代理
先看一个例子:客户实际需要调用的是RealSubject的request()方法,现在用ProxySubject来代理RealSubject,同样达到目的,同时还加入了其他方法preRequest()等,提供了额外服务。
抽象角色:
真实角色:
代理角色:
客户调用:
但是这样的话,真实角色必须先存在,而且一个真实角色得对应一个代理角色,会造成类的冗余。如果事先不知道真实角色,这时候我们就需要使用动态代理了。
动态代理类的字节码是程序运行时由Java反射机制动态生成。
3.2 JDK动态代理
JDK的动态代理由JDK的java.lang.reflect.*包提供支持,需要完成这么几个步骤:
1)编写业务接口和服务类(实现类),这个是真正服务提供者,在JDK代理中接口是必须的
2)编写代理类,提供绑定和代理方法
JDK的代理最大的缺点就是需要提供接口,MyBatis的Dao(或称Mapper)就是一个接口,它采用的就是JDK的动态代理。
关键代码:下图代理类Proxy不去实现具体的某个业务接口,而是实现了JDK提供的InbocationHander类。在Proxy中我们不需要知道具体业务类(委托类),在运行之前,将委托类和代理类进行了解耦,在运行期通过private Object target才产生形成联系,然后在调用的时候通过getInstance确定谁是委托对象。
public class Proxy implements InvocationHandler {
private Object target;
// 绑定委托对象并返回一个代理类
public Object GetInstance(Object target) {
this.target = target;
// 取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
// 调用方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
System.out.println("before");
// 执行方法
result = method.invoke(target, args);
System.out.println("after");
return result;
}
}
然后客户调用的时候才传入真正的具体业务类(委托类)
Proxy proxy = new Proxy();
// 在这里进行真正的对象传入
User user = (User)proxy.getInstance(new UserImpl());
user.getUsers();
3.3 CGLIB动态代理
开源框架CGLIB是一种流行的动态代理,克服了JDK动态代理必须提供接口才可以使用的缺陷。
CGLIB是针对类来实现代理的,其实现原理:CGLIB的底层采用ASM字节码生成框架,使用字节码技术生成代理,比使用反射生成代理的效果要高,是对指定的目标类生成一个子类,并覆盖其中方法实现增强。但是也有一点点不足,因为采用的是继承,所以不能对final修饰的类进行代理。
步骤:
1)建立一个普普通通的业务类
2)写CGLIB代理类,这里的不同是第一步中,我们不需要在建接口了,只是一个普普通通的java类。
public class CglibProxy implements MethodInterceptor {
// 委托对象,运行时定类型
private Object target;
// 创建代理对象
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
// 回调方法
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("before");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after");
return result
}
}
发现proxy.invokeSuper(obj, args)是关键,调用者只需要传入对象就行
CglibProxy cproxy = new CglibProxy();
User user = (User)cproxy.getInstance(new User());
user.getUsers();
4.应用
4.1 MyBatis-DAO的代理开发模式
只需要定义Dao接口,由MyBatis产生Dao的接口的实现类,属于JDK动态代理,因为提供了接口
此外,在MyBatis中通常在延迟加载的时候才会用到CGLIB动态代理
步骤:
1)定义一个Dao接口,接口中
public interface DemoDao {
public List<User> getUserById(Long id) ;
}
2)对应的mapper.xml中添加相应的select
<select id="getUserById" parameterType="long" resultMap="BaseResultMap">
select ...
</select>
3)service层可以直接通过注入dao接口,直接调用方法demoDao.getUserById(1L)
注:此处的demoDao其实就是MapperProxy<T>
4.2 Spring的AOP(面向切面编程)
面向对象编程是从【静态角度】考虑程序的结构,而面向切面编程是从【动态角度】考虑程序运行过程。
AOP底层,就是采用【动态代理】模式实现的。采用了两种代理:JDK动态代理和CGLIB动态代理。
5.面试
1)静态代理与动态代理区别
静态代理是代理类中需要引入真实类,所以事先必须知道真实类,并且代理类源代码是由程序员编写的,在程序运行前,它的.class文件就已经存在了。而动态代理是基于反射生成代理对象,所以代理类字节码是在程序运行时由Java反射机制动态生成。动态代理包括JDK动态代理和CGLIB动态代理(区别JDK动态代理必须提供接口才可以)。典型代表MyBatis的Dao(或称Mapper)就是一个接口,它采用的就是JDK的动态代理;Spring的AOP也采用了动态代理。