淺談Java【代理設計模式】——看這篇文章就懂了

什麼是代理模式

爲其他對象提供一種代理以控制對這個對象的訪問。

爲什麼使用代理模式

中介隔離:在某些情況下,一個客戶類不想或者不能直接引用一個委託對象,而代理類對象可以在客戶類和委託對象之間起到中介的作用,其特徵是代理類和委託類實現相同的接口

開閉原則,增加功能:代理類除了是客戶類和委託類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合代碼設計的開閉原則。代理類主要負責爲委託類預處理消息過濾消息把消息轉發給委託類,以及事後對返回結果的處理等。代理類本身並不真正實現服務,而是同過調用委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共的服務。例如我們想給項目加入緩存、日誌這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委託類。

代理模式實現原理

代理模式主要包含三個角色,即抽象主題角色(Subject)、委託類角色(被代理角色,Proxied)以及代理類角色(Proxy)

抽象主題角色(Subject):可以是接口,也可以是抽象類

委託類角色(proxied):真實主題角色,業務邏輯的具體執行者

代理類角色(Proxy):內部含有對真實對象RealSubject的引用,負責對真實主題角色的調用,並在真實主題角色處理前後做預處理和後處理

代理模式應用場景

SpringAop、日誌收集、權限控制、過濾器、RPC遠程調用

代理模式創建的方式

靜態代理 和 動態代理

靜態代理

靜態代理是由程序員創建或工具生成代理類的源碼,再編譯代理類。

所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。

一句話,自己手寫代理類就是靜態代理。

基於接口實現方式

//主題(Subject)
public interface OrderService {
    void order();
}
//實現接口
public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("用戶下單操作..");
    }
}
//代理類
public class OrderServiceProxy implements OrderService {
    /**
     * 代理對象
     */
    private OrderService proxiedOrderService;

    public OrderServiceProxy( OrderService orderService) {
      this.proxiedOrderService=orderService;
    }

    public void order() {
        System.out.println("日誌收集開始..");
        proxiedOrderService.order();
        System.out.println("日誌收集結束..");
    }
}
//測試
public class ClientTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy(new OrderServiceImpl());
        orderService.order();
    }
}

接口繼承方式實現

//繼承接口實現類
public class OrderServiceProxy extends OrderServiceImpl {

    public void order() {
        System.out.println("日誌收集開始..");
        super.order();
        System.out.println("日誌收集結束..");
    }
}
//測試
public class ClientTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy();
        orderService.order();
    }
}

動態代理

動態代理是在實現階段不用關心代理類,而在運行階段才指定哪一個對象。

動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成 。

JDK動態代理

JDK動態代理的一般步驟如下:

1.創建被代理的接口和類;

2.實現InvocationHandler接口,對目標接口中聲明的所有方法進行統一處理;

3.調用Proxy的靜態方法,創建代理類並生成相應的代理對象;

