一篇文章搞懂代理模式

原创
2020/04/19 18:50
阅读数 40

代理模式

代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗点说,就是一个中介,比如有一个广州人,是个本地人,有两套房,他要租出去收租,但是除了收租,他还要去找租客,带租客看房,还要准备租房合同,核算水电费等等,很麻烦。这个本地人他也不想这么折腾,他只想完成他的核心业务(收钱),其他杂七杂八的事情就不想管,但是总要有人去做,那就找租房中介,也就是二手房东。二手房东就代理这个广州本地人把房子租给租客。这个道理就是这么简单。

因为很多广州本地人都有这个需求,干脆就搞一个中介公司来专门去做租房子的事情。

代理模式,运用在编程里,也是这个道理,有一些非核心业务的代码,在很多地方都需要用到的逻辑,可以交给代理对象完成,程序员只需要关心核心业务的逻辑即可。

实现代理模式的三种方式

项目就基于上一篇模板模式以及实战应用的项目进行试验。

静态代理

假设原来有一个接口UserService,controller层调用userServicegetAllUser()方法。如下所示:

public interface UserService {    /**     * 获取所有用户信息     * @return List     * @date 2020/4/12     */    List<User> getAllUser() throws Exception;}
@RestController@RequestMapping("/xiaoniu")public class UserController {
@Resource(name = "userService") private UserService userService;
@RequestMapping("/getAllUser") public List<User> getAllUser()throws Exception{ return userService.getAllUser(); }}

如果用静态代理实现记录日志信息,怎么记录呢?

首先创建一个代理类UserServiceProxy,实现UserService接口,然后在UserServiceProxy里面创建一个成员变量userService,再写一个有参构造器来初始化userService。代码如下:

public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) { this.userService = userService; }
@Override public List<User> getAllUser() throws Exception { System.out.println("记录日志:执行getAllUser()方法前"); List<User> userList = userService.getAllUser(); System.out.println(userList); System.out.println("记录日志:执行getAllUser()方法后"); return userList; }}

所以在controller层调用的方式就要改一下,是用代理类UserServiceProxy调用getAllUser()方法。如下:

@RestController@RequestMapping("/xiaoniu")public class UserController {
@Resource(name = "userService") private UserService userService;
@RequestMapping("/getAllUser") public List<User> getAllUser()throws Exception{ return new UserServiceProxy(userService).getAllUser(); }}

然后启动项目,调用一下接口,就可以看到控制台打印如下日志:

/*记录日志:执行getAllUser()方法前[User{id=1, name='大司马', age=36, job='厨师'}, User{id=2, name='朴老师', age=36, job='主播'}, User{id=3, name='王刚', age=30, job='厨师'}, User{id=4, name='大sao', age=32, job='美食up主'}, User{id=5, name='姚大秋', age=35, job='主持人'}]记录日志:执行getAllUser()方法后*/

这就是静态代理的实现思路,很简单。但是一般我们肯定是不用这种方式。因为这种方式太笨了,很容易就可以看出几个缺点。

1.要实现接口,也就是目标的方法要定义一个接口方法,实际上是运用了java多态的特性

2.第一点还不是致命的,因为JDK动态代理也是必须要定义接口;致命的是每一个你想代理的接口你都要去创建一个代理类去实现,假设有很多要代理的接口,那就创建很多代理类,这样显得很臃肿

假设还是不理解为什么要动态代理,不妨我们再多加一个支付接口PayService,这个支付接口我们也要加上日志记录。

用静态代理怎么做?很简单呀,再创建一个PayServiceProxy类不就完了吗,如果还有OrderService(订单),

WarehouseService(仓库)等等。那就要创建很多XXXServiceProxy类。如果使用动态代理,就没必要创建这么多代理类,创建一个代理类就够了!

动态代理就是为了解决静态代理的这个缺点产生的。

JDK动态代理

JDK本身就带有动态代理,必须要满足一个条件,就是要有接口。原理其实和静态代理是一样的,也是用代理类去实现接口,但是代理类不是一开始就写好的,而是在程序运行时通过反射创建字节码文件然后加载到JVM。也就是动态生成的代理类对象。

下面就是用JDK动态代理实现代理模式。

public class LogRecordProxy<T> implements InvocationHandler {
private T target;
public LogRecordProxy(T t) { this.target = t; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("记录日志:执行" + method.getName() + "方法前"); Object result = method.invoke(target, args); System.out.println(result); System.out.println("记录日志:执行" + method.getName() + "方法后"); return result; }
/** * 获取代理对象的方法 * */ @SuppressWarnings("unchecked") public <T> T getProxy() throws Exception { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }}

在controller层,就要改成这样。

