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

寫在前面:設計模式源於生活,而又高於生活!

什麼是代理模式

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

爲何使用代理模式

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

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

代理模式實現原理

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

抽象主題角色(Subject):能夠是接口,也能夠是抽象類數據庫

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

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

代理模式應用場景

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

代理模式建立的方式

靜態代理 和 動態代理函數

靜態代理

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

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

一句話,本身手寫代理類就是靜態代理。

基於接口實現方式

//主題(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
 : --------------返回內容----------------
 

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

本文參考:螞蟻課堂

相關文章
相關標籤/搜索