Java框架之spring(二)靜態和動態代理、AOP及AOP的實現方式

引用博客:
https://blog.csdn.net/m13666368773/article/details/7802126
https://www.cnblogs.com/hellokuangshen/p/11249253.html
https://www.cnblogs.com/xdp-gacl/p/3495887.html
優秀的博客,大家可以多看看!

代理

爲什麼麼要學習代理模式,因爲aop的底層機制就是動態代理;

代理模式:

  • 靜態代理
  • 動態代理

靜態代理

代理模式的角色分析:

抽象角色:一般會使用抽象類或者接口實現

真實角色:被代理的角色

代理角色:代理真實角色,代理真實角色後,一般會做一些附屬的操作;

客戶:使用代理角色進行一些操作

在這裏插入圖片描述

代碼實現

接口

//租房的接口:抽象
public interface Rent {
    //租房
    void rent();
}

真實對象

//這個房子要出租
public class Host implements Rent {

    //出租
    public void rent(){
        System.out.println("房東要出租房子");
    }

}

代理對象

package com.kuang.staticproxy;

//房屋中介--代理
public class Proxy implements Rent {

    //房東
    private Host host;

    public void setHost(Host host) {
        this.host = host;
    }

    public void rent() {
        lookHouse();
        host.rent();
        fare();
    }

    private void lookHouse(){
        System.out.println("中介帶你看房");
    }

    private void fare(){
        System.out.println("收取中介費");
    }


}

測試

public class You {
    public static void main(String[] args) {

        Host host = new Host();

        Proxy proxy = new Proxy();
        proxy.setHost(host);
        proxy.rent();

    }
}

輸出結果:
在這裏插入圖片描述

靜態代理模式的好處

  • 可以使真實角色更加純粹,不用去關注一些公共的事情;
  • 公共的業務由代理來完成,實現業務的分工;
  • 公共業務的要擴展的話,可以更加集中和方便;

缺點:

  • 假如我們的真實角色變得非常多,代理類也會隨之增多,工作量變大,開發效率變低!

然後我們想需要一種能夠有靜態代理的全部好處,但是又不存在這種缺點的東西。

動態代理

動態代理和靜態代理的角色都是一樣;

靜態代理模式的代理類是我們提前寫好的,動態代理的類是動態生成的;

動態代理大概分兩類:
基於接口實現:JDK

基於類實現:cglib

當今用的比較多的是 JAVAssist來生成動態代理
瞭解動態代理之前,需要掌握兩個類:

  • InvocationHandler
  • Proxy

InvocationHandler是由代理實例的調用處理程序實現的接口

invoke(Object proxy, 方法 method, Object[] args) 處理代理實例上的方法調用並返回結果。

Proxy提供了創建動態代理類和實例的靜態方法,它也是由這些方法創建的所有動態代理類的超類。**

newProxyInstance(ClassLoader loader, 類<?>[] interfaces, InvocationHandler h) 返回指定接口的代理類的實例,該接口將方法調用分派給指定的調用處理程序。

代碼實現

抽象角色

//租房的接口:抽象
public interface Rent {
    //租房
    void rent();
}

真實角色

//這個房子要出租
public class Host implements Rent {

    //出租
    public void rent(){
        System.out.println("房東要出租房子");
    }

}

動態代理類生成的接口對象

package com.kuang.dynamicproxy;

import com.kuang.staticproxy.Rent;

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

public class InvocationHandlerProxy implements InvocationHandler {

    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //動態生成代理類
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),
                this);
    }

    //proxy:代理類
    //method :代理類的調用處理程序的方法 的對象
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        lookHouse();
        Object result = method.invoke(rent, args);
        zhongJieFei();
        return result;
    }

    private void lookHouse(){
        System.out.println("中介帶看房子");
    }

    private void zhongJieFei(){
        System.out.println("收中介費");
    }


}

測試

public static void main(String[] args) {

    Host host = new Host();
    InvocationHandlerProxy ihp = new InvocationHandlerProxy();
    ihp.setRent(host);

    Rent proxy = (Rent) ihp.getProxy(); //dong

    proxy.rent();
}

動態代理的好處:

  • 可以使真實角色更加純粹,不用去關注一些公共的事情;
  • 公共的業務由代理來完成,實現業務的分工;
  • 公共業務的要擴展的話,可以更加集中和方便;
  • 一個動態代理,一般代理一類的業務,一個動態代理可以代理多個類,代理接口;

AOP(

什麼是AOP

AOP是OOP的延續,是Aspect Oriented Programming的縮寫,意思是面向切面編程。可以通過預編譯方式和運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP可以說也是這種目標的一種實現。

我們現在做的一些非業務,如:日誌、事務、安全等都會寫在業務代碼中(也即是說,這些非業務類橫切於業務類),但這些代碼往往是重複,複製——粘貼式的代碼會給程序的維護帶來不便,AOP就實現了把這些業務需求與系統需求分開來做。這種解決的方式也稱代理機制

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

AOP在Spring中的作用

提供聲明式事務; 允許用戶自定義切面

橫切關注點:跨越應用程序多個模塊的方法或功能。即是,與我們業務邏輯無關的,但是我們需要關注的部分,就是橫切關注點。如日誌 , 安全 , 緩存 , 事務等等 …

切面(ASPECT):橫切關注點 被模塊化 的特殊對象。即,它是一個類。

通知(Advice):切面必須要完成的工作。即,它是類中的一個方法。

目標(Target):被通知對象。

代理(Proxy):向目標對象應用通知之後創建的對象。

切入點(PointCut):切面通知 執行的 “地點”的定義。

連接點(JointPoint):與切入點匹配的執行點。

在這裏插入圖片描述
SpringAOP中,通過Advice定義橫切邏輯,Spring中支持5種類型的Advice:
在這裏插入圖片描述
即 aop 在 不改變原有代碼的情況下 , 去增加新的功能 .

使用SpringAPI實現AOP

編寫業務類

接口

package com.kuang.service;

public interface UserService {

    void add();
    void delete();
    void update();
    void query();

}

實現類

package com.kuang.service;

public class UserServiceImpl implements UserService {

    public void add() {
        System.out.println("增加了一個用戶");
    }

    public void delete() {
        System.out.println("刪除了一個用戶");
    }

    public void update() {
        System.out.println("更新了一個用戶");
    }

    public void query() {
        System.out.println("查詢了一個用戶");
    }

}

定義日誌增加類實現

package com.kuang.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {

    //method:要執行的目標對象的方法
    //objects:要被調用的方法的參數
    //o:目標對象
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被執行了");
    }

}


