アスペクト指向プログラミング、Spring AOP

アスペクト指向プログラミング(AOP)は、手続き型およびオブジェクト指向プログラミング(OOP)をさらに発展させたプログラミングパラダイムです。 AOPの考え方は、いわゆるエンドツーエンド機能を強調することです。 そして、すべてが順調であるため、ここではJava-Spring @AspectJ注釈スタイル(スキーマベースのxmlスタイルもあり、機能は似ています)でそれを行う方法を示します。



横断的機能の強調表示







画像



そして後



画像



つまり いくつかのモジュールに影響を与える機能がありますが、ビジネスコードに直接関係しないため、別の場所に配置することをお勧めします。これを上の図に示します。



参加ポイント





画像



参加ポイント-AOPの次の概念。これらは観察ポイントであり、機能の導入が計画されているコードへのアクセスです。



ポイントカット



画像



ポイントカットはスライス、接続ポイントのクエリであり、1つ以上のポイントにすることができます。 ポイントを照会するためのルールは非常に多様です。上の図では、メソッドと特定のメソッドに対する注釈の要求です。 ルールは&&、||、!で組み合わせることができます!



アドバイス



画像



アドバイス-カットポイントで実行される一連の指示(Pointcut)。 指示は、さまざまなタイプのイベントで実行できます。





1つのPointcutで、さまざまなタイプのアドバイスを「ハング」させることができます。



アスペクト



画像



アスペクト-ポイントカットとアドバイスの説明を含むモジュール。



ここで例を挙げて、最後にすべて(またはほとんどすべて)が適切に配置されるようにします。 私たちは皆、ビジネスコードに関係なく、多くのモジュールに浸透するロギングコードについて知っていますが、それなしでは不可能です。 そのため、この機能をビジネスコードから分離します。

例-コードロギング


対象サービス



@Service public class MyService { public void method1(List<String> list) { list.add("method1"); System.out.println("MyService method1 list.size=" + list.size()); } @AspectAnnotation public void method2() { System.out.println("MyService method2"); } public boolean check() { System.out.println("MyService check"); return true; } }
      
      





ポイントカットとアドバイスの説明を含むアスペクト。



 @Aspect @Component public class MyAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Before("callAtMyServicePublic()") public void beforeCallAtMethod1(JoinPoint jp) { String args = Arrays.stream(jp.getArgs()) .map(a -> a.toString()) .collect(Collectors.joining(",")); logger.info("before " + jp.toString() + ", args=[" + args + "]"); } @After("callAtMyServicePublic()") public void afterCallAt(JoinPoint jp) { logger.info("after " + jp.toString()); } }
      
      





そして、呼び出しテストコード



 @RunWith(SpringRunner.class) @SpringBootTest public class DemoAspectsApplicationTests { @Autowired private MyService service; @Test public void testLoggable() { List<String> list = new ArrayList(); list.add("test"); service.method1(list); service.method2(); Assert.assertTrue(service.check()); } }
      
      





説明 ターゲットサービスでは、ロギングについては言及されていません。呼び出しコードでは、すべてのロギングが個別のモジュールに集中しています。

@Aspect

class MyAspect ...








ポイントカットで



  @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { }
      
      





戻り値の型*と引数の数(..)を使用して、すべてのパブリックMyServiceメソッドを要求しました



Pointcut(callAtMyServicePublic)を参照するアドバイスの前後に、ログに書き込むための指示を書きました。 JoinPointは、追加情報を提供する必須パラメーターではありませんが、使用する場合は最初にする必要があります。

すべてが異なるモジュールに分けられています! 呼び出し元コード、ターゲット、ロギング。



コンソールの結果



画像



ポイントカットのルールは異なる場合があります

ポイントカットとアドバイスのいくつかの例:


メソッドの注釈の要求。



 @Pointcut("@annotation(AspectAnnotation)") public void callAtMyServiceAnnotation() { }
      
      





