面向方面的编程,Spring AOP

面向方面的编程(AOP)是一种编程范例,是对过程和面向对象编程(OOP)的进一步发展。 AOP的思想是强调所谓的端到端功能。 一切都井井有条,这里我将展示如何使用Java-Spring @AspectJ批注样式(也有基于模式的xml样式,功能相似)。

突出端到端功能




图片

和之后

图片

即 有影响几个模块的功能,但是它与业务代码没有直接关系,因此最好将其放在单独的位置,如上图所示。

连接点



图片

连接点-AOP的下一个概念,它们是观察点,加入了计划引入功能的代码。

切入点


图片

切入点是切片,是对附件点的查询,它可以是一个或多个点。 查询点的规则非常多样化,在上图中,对方法的注释请求和特定方法。 规则可以由&&,||组合!

忠告


图片

建议-在切点(Pointcut)上执行的一组指令。 可以针对各种类型的事件执行指示:

  • 之前 -方法调用之前
  • 之后 -方法调用之后
  • 返回之后 -从函数返回值之后
  • 抛出后 -如果发生异常
  • 最终之后 -如果执行了finally块
  • 围绕 -您可以在方法调用之前执行pre。,Post。,Processing,并且通常也绕过方法调用。

在一个切入点上,您可以“挂起”几种不同类型的建议。

方面


图片

方面-包含切入点和建议描述的模块。

现在,我举一个例子,最后一切都会(或几乎全部)解决。 我们都知道有关渗透到许多模块的日志代码,这些模块与业务代码无关,但是没有它是不可能的。 因此,我将此功能与业务代码分开。
示例-代码记录

目标服务

@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) { } 

通过注释检查“周围”类型的建议的权限的示例

 @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); } 

即 在调用代码和目标中,没有权限检查,只有业务代码本身。
使用“ 周围 ”类型的建议对同一服务进行概要分析的示例

 @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)); // ... } 

您还可以在“ 周围 ”类型的通知中删除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; } 



项目结构

图片

pom文件
 <?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面向方面的编程

Source: https://habr.com/ru/post/zh-CN428548/


All Articles