Spring AOPの詳細解説(超総合)

1.AOP

1。概要

AOPとはAspect Oriented Programmingの略で、アスペクト指向プログラミングを意味し、プリコンパイルと実行時の動的プロキシによりプログラムの機能の一元的な維持を実現する技術です。AOP は、ソフトウェア開発のホットスポットである OOP の継続であり、Spring フレームワークの重要な内容であり、関数型プログラミングの派生パラダイムです。AOP を使用すると、ビジネス ロジックのさまざまな部分を分離することができます。これにより、ビジネス ロジックのさまざまな部分間の結合度が低減され、プログラムの再利用性が向上し、同時に開発の効率も向上します。

Spring AOP は、AOP プログラミング モデルに基づいたフレームワークであり、システム間のコードの重複を効果的に削減し、疎結合の目的を達成できます。Spring AOP は Pure Java で実装され、特別なコンパイル プロセスやクラス ローダーを必要とせず、実行時にプロキシを介して拡張コードをターゲット クラスに埋め込みます。実装には、インターフェイス ベースの JDK 動的プロキシと継承ベースの CGLIB 動的プロキシの 2 つがあります。

AspectJ は Java 言語に基づいた AOP フレームワークであり、Spring 2.0 から Spring AOP は AspectJ のサポートを導入しました。AspectJ は Java 言語を拡張し、コンパイル時に水平コード埋め込みを行う特別なコンパイラを提供します。

2. AOP の用語

AOP をより深く理解するには、AOP に関連する用語をいくつか理解する必要があります。これらの用語は Spring に固有のものではなく、その一部は AspectJ などの他の AOP フレームワークにも同様に適用されます。それらの意味は次のとおりです。

**アドバイス (アドバイス):** は、ジョインポイント、つまりエントリ ポイントの拡張コンテンツをインターセプトした後に行うことを指します。通知には、「前後」「前後」(通知の種類については後述)、事前通知、事後通知、周囲通知、最終通知、例外通知などがあります。一人で取り囲む。

**Joinpoint (Joinpoint):** はインターセプトできるポイントを指します。Spring では、動的プロキシによってインターセプトできるメソッドを指します (メソッドの前と後ろ (例外をスローする) はすべて接続です) )ポイント);

Pointcut : インターセプトする Joinpoint、つまり、インターセプトされた接続ポイント、インターセプトされたクラス内のメソッドを参照します (接続ポイントをフィルターし、必要なメソッドをいくつか選択します)。

**アスペクト:**アスペクトはポイントカットとアドバイスで構成されており、これには拡張ロジックの定義とポイントカットの定義の両方、つまり 2 つの組み合わせが含まれます。

はじめに: 新しいメソッド属性を既存のクラスに追加できるようにします (つまり、アスペクト (つまり、新しいメソッド属性: 通知定義) をターゲット クラスに使用します)。

**ターゲット (Target): **プロキシのターゲット オブジェクト、つまり拡張オブジェクト (冒頭で述べたターゲット クラス、つまり通知されるオブジェクト)。

**プロキシ (プロキシ):** は生成されたプロキシ オブジェクトを指します。

**ウィービング:** は、拡張コードをターゲットに適用してプロキシ オブジェクトを生成するプロセスを指します。

Spring AOP はアスペクトの実装を担当するフレームワークであり、アスペクトによって定義された拡張ロジックをアスペクトによって指定されたエントリ ポイントに織り込みます。

AOP は Spring のコアの 1 つであり、AOP はプログラミングを簡素化するために Spring でよく使用されます。Spring フレームワークで AOP を使用する主な利点は次のとおりです。

  • 特に EJB 宣言型サービスの代替として、宣言型エンタープライズ サービスを提供します。最も重要なことは、このサービスは宣言型トランザクション管理であるということです。
  • ユーザーがカスタム アスペクトを実装できるようにします。OOP プログラミングに適さない一部のシナリオでは、AOP を使用して補完します。
  • ビジネスロジックの各部分を分離できるため、ビジネスロジックの各部分間の結合度が下がり、プログラムの再利用性が向上し、開発効率も向上します。

3. 入門ケース

  1. Maven プロジェクトを作成し、pom.xml を変更して spring-context、spring-test、aspectjweaver、lombok の依存関係を追加します。
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <!-- 引入spring框架相关jar,包含了aop相关的支持-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
  </dependency>
  <!-- spring单元测试支持的jar-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
  </dependency>
  <!-- spring aop中解析切入点表达式,以及用到其中的注解-->
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
  </dependency>
  <!-- @DATA等注解支持-->   
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
  </dependency>
