为其他对象提供一种代理以控制对这个对象的访问
【产品】: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地址