代理模式和動態代理實戰應用

代理設計模式

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容