面向方面的编程(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 { }
可以使用“ SecurityAnnotation”为调用添加注释之前需要检查的方法,然后在Aspect中,我们将获得其中的一部分,并且将在调用并拦截权限检查之前截取所有这些方法。
目标代码:
@Service public class MyService { @SecurityAnnotation public Balance getAccountBalance(User user) {
来电显示:
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) { }
等
项目结构

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/> </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面向方面的编程