代理模式和动态代理实战应用

代理设计模式

java有20多种设计模式,代理模式肯定是非常出名的一种。
代理模式可以理解为不直接访问对象,让代理对象去达到某种目的。
一般是用于对方法的增强,在不动原代码的情况下,对该方法运行前,后,异常加一些处理。
最出名的Sping的AOP(面向切面编程)底层就是动态代理帮实现的。
在平时开发中,使用设计模式可以让我们的代码更加具有可扩展性。

静态代理

这个静态代理非常简单的。理解了上述的意思,我们就可以写个差不多的出来。看代码

package com.zyc.proxydesignpattern.staticproxy;
//这个是登录方法接口
public interface UserService {
    String login(String username, String password);
}
package com.zyc.proxydesignpattern.staticproxy;
//这个是登录方法实现类,并实现了 login方法
public class UserServiceImpl implements UserService {
    @Override
    public String login(String username, String password) {
        if("admin".equals(username) && "123".equals(password)){
            System.out.println("登录成功");
            return "loginSuccess";
        }else{
            System.out.println("登录失败");
            return "error";
        }
    }
}

package com.zyc.proxydesignpattern.staticproxy;

//这个是代理类  也需要实现UserService接口
public class UserServiceProxy implements UserService {
    //定义 被代理的对象
    private UserService userService;

    public UserServiceProxy(UserService userService){
        this.userService = userService;
    }

    //在调用的时候,给该方法的前后添加操作。
    @Override
    public String login(String username, String password) {
        System.out.println("---前置---");
        String result = userService.login(username,password);
        System.out.println("---后置---");
        return result;
    }
}


    @Test
    public void staticTest(){
        new UserServiceProxy(userServiceByStatic).login("admin","123");
    }
    //当调用该方法后运行的结果为
    ---前置---
    登录成功
    ---后置---

上边的三段代码就是一个简单的静态代理的demo
静态代理的缺点比较明显,这是一个UserService的代理,那再有AService,BService呢?
就需要重新创建代理类,比较麻烦。所以静态代理在实际开发中使用并不多。

动态代理(JDK)

说完静态代理,就说说JDK本身自带的动态代理。
JDK的动态代理是靠反射完成的。直接看代码。

package com.zyc.proxydesignpattern.dynamicproxy.jdk;
//接口
public interface UserService {
    String login(String username,String password);
}
package com.zyc.proxydesignpattern.dynamicproxy.jdk;
//实现类,到此为止和静态代理是一模一样的。
public class UserServiceImpl implements UserService{
    //JDK的动态代理必须要有接口,这是和cglib最大不同的地方
    @Override
    public String login(String username, String password) {
        if("admin".equals(username) && "123".equals(password)){
            System.out.println("登录成功");
            return "loginSuccess";
        }else{
            System.out.println("登录失败");
            return "error";
        }
    }
}

JDK的动态代理最重要的就是这个 InvocationHandler
我们需要实现他的 invoke方法,实际上也就是让目标方法运行

package com.zyc.proxydesignpattern.dynamicproxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyInvocationHandler implements InvocationHandler{

    //目标方法
    private Object target;

    public MyInvocationHandler(Object target){
        this.target = target;
    }

    public Object getProxy(){
        //获取该类的代理
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    //通过反射让方法运行。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("---前置---");
        Object result = method.invoke(target,args);
        System.out.println("---后置---");
        return result;
    }
}

    @Test
    public void dynamicTest(){
        private UserService userService = new UserServiceImpl();
        //创建MyInvocationHandler 来获取代理类,从而让代理类运行login方法
        UserService proxy = (UserService) new MyInvocationHandler(userService).getProxy();
        proxy.login("admin","123");

    }
    //当调用该方法后运行的结果为
    ---前置---
    登录成功
    ---后置---

有了动态代理,可以说就比静态代理方便了很多。
你需要代理什么,就传什么值就可以。也可以泛型下就不用强转了。
这只是一个小demo。就不弄那么麻烦了。
一会说为什么被代理类要有接口,
如果不想要接口 ,。其实还有一种动态代理,。