</dependencies>
  1. ユーザーサービス
public interface UserService {
    
    
    void add();
    void del();
    void update();
    void select();
}
  1. UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    
    

    @Override
    public void add() {
    
    
        System.out.println("add");
    }

    @Override
    public void del() {
    
    
        System.out.println("del");
    }

    @Override
    public void update() {
    
    
        System.out.println("update");
    }

    @Override
    public void select() {
    
    
        System.out.println("select");
    }
}
  1. ロギング機能がシミュレートされる通知クラス Logger を作成します。
public class Logger {
    
    
    public void printLog(){
    
    
        System.out.println("日志记录了.....");
    }
}
  1. 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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置目标类的bean-->
    <bean id="userService" class="com.hqyj.cl.service.impl.UserServiceImpl"/>
    <!-- 配置通知类的bean-->
    <bean id="logger" class="com.hqyj.cl.utils.Logger"/>
    <!-- 配置AOP-->
    <aop:config>
        <!--切入点
            expression: 表达式
                execution(* com.hqyj.cl.pojo.UserServiceImpl.*(..))
                第一个 *: 返回值类型
                第二个参数: 切入的位置
                第三个参数 *: 切入的方法
                (..): 表示方法参数随便是多少个
        -->
        <aop:aspect id="aspect" ref="logger">
            <!-- 配置前置通知-->
            <aop:before method="printLog" pointcut="execution(* com.hqyj.cl.service.impl.UserServiceImpl.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. UserServiceImplTest
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class UserServiceImplTest {
    
    
    @Autowired
    private UserService userService;

    @Test
    public void add(){
    
    
        userService.add();
    }
}

テスト結果は add メソッドを呼び出す前に文を出力します日志记录了.....

4. 通知の種類

アドバイスは直訳すると通知、一部の資料は「強化された処理」と訳され、下表のように全部で5種類あります。

通知する 説明する
前(事前通知) ターゲット メソッド呼び出しの前にアドバイス メソッドが実行される
帰国後(帰国後の通知) 通知メソッドは、ターゲット メソッドが返された後に呼び出されます。
投球後(例外通知) 通知メソッドは、ターゲット メソッドが例外をスローした後に呼び出されます。
後(最終通知) アドバイス メソッドは、ターゲット メソッドが返された後、または例外が発生した後に呼び出されます。
あたり(通知あたり) アドバイスメソッドはターゲットメソッドをラップします

4.1 サンプルコード

  1. ロガー通知クラス
public class Logger {
    
    
    public void printLogBefore(){
    
    
        System.out.println("日志记录了...前置通知");
    }

    public void printLogRound(){
    
    
        System.out.println("日志记录了...环绕通知");
    }

    public void printLogAfter(){
    
    
        System.out.println("日志记录了...后置通知");
    }

    public void printLogException(){
    
    
        System.out.println("日志记录了...异常通知");
    }

    public void printLogFinally(){
    
    
        System.out.println("日志记录了...最终通知");
    }
}
  1. ユーザーサービス
public interface UserService {
    
    
    void add();
    void del();
    void update();
    void select();
}
  1. UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    
    

    @Override
    public void add() {
    
    
//        int i = 1/0;
        System.out.println("add");
    }

    @Override
    public void del() {
    
    
        System.out.println("del");
    }

    @Override
    public void update() {
    
    
        System.out.println("update");
    }

    @Override
    public void select() {
    
    
        System.out.println("select");
    }
}
  1. 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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置目标类的bean-->
    <bean id="userService" class="com.hqyj.cl.service.impl.UserServiceImpl"/>
    <!-- 配置通知类的bean-->
    <bean id="logger" class="com.hqyj.cl.utils.Logger"/>
    <!-- 配置AOP-->
    <aop:config>
        <!--切入点
            expression: 表达式
                execution(* com.hqyj.cl.pojo.UserServiceImpl.*(..))
                第一个 *: 返回值类型
                第二个参数: 切入的位置
                第三个参数 *: 切入的方法
                (..): 表示方法参数随便是多少个
        -->
        <aop:aspect id="aspect" ref="logger">
            <!-- aop:pointcut标签单独配置切入点表达式,方便各通知标签引用 -->
            <aop:pointcut id="pointcut" expression="execution(* com.hqyj.cl.service.*.*(..))"/>
            <!-- 配置前置通知-->
            <aop:before method="printLogBefore" pointcut-ref="pointcut"/>
            <!-- 配置后置通知-->
            <aop:after-returning method="printLogAfter" pointcut-ref="pointcut"/>
            <!-- 异常通知配置/后置通知和异常通知这两类不会同时通知-->
            <aop:after-throwing method="printLogException" pointcut-ref="pointcut"/>
            <!-- 最终通知配置-->
            <aop:after method="printLogFinally" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. テストクラス
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class UserServiceImplTest {
    
    

    @Test
    public void add(){
    
    
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ac.getBean("userService", UserService.class);
        userService.add();
    }
}

