为其他对象提供一种代理以控制对这个对象的访问
【产品】:Kerwin,我记得你是在通州租房住吧?
【开发】:是啊,怎么了?
【产品】:你是房东直租还是中介啊?我最近真是特别烦中介,收费都好黑!
【开发】:我啊,我租的房子名义上倒是房东直租,但估计还是中介,你知道吗,中介的扩张是一个必然。
【产品】:扩张?你指的是全北京的房子都是中介的意思吗?
【开发】:现在肯定不至于全部都是,但也是大部分了,为什么会这样呢,因为中介需要控制租户,控制租金市场,如果租户直租房东,房东钱多人好,就很有可能很便宜,这就会打乱市场价格,所以拿下所有房老板,不仅为了赚钱,也是为了控制这种市场关系。
【产品】:我看你们程序员平常“傻傻的”,怎么对这个这么了解?莫非有计算机相关的故事?
【开发】:被你说中了,这个就是代理模式!它的诞生就是为了控制对象的访问,不过我们一般是用来增强其功能,不像XXX?
「定义正常业务类接口」
public interface PhoneInterface {
/***
* 更新电话号码
* @param phoneNum 电话号码
* @throws Exception 可能抛出Exception 异常
*/
void updatePhone(Long phoneNum);
}
「实现正常业务类」
public class PhoneServiceImpl implements PhoneInterface{
@Override
public void updatePhone(Long phoneNum) {
System.out.println("update phoneNum is: -> " + phoneNum);
}
}
「静态代理业务类」
public class PhoneServiceProxy implements PhoneInterface {
/** 代理模式一般自行New对象, 反观装饰器模式则是传入对象 **/
private PhoneInterface phoneInterface;
public PhoneServiceProxy() {
this.phoneInterface = new PhoneServiceImpl();
}
@Override
public void updatePhone(Long phoneNum) {
before(phoneNum);
phoneInterface.updatePhone(phoneNum);
after();
}
private void before(Long phoneNum) {
System.out.println(MessageFormat.format("log start time:{0} , phoneNum is: {1}", new Date(), phoneNum));
if (null == phoneNum || String.valueOf(phoneNum).length() != 11) {
throw new RuntimeException("Update phoneNum fail, phoneNum is wrong.");
}
}
private void after() {
System.out.println(MessageFormat.format("log end time:{0}", new Date()));
}
}
静态代理模式的设计思路:
简单来说,
❝如果看着有点模棱两可,建议看完本文后,访问专题设计模式开源项目,里面有具体的代码示例,链接在最下面 ❞
刚才 HeadFirst核心代码 章节展示的是其静态代码的书写方式,如果所有的类都基于这样实现,那势必发生类膨胀的无解问题,因此真正常用的还是动态代理,分为两种 CGLIB | JDK动态代理
「注意事项:」
「JDK 动态代理必不可少的三要素:InvocationHandler,newProxyInstance,invoke」
public class MybatisInvocation implements InvocationHandler {
/**
* 代理指定的接口
* @param tClass 接口class
* @param <T> 接口类型
*/
@SuppressWarnings("unchecked")
public static <T> T newProxyInstance(Class<T> tClass) {
return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, new MybatisInvocation());
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.isAnnotationPresent(Select.class)) {
Select select = method.getAnnotation(Select.class);
System.out.println(MessageFormat.format("Method Name: {0} , Annotation Value is: {1}", method.getName(), select.value()));
}
// 获取到SQL及参数, 即可通过JDBC进行数据库操作查询数据, MyBatis不再神秘
return Arrays.asList("I", " am", " Kerwin~");
}
}
「被代理的接口」
public interface MyBatis {
@Select("select * from demo")
List<String> select();
}
「测试调用」
public class App {
public static void main(String[] args) {
// JDK动态代理:模拟 MyBatis 核心代理阶段
MyBatis batis = MybatisInvocation.newProxyInstance(MyBatis.class);
System.out.println("Result:" + batis.select());
}
}
「输出结果」
Method Name: select , Annotation Value is: select * from demo
Result:[I, am, Kerwin~]
MyBatis中的JDK 动态代理:我们在使用MyaBtis的时候,肯定想过,它凭什么一个接口就可以输出结果,利用JDK 动态代理,可以非常方便的构建接口的代理,我们便可以在 Invoke
方法中大做文章,解析方法注解的值,解析其方法返回值,然后利用JDBC即可实现数据库查询实现一个简单ORM框架,推荐大家自行尝试一下
「基础使用」
public class PhoneCglibProxy implements MethodInterceptor {
Object target;
public PhoneCglibProxy(Object o) {
this.target = o;
}
public Object newProxyInstance(){
Enhancer en = new Enhancer();
// 设置要代理的目标类
en.setSuperclass(target.getClass());
// 设置要代理的拦截器
en.setCallback(this);
// 生成代理类的实例
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(MessageFormat.format("Method Name is: {0} Params is: {1}", method.getName(), Arrays.toString(objects)));
return methodProxy.invokeSuper(o, objects);
}
}
「测试调用」
public class App {
/***
* CGLIB动态代理
* 如果类是final的,则无法生成代理对象,报错
* 如果方法是final的,代理无效
*
* 关键代码:
* 1.PhoneCglibProxy 实现 MethodInterceptor 方法拦截器接口 同时实现其 newProxyInstance方法 -> 该方法内容比较固定
* 2.通过代理工厂构建, 创建对象, 使用即可
*
* Spring 3.2之后默认包含了cglib依赖
* 普通项目 CGLIB依赖如下:
*
* <dependency>
* <groupId>cglib</groupId>
* <artifactId>cglib-nodep</artifactId>
* <version>2.2.2</version>
* </dependency>
*
* 推荐代码阅读顺序:
*
* @see PhoneServiceImpl
* @see PhoneCglibProxy
*/
public static void main(String[] args){
PhoneServiceImpl phone = new PhoneServiceImpl();
PhoneCglibProxy proxyFactory = new PhoneCglibProxy(phone);
PhoneServiceImpl service = (PhoneServiceImpl) proxyFactory.newProxyInstance();
service.updatePhone(15186564812L);
}
}
CGLIB 动态代理:Spring 3.2之后默认包含了cglib依赖,在使用中也要注意 final 关键字会使CGLIB代理失效,另外Spring AOP 默认采用JDK 动态代理,同时配合CGLIB代理一起实现的。
「Spring AOP」:
❝关于两种动态原理的实现原理可以查查其他的文章~ ❞
当我们需要为额外控制对象方法的执行时,比如历史项目的接口都没有记录日志,在Spring环境下,我们可以对所有的Bean方法增加日志功能,又或是多数据源时,通过注解标明对应的数据源,解耦代码等等
「附上GOF一书中对于代理模式的UML图:」
GitHub地址