动态代理(cglib)

cglib原理是让目标类生成一个子类,然后让子类去进行方法的增强。

package com.zyc.proxydesignpattern.dynamicproxy.cglib;
//被代理类
public class UserService {
    //在用cglib的时候, UserService可以没有接口。
    //但是 login 方法不能用final修饰,不能用private修饰 ,因为cglib的代理原理是找该类的子类去继承该方法去实现,
    //如果用private 或者 final 修饰  无法继承该方法。
    public String login(String username, String password) {
        if("admin".equals(username) && "123".equals(password)){
            System.out.println("登录成功");
            return "loginSuccess";
        }else{
            System.out.println("登录失败");
            return "error";
        }
    }
}
package com.zyc.proxydesignpattern.dynamicproxy.cglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
//看好包名  一定要用 cglib下的MethodInterceptor
//这个是cglib代理要实现的接口
public class MyCglibProxy implements MethodInterceptor {

    //实现 MethodInterceptor 中的 intercept 接口
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("---前置---");
        //实际方法运行的地方
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("---后置---");
        return result;
    }

}
package com.zyc.proxydesignpattern.dynamicproxy.cglib;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;

import java.lang.reflect.Method;

//代理工厂
public class ProxyFactory {
    /**
     * 防止在外边创建
     */
    private ProxyFactory(){}

    //代理工厂方法。
    //这几天有时间的话 在写个工厂设计模式
    public static UserService getUserServiceProxy(MyCglibProxy myCglibProxy){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService.class);
        enhancer.setCallback(new MyCglibProxy());
        //这里就不详细写了,cglib功能挺多的
        //setCallbacks可是设置多个代理,然后根据 setCallbackFilter 的 accept 方法 看哪个方法走哪个代理。
//        enhancer.setCallbacks(new Callback[]{new MyCglibProxy()});
//        enhancer.setCallbackFilter((Method method)-> {
//            
//            return 0;
//        });
        return (com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService)enhancer.create();
    }
}
    @Test
    public void cglibTest(){
        /*这样搞不方便,可以搞一个工厂

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService.class);
        enhancer.setCallback(new MyCglibProxy());
        com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService userService = (com.zyc.proxydesignpattern.dynamicproxy.cglib.UserService)enhancer.create();
        userService.login("admin","4554");*/
        //如果要是不用代理工厂的话,
        //按照上边写就行,但是这样写更简洁一点。也更有拓展性
        //通过代理工厂获取 代理,然后用代理去运行login
        ProxyFactory.getUserServiceProxy(new MyCglibProxy()).login("123","3443");
    }
    //当调用该方法后运行的结果为
    ---前置---
    登录失败
    ---后置---

虽然大家都知道反射的效率是很低的。但是在1.8以后 JDK的动态代理还是要比cglib要效率高一点的。
然后我们看看JDK的动态代理和cglib的动态代理有什么不同吧。
借图:

借的图

参考:
深入理解CGLIB动态代理机制

动态代理在实际开发中的使用

在这里我们基于 JDK的动态代理来搞
比如,我们开发中获取一个数据需要有降级处理,先从redis中获取,如果没有获取到在从mysql中获取。
我们看代码。

package com.zyc.proxydesignpattern.inaction.service.impl;

import com.zyc.proxydesignpattern.inaction.entity.Project;
import com.zyc.proxydesignpattern.inaction.service.ProjectService;
import org.springframework.stereotype.Service;
//很普通的service层
@Service
public class ProjectServiceImpl implements ProjectService{
    public Project getProjectById(String id){
        return new Project("123","假装从数据库中取出的项目");
    }
}

package com.zyc.proxydesignpattern.inaction.util;