例外が発生しない場合、出力は次のようになります。

日志记录了...前置通知
add
日志记录了...后置通知
日志记录了...最终通知

例外が発生した場合、出力は次のようになります。

日志记录了...前置通知
日志记录了...异常通知
日志记录了...最终通知

4.2 サラウンド通知

  • ターゲット メソッドの実行前と実行後の両方で追加のコードを実行できるという通知。
  • サラウンド通知では、ターゲット メソッドが実行される前に、ターゲット メソッドを明示的に呼び出す必要があります。この明示的な呼び出しは、ProceedingJoinPoint を通じて実現されます。サラウンド通知でこのタイプの仮パラメータを受け取ることができ、Spring コンテナは自動的にobject Enter、このパラメータはサラウンド通知の最初のパラメータ位置になければならないことに注意してください。
  • サラウンド通知のみが ProceedingJoinPoint を受信でき、他の通知は JoinPoint のみを受信できます。
  • 周囲の通知は戻り値を返す必要があります。そうでない場合、実際の呼び出し元は戻り値を取得せず、null のみを取得します。
  • 周囲の通知には、ターゲット メソッドを実行するかどうか、値を返すかどうか、および戻り値を変更するかを制御する機能があります。
  • サラウンド通知にはそのような機能がありますが、技術的に不可能だからではなく、ソフトウェア階層化の「高い結合性と低い結合性」という目標を破壊しないように注意して使用する必要があります。
  1. ロガー通知クラス
public class Logger {
    
    
    public void printLogBefore(){
    
    
        System.out.println("日志记录了...前置通知");
    }

    public Object printLogAround(ProceedingJoinPoint pjp){
    
    
        System.out.println("日志记录了...环绕通知");
        Object result = null;
        try{
    
    
            // 模拟执行前置通知
            printLogBefore();
            // 得到方法执行所需的参数
            Object[] args = pjp.getArgs();
            // 明确调用业务层方法(切入点方法)
            result = pjp.proceed(args);
            // 模拟执行后置通知
            printLogAfter();
            return result;
        }catch (Throwable t){
    
    
            // 模拟执行异常通知
            printLogException();
            // //如果真实对象调用方法时发生异常,将异常抛给虚拟机
            throw new RuntimeException(t);
        }finally {
    
    
            // 模拟执行最终通知
            printLogFinally();
        }
    }

    public void printLogAfter(){
    
    
        System.out.println("日志记录了...后置通知");
    }

    public void printLogException(){
    
    
        System.out.println("日志记录了...异常通知");
    }

    public void printLogFinally(){
    
    
        System.out.println("日志记录了...最终通知");
    }
}
  1. 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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 配置目标类的bean-->
    <bean id="userService" class="com.hqyj.cl.service.impl.UserServiceImpl"/>
    <!-- 配置通知类的bean-->
    <bean id="logger" class="com.hqyj.cl.utils.Logger"/>
    <!-- 配置AOP-->
    <aop:config>
        <!--切入点
            expression: 表达式
                execution(* com.hqyj.cl.pojo.UserServiceImpl.*(..))
                第一个 *: 返回值类型
                第二个参数: 切入的位置
                第三个参数 *: 切入的方法
                (..): 表示方法参数随便是多少个
        -->
        <aop:aspect id="aspect" ref="logger">
            <!-- aop:pointcut标签单独配置切入点表达式,方便各通知标签引用 -->
            <aop:pointcut id="pointcut" expression="execution(* com.hqyj.cl.service.*.*(..))"/>
            <!-- 环绕通知-->
            <aop:around method="printLogAround" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. テストクラス
@Test
public void add(){
    
    
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ac.getBean("userService", UserService.class);
    userService.add();
}