//主題()Subject
public interface OrderService {
    void order();
}
//實現接口
public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("修改數據庫訂單操作..");
    }
}
//代理類
public class JdkInvocationHandler implements InvocationHandler {
    /**
     * 目標代理對象
     */
    public Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>>日誌收集開始>>>>");
        // 執行代理對象方法
        Object reuslt = method.invoke(target, args);
        System.out.println(">>>日誌收集結束>>>>");
        return reuslt;
    }

    /**
     * 獲取代理對象接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

}
JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
OrderService proxy = jdkInvocationHandler.getProxy();
proxy.order();

原理分析

1.	獲取代理的生成的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

2.使用反編譯工具該Proxy0.class

注意:繼承了Proxy類,實現了代理的接口,由於java不能多繼承,這裏已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。

CGLIB動態代理

Cglib是一個強大的,高性能,高質量的代碼生成類庫。它可以在運行期擴展JAVA類與實現JAVA接口。其底層實現是通過ASM字節碼處理框架來轉換字節碼並生成新的類。大部分功能實際上是ASM所提供的,Cglib只是封裝了ASM,簡化了ASM操作,實現了運行期生成新的class。

CGLIB原理

運行時動態的生成一個被代理類的子類(通過ASM字節碼處理框架實現),子類重寫了被代理類中所有非final的方法。在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢植入橫切邏輯。

Cglib優缺點

優點:JDK動態代理要求被代理的類必須實現接口,當需要代理的類沒有實現接口時Cglib代理是一個很好的選擇。另一個優點是Cglib動態代理比使用java反射的JDK動態代理要快

缺點:對於被代理類中的final方法,無法進行代理,因爲子類中無法重寫final函數

CGLIB代理實現

版權@須臾之餘https://my.oschina.net/u/3995125

實現MethodInterceptor接口的intercept方法後,所有生成的代理方法都調用這個方法。

intercept方法的具體參數有

obj 目標類的實例

1.method 目標方法實例(通過反射獲取的目標方法實例)
2.args 目標方法的參數
3.proxy 代理類的實例

該方法的返回值就是目標方法的返回值。

public class OrderServiceImpl {
    public void order() {
        System.out.println("用戶下單操作..");
    }
}
public class CglibMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("<<<<<日誌收集開始...>>>>>>>");
        Object reuslt = proxy.invokeSuper(obj, args);
        System.out.println("<<<<<日誌收集結束...>>>>>>>");
        return reuslt;
    }
}



System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor();
Enhancer enhancer = new Enhancer();
// 設置代理類的付類
enhancer.setSuperclass(OrderServiceImpl.class);
// 設置回調對象
enhancer.setCallback(cglibMethodInterceptor);
// 創建代理對象
OrderServiceImpl orderServiceImpl = (OrderServiceImpl) enhancer.create();
orderServiceImpl.order();

Maven依賴

<dependencies>

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.12</version>
    </dependency>
</dependencies>

結果

>>>>cglib日誌收集開始....
執行訂單業務邏輯代碼
>>>>cglib日誌收集結束....

靜態代理與動態代理區別

靜態代理需要自己寫代理類,而動態代理不需要寫代理類。

JDK動態代理與CGLIB實現區別

JDK動態代理底層實現:

JDK的動態代理使用Java的反射技術生成動態代理類,只能代理實現了接口的類, 沒有實現接口的類不能實現動態代理。

CGLIB動態代理底層實現:

運行時動態的生成一個被代理類的子類(通過ASM字節碼處理框架實現),子類重寫了被代理類中所有非final的方法,在子類中採用方法攔截的技術攔截所有父類方法的調用,不需要被代理類對象實現接口,從而CGLIB動態代理效率比Jdk動態代理反射技術效率要高。

案例:使用AOP攔截Controller所有請求日誌

@Aspect
@Component
@Slf4j
public class AopLogAspect {


    // 申明一個切點 裏面是 execution表達式
    @Pointcut("execution(* com.xuyu.controller.*.*(..))")
    private void serviceAspect() {
    }
    // 請求method前打印內容
    @Before(value = "serviceAspect()")
    public void methodBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        // 打印請求內容
        log.info("===============請求內容===============");
        log.info("請求地址:" + request.getRequestURL().toString());
        log.info("請求方式:" + request.getMethod());
        log.info("請求類方法:" + joinPoint.getSignature());
        log.info("請求類方法參數:" + Arrays.toString(joinPoint.getArgs()));
        log.info("===============請求內容===============");

    }

    // 在方法執行完結後打印返回內容
    @AfterReturning(returning = "o", pointcut = "serviceAspect()")
    public void methodAfterReturing(Object o) {
        log.info("--------------返回內容----------------");
        log.info("Response內容:" + o.toString());
        log.info("--------------返回內容----------------");

    }
}

Maven依賴信息

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
</parent>
<dependencies>

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.12</version>
    </dependency>
    <!-- sprinboot web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

結果

 ===============請求內容===============
 : 請求地址:http://127.0.0.1:8080/getUser
 : 請求方式:GET
 : 請求類方法:String com.xuyu.service.controller.IndexController.getUser(String,Integer)
 : 請求類方法參數:[xuyu, 2]
 : ===============請求內容===============
 : >>>userName:{},xuyu
 : --------------返回內容----------------
 : Response內容:success_getUser
 : --------------返回內容----------------
 

知識分享

本文參考:螞蟻課堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章