import com.zyc.proxydesignpattern.inaction.entity.Project;
import org.springframework.stereotype.Component;
//同样很普通的redis操作工具
//我们假设id为123的项目在缓存中存在
@Component
public class RedisUtil {
    public Project getProjectById(String id){
        if(id!=null && "123".equals(id)){

         return new Project(id,"假装从缓存中取出的项目");
        }else{
            return null;
        }
    }
}
package com.zyc.proxydesignpattern.inaction.util.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//也很普通的代理工具 ,就多了几个操作类
public class DataBaseProxyHandler<T> implements InvocationHandler {

    //被代理对象
    private Object delegate;

    //需要去做事情的接口
    private ProxyInterface myProxyInterface;

    //需要去做事情的类
    private Object param;

    //构造器 在这里给赋值
    public DataBaseProxyHandler(Object v) {
        this.param = v;
    }

    //proxy方法,返回
    public <T> T proxy(T delegate, ProxyInterface myProxyInterface) {
        this.myProxyInterface = myProxyInterface;
        this.delegate = delegate;

        return (T) Proxy.newProxyInstance(this.delegate.getClass().getClassLoader(),
                this.delegate.getClass().getInterfaces(), this);
    }

    //就多了个几个类,其他一模一样
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {


        Object obj = null;

        if (args != null && args.length > 0){
            obj = myProxyInterface.doBegin(param,args);
        }else{
            obj = myProxyInterface.doBegin(param,null);
        }
        if (obj != null)
            return obj;

        obj = method.invoke(this.delegate, args);

        if (args != null && args.length > 0){
            myProxyInterface.doEnd(obj, param,args);
        }else{
            myProxyInterface.doEnd(obj, param,null);
        }

        return obj;
    }
}
package com.zyc.proxydesignpattern.inaction.util.proxy;

public interface ProxyInterface<T,V> {
    /**
     * T 参数代表需要操作对象的工具类
     * V 参数实体对象
     * @param
     * @return
     */
    Object doBegin(T t, Object[] param);

    /**
     * T 参数代表需要操作对象的工具类
     * returnObj invok 后返回的参数
     *
     * @param returnObj
     * @param t
     * @return
     */
     Object doEnd(V returnObj, T t,Object[] param);
}
package com.zyc.proxydesignpattern.inaction.controller;

import com.zyc.proxydesignpattern.inaction.entity.Project;
import com.zyc.proxydesignpattern.inaction.service.ProjectService;
import com.zyc.proxydesignpattern.inaction.util.RedisUtil;
import com.zyc.proxydesignpattern.inaction.util.proxy.DataBaseProxyHandler;
import com.zyc.proxydesignpattern.inaction.util.proxy.ProxyInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class ProjectController {


    @Autowired
    ProjectService projectService;


    @Autowired
    RedisUtil redisUtil;

    //如果前边动态代理搞明白了。这些代码很容易理解
    //就多了个ProxyInterface去进行前置和后置
    public Project getProjectById(String id){
        //在这里用了个匿名内部类,就不在外边创建新的Proxy了。
        //在这里可以更加直观的看,
        return new DataBaseProxyHandler<ProjectService>(redisUtil).proxy(projectService, new ProxyInterface() {
            //在doBegin方法中是 getProjectById 前做的事情。
            //从 DataBaseProxyHandler 的invoke 方法,可以看到 如果返回的是null才会去运行 getProjectById
            //如果不为null  则直接返回了。
            @Override
            public Object doBegin(Object o, Object[] param) {
                RedisUtil redisUtil = (RedisUtil)o;

                return redisUtil.getProjectById(id);
            }

            //在doEnd方法中 是 getProjectById 后做的事情
            //在 getProjectById 中 doEnd是不用做任何事的。
            //但是 如果是 saveProject 呢? 我们可以在doBegin中不做任何事情,
            //在doEnd中可以 判断如果saveProject插入到 mysql/oracle 那么在doEnd可以插入到缓存中。
            @Override
            public Object doEnd(Object returnObj, Object o, Object[] param) {
                return null;
            }
        }).getProjectById(id);
    }
}

以上就是动态代理在实际开发中的使用。
github:https://github.com/zycisbg/ProxyDesignPattern

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容