4.3 ジョインポイント

どのアドバイス メソッドでも、最初のパラメータを org.aspectj.lang.JoinPoint タイプとして定義できます (周囲のアドバイスでは、最初のパラメータを JoinPoint のサブクラスである ProceedingJoinPoint タイプとして定義する必要があります)。JoinPoint インターフェイスは、getArgs() (メソッド パラメーターを返す)、getSignature() (通知されるメソッドに関する情報を返す) などの一連の便利なメソッドを提供します。

2. アノテーションに基づいて AOP を実装する (理解する)

1. 注意事項

完全なアノテーションによる AOP の実装では、主に次のアノテーションが使用されます。

// 标明这是一个配置文件,代替了之前的Spring-config.xml配置文件
@Configuration  
// 相当于Spring-config.xml配置文件<context:component-scan base-package="com.hqyj.cl"/>,配置自动扫描的包
@ComponentScan("com.hqyj.aop") 
// 相当于Spring-config.xml配置文件<aop:aspectj-autoproxy/>,自动为切面方法中匹配的方法所在的类生成代理对象
@EnableAspectJAutoProxy
// 标注这个类是一个切面
@Aspect 
// Pointcut是植入Advice的触发条件
@Pointcut
// 前置通知
@Before()
// 后置通知
@AfterReturning
// 最终通知
@After
// 异常通知
@AfterThrowing
// 环绕通知
@Around

2. サンプルコード

  1. SpringConfiguration 構成クラス
@Configuration
@ComponentScan("com.hqyj.cl.aop")
@EnableAspectJAutoProxy
public class SpringConfiguration {
    
    

    @Bean
    public UserService userService(){
    
    
        return new UserServiceImpl();
    }

}
  1. AnnotationLogger通知クラス
@Aspect // 标注这个类是一个切面
@Component // 标注该类会创建一个bean
public class AnnotationLogger {
    
    

    @Pointcut("execution(* com.hqyj.cl.service.*.*(..))")
    public void pointCut(){
    
    

    }

    // 前置通知:目标方法执行之前执行以下方法体的内容
    @Before("pointCut()")
    public void beforeMethod(){
    
    
        System.out.println("前置通知");
    }

    // 后置通知:目标方法正常执行完毕时执行以下代码
    @AfterReturning(value = "pointCut()")
    public void afterReturningMethod(){
    
    
        System.out.println("后置通知");
    }

    // 最终通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。
    @After("pointCut()")
    public void afterMethod(){
    
    
        System.out.println("最终通知");
    }

    // 异常通知:目标方法发生异常的时候执行以下代码
    @AfterThrowing("pointCut()")
    public void afterThrowing(){
    
    
        System.out.println("异常通知");
    }

}
  1. UserService と UserServiceImpl についても上記と同じ

  2. テストクラス

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class AnnotationLoggerTest {
    
    
    @Test
    public void annotationLoggerTest(){
    
    
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = ac.getBean("userService", UserService.class);
        userService.add();
    }
}

3.サラウンド通知

自分自身をテストしてください

3. 動的なエージェントプロセス

最初にアスペクトを構成し、次にアスペクトで通知を構成します。通知を特定の場所に向け、特定のメソッドまたは複数のメソッドの通知を構成するには、まずメソッドの場所を指定する必要があります。このとき、エントリ ポイントを構成する必要があります。プロセス全体は動的プロキシです。プロセス。

代理店モード: 静的な代理店モード。たとえば、Zhang San は家を借りたいのですが、家が見つからないため、仲介者を探す必要があります。これは典型的な代理店モードです。仲介者が代理人となり、張三氏が代理人となる。静的プロキシには制限があります。たとえば、Zhang San が家を借りたい場合は、レンタル代理店しか見つけることができませんが、結婚したい場合は、結婚式代理店を見つける必要があります。クラスにはプロキシ オブジェクトが必要です。動的プロキシ モード (InvocationHandler インターフェイスを実装する必要があります)。プロキシ オブジェクトは実行プロセス中に生成されます。オブジェクトが何であっても、それをクラスにカプセル化でき、ioc のリフレクションを通じてオブジェクトを取得できます。そのため、家を借りる場合でも、結婚式のお祝いの場合でも、要件に応じてプロキシ オブジェクトを動的に生成できます。

おすすめ

転載: blog.csdn.net/ailaohuyou211/article/details/130394284