package com.kuang.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {

    //returnValue : 返回值
    //method : 被調用的方法
    //args : 被調用的方法對象的參數
    //target : 被調用的目標對象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("執行了"+target.getClass().getName()
        +"的"+method.getName()+"方法"
        +"返回值"+returnValue);
    }

}

編寫Spring核心配置文件

【導入約束】

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--註冊bean-->
    <bean id="userService" class="com.kuang.service.UserServiceImpl"/>

    <!--註冊日誌類的bean-->
    <bean id="log" class="com.kuang.log.Log"/>
    <bean id="afterLog" class="com.kuang.log.AfterLog"/>

    <!--使用spring的aop切入
    1.導入約束:
        xmlns:aop="http://www.springframework.org/schema/aop"
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
    2.aop:config


    -->
    <aop:config>
        <!--切入點
        expression 表達式,表示要切入的位置
        語法:execution([類的修飾符] [類的全路徑] [方法] [參數])
        -->
        <aop:pointcut id="pointcut" expression
                ="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
        <!--執行通知,增強-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>

    </aop:config>


</beans>

測試類

【需要導入aop的織入包】
https://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.9


<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

編寫測試類

package com.kuang.service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringAopTest {
    @Test
    public void test(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = (UserService) context.getBean("userService");
        /*
        問題:報錯,沒有aspectjweaver包
        使用aop需導入一個包
                <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>


        問題:spring調用的是真實對象userService
        暗箱中: 動態的修改userService,在方法的前後或者其他通知的地方增加了我們的切入代碼。
        我們就可以實現依舊調用原來的對象,產生增加新的業務的功能;

         */

        userService.add();

    }
}

Aop很重要:一定要理解其中思路,主要要思想這一塊,橫向編程

Spring的AOP就是將公共業務代碼(日誌,事務,安全…),和業務類(真實對象)結合起來,實現公共業務的重複利用,解耦,本質還是動態代理。

自定義類實現AOP

  1. 真實對象和之前的一樣
  2. 自定一個Aop增強類:也就是所謂的切面
package com.kuang.diy;

public class Diy {

    public void before(){
        System.out.println("===================方法執行前===================");
    }

    public void after(){
        System.out.println("===================方法執行後===================");
    }

}

  1. 注入bean
<!--自定義的AOP增強類-->
<bean id="diy" class="com.kuang.diy.Diy"/>
  1. 使用aop進行增強
<!--編寫aop配置文件-->
<aop:config>

    <!--切面-->
    <aop:aspect ref="diy">
        <!--切入點-->
        <aop:pointcut id="diyPointCut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
        <aop:before method="before" pointcut-ref="diyPointCut"/>
        <aop:after method="after" pointcut-ref="diyPointCut"/>

    </aop:aspect>

</aop:config>

  1. 測試類
@Test
public void test2(){

    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    UserService userService = (UserService) context.getBean("userService");

    userService.add();

}
  1. 運行結果圖
    在這裏插入圖片描述

使用註解實現AOP

  • @before @after…
  1. 目標對象不變

  2. 編寫增強的類,寫註解

    • 注意點:需要將類註解爲 切面
    • 方法上就是,切入點,增強
package com.kuang.anno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;


//必須要寫切面註解,否則就無法切入;
@Aspect
public class Anno {


    //切入點可以直接寫到增強上面
    @Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("=====================方法執行前=====================");
    }

    @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=====================方法執行後=====================");
    }

    //環繞增加
    //切入點參數 : ProceedingJoinPoint
    //環繞增強本質:
    /*
    將目標對象作爲參數傳遞進方法中,
    在方法執行的前後,增加一些操作,僅此而已
    但是可以拿到一些目標對象的東西;
     */

    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("環繞前");
        System.out.println("簽名:"+jp.getSignature());//獲得執行的切入點

        //執行目標方法
        Object proceed = jp.proceed();

        System.out.println("環繞後");
        System.out.println(proceed); //null

    }


}
  1. 配置文件
<!--註解實現AOP的類-->
<bean id="anno" class="com.kuang.anno.Anno"/>
<!--識別註解,自動代理-->
<aop:aspectj-autoproxy/>
  1. 測試類
@Test
public void test3(){

    ApplicationContext context = new ClassPathXmlApplicationContext("anno.xml");

    UserService userService = (UserService) context.getBean("userService");

    userService.add();

}

AOP小結

  • 本質就是動態代理
  • 需要到一個包,用來進行aop織入的包: aspectjweaver
  • 注意別遺漏了切面;
  • 三種實現AOP的方法
    • 使用SpringAPI來實現AOP
    • 使用自定義類來實現AOP
    • 使用註解實現AOP
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章