@RestController@RequestMapping("/xiaoniu")public class UserController {
@Resource private UserService userService;
@RequestMapping("/getAllUser") public List<User> getAllUser() throws Exception { //获取代理对象 UserService userServiceProxy = new LogRecordProxy<>(userService).getProxy(); return userServiceProxy.getAllUser(); }}

假设有一个PayService也要做日志记录,就可以直接使用。

    @Resource(name = "payService")    private PayService payService;    
@RequestMapping("/pay") public String pay(@RequestParam(name = "channel") String channel, @RequestParam(name = "amount") String amount )throws Exception{ //获取代理对象,实际上就在构造器上改一下传入的参数即可 PayService payServiceProxy = new LogRecordProxy<>(payService).getProxy(); return payServiceProxy.pay(channel,amount); }

很多文章给的例子都不带泛型,也可以,就是获取的代理对象需要强转一下,强转成对应的接口类。

注意:这里一定要用接口接收代理对象,不能用实现类!

因为返回的对象已经不是实现类的对象,而是和实现类有共同的接口类的代理类对象,所以当然只能用接口类去接收。

这也是为什么一再强调要面向接口编程的原因,因为面向接口编程可以做更多的扩展。假设是面向实现类去编程,那就不能用JDK动态代理去扩展了!

CGLB动态代理

那如果有些场景真的没有接口呢,我们怎么运用代理模式?

首先引入maven配置

<dependency>    <groupId>cglib</groupId>    <artifactId>cglib</artifactId>    <version>2.2.2</version></dependency>

然后创建一个方法拦截器LogRecordInterceptor,要实现MethodInterceptor类,如下:

public class LogRecordInterceptor implements MethodInterceptor {
private Object target;
public LogRecordInterceptor(Object target) { this.target = target; }
@Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("记录日志:执行" + method.getName() + "方法前,参数:" + Arrays.toString(args)); Object result = method.invoke(target, args); System.out.println(result); System.out.println("记录日志:执行" + method.getName() + "方法后,参数:" + Arrays.toString(args)); return result; }}

然后再创建一个工厂类InterceptorFactory,用于创建代理对象。

public class InterceptorFactory {
@SuppressWarnings("unchecked") public static <T> T getInterceptor(Class<T> clazz, MethodInterceptor methodInterceptor) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(methodInterceptor); return (T) enhancer.create(); }}

接着我们就可以创建一个没有接口的类,我这里就创建一个数学工具类进行测试

public class MathUtil {    /**     * 获取一个数的平方     * */    public String getSquare(int num) {        return String.valueOf(num * num);    }}

然后在controller层定义一个接口来测试

@RequestMapping("/getSquare")    public String getSquare(@RequestParam(name = "num") Integer num) throws Exception {        MathUtil mathUtil = InterceptorFactory.getInterceptor(MathUtil.class, new LogRecordInterceptor(new MathUtil()));        return mathUtil.getSquare(num);    }

用浏览器或者POSTMAN工具调用接口,就可以在控制台看到以下输出:

/*记录日志:执行getSquare方法前,参数:[2]4记录日志:执行getSquare方法后,参数:[2]*/

这样就实现没有定义接口也可以实现动态代理!

实际上,定义接口的也可以用这种方法来进行扩展,比如上面的userService接口

@RestController@RequestMapping("/xiaoniu")public class UserController {
@Resource private UserService userService;
@RequestMapping("/getAllUser") public List<User> getAllUser() throws Exception { UserServiceImpl userServiceProxy = InterceptorFactory .getInterceptor(UserServiceImpl.class, new LogRecordInterceptor(userService)); return userServiceProxy.getAllUser(); }}

调用接口我们在控制台也是可以看到以下输出日志:

/*记录日志:执行getAllUser方法前,参数:[][User{id=1, name='大司马', age=36, job='厨师'}, User{id=2, name='朴老师', age=36, job='主播'}, User{id=3, name='王刚', age=30, job='厨师'}, User{id=4, name='大sao', age=32, job='美食up主'}, User{id=5, name='姚大秋', age=35, job='主持人'}]记录日志:执行getAllUser方法后,参数:[]*/

总结

以上就是代理模式的一些通俗的解释,还有三种实现的方式的学习

多说几句,我们都知道Spring框架有两个核心技术,一个叫控制反转IOC,另一个叫切面编程AOP。切面编程大家都很熟悉,用的就是代理模式,那么AOP实现的代理模式用的是JDK动态代理还是CLB动态代理

答曰:两个都用!

最简单的,我们看Spring的事务管理,就是用代理模式实现的,如果有兴趣,其实我们自己也可以通过JDK动态代理手写实现事务管理,其实不是很难。篇幅有限,以后可以单独写一篇文章详细说明Spring的事务管理,敬请期待。更多的设计模式实战经验的分享,就关注java技术小牛吧。

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!


本文分享自微信公众号 - java技术爱好者(yehongzhi_java)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部