彼へのアドバイス



  @Before("callAtMyServiceAnnotation()") public void beforeCallAt() { }
      
      





ターゲットメソッドのパラメーターを示す特定のメソッドの要求



 @Pointcut("execution(* com.example.demoAspects.MyService.method1(..)) && args(list,..))") public void callAtMyServiceMethod1(List<String> list) { }
      
      





彼へのアドバイス



  @Before("callAtMyServiceMethod1(list)") public void beforeCallAtMethod1(List<String> list) { }
      
      





結果を返すためのポイントカット



  @Pointcut("execution(* com.example.demoAspects.MyService.check())") public void callAtMyServiceAfterReturning() { }
      
      





彼へのアドバイス



  @AfterReturning(pointcut="callAtMyServiceAfterReturning()", returning="retVal") public void afterReturningCallAt(boolean retVal) { }
      
      





注釈を使用して、タイプAroundのアドバイスの権利を確認する例


 @Retention(RUNTIME) @Target(METHOD) public @interface SecurityAnnotation { } // @Aspect @Component public class MyAspect { @Pointcut("@annotation(SecurityAnnotation) && args(user,..)") public void callAtMyServiceSecurityAnnotation(User user) { } @Around("callAtMyServiceSecurityAnnotation(user)") public Object aroundCallAt(ProceedingJoinPoint pjp, User user) { Object retVal = null; if (securityService.checkRight(user)) { retVal = pjp.proceed(); } return retVal; }
      
      





呼び出しの前にチェックする必要があるメソッドに「SecurityAnnotation」という注釈を付けることができます。Aspectではそれらのスライスを取得し、呼び出しと権利チェックが行われる前にすべてのメソッドをインターセプトします。



ターゲットコード:



 @Service public class MyService { @SecurityAnnotation public Balance getAccountBalance(User user) { // ... } @SecurityAnnotation public List<Transaction> getAccountTransactions(User user, Date date) { // ... } }
      
      





発信者コード:



 balance = myService.getAccountBalance(user); if (balance == null) { accessDenied(user); } else { displayBalance(balance); }
      
      





つまり 呼び出しコードとターゲットでは、権利の検証はなく、ビジネスコード自体のみが検証されます。

タイプAroundのアドバイスを使用して同じサービスをプロファイリングする例


 @Aspect @Component public class MyAspect { @Pointcut("execution(public * com.example.demoAspects.MyService.*(..))") public void callAtMyServicePublic() { } @Around("callAtMyServicePublic()") public Object aroundCallAt(ProceedingJoinPoint call) throws Throwable { StopWatch clock = new StopWatch(call.toString()); try { clock.start(call.toShortString()); return call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } } }
      
      





MyServiceメソッドを呼び出して呼び出し元のコードを実行すると、各メソッドの呼び出し時間を取得できます。 したがって、呼び出しコードとターゲットを変更せずに、ロギング、プロファイラー、セキュリティという新しい機能を追加しました。

UIフォームでの使用例


設定により、フォーム上のフィールドを非表示/表示するコードがあります。



 public class EditForm extends Form { @Override public void init(Form form) { formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE)); // ... }
      
      





タイプAroundのアドバイスでupdateVisibilityを削除することもできます。



 @Aspect public class MyAspect { @Pointcut("execution(* com.example.demoAspects.EditForm.init() && args(form,..))") public void callAtInit(Form form) { } // ... @Around("callAtInit(form)") public Object aroundCallAt(ProceedingJoinPoint pjp, Form form) { formHelper.updateVisibility(form, settingsService.isVisible(COMP_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_LAST_NAME)); formHelper.updateVisibility(form, settingsService.isVisible(COMP_BIRTH_DATE)); Object retVal = pjp.proceed(); return retVal; }
      
      





など



プロジェクト構造



画像



ポンファイル
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demoAspects</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demoAspects</name> <description>Demo project for Spring Boot Aspects</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
      
      







素材



Springによるアスペクト指向プログラミング



All Articles