面向切面编程,又叫AOP ,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
术语 | 说明 |
---|
切面 | 切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强 | 织入 | 织入是指将切面代码插入到目标对象的过程。 | 连接点 | 连接点指切面可以织入的位置。 | 切入点 | 切入点指切面具体织入的位置。 | 通知(Advice) | 通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 | 顾问(Advisor) | 顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。 不仅指定了切入时间点,还可以指定具体的切入点 |
准备环境
创建一个maven 项目,添加对应的依赖。
<?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>org.spring.ss</groupId>
<artifactId>spring04-aop</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring.verson>5.3.9</spring.verson>
<junit.version>5.7.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.verson}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
添加一个接口和实现类,用于切面测试。 org.spring.ss.service.IUserService
package org.spring.ss.service;
public interface IUserService {
public String say(String name);
public String run(String name);
}
org.spring.ss.service.impl.UserServiceImpl
package org.spring.ss.service.impl;
import org.spring.ss.service.IUserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements IUserService {
@Override
public String say(String name) {
return "hello, " + name;
}
@Override
public String run(String name) {
return "running, " + name;
}
}
添加配置类SpringConfig.java , 启用切面功能的使用。
package org.spring.ss;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class SpringConfig {
}
添加一个测试类,看下是否运行正常。
package org.spring.ss;
import org.junit.jupiter.api.Test;
import org.spring.ss.service.IUserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AOPTest {
@Test
public void aopTest1() {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
IUserService bean = ac.getBean(IUserService.class);
System.out.println(bean.say("xiaoping"));
System.out.println("****************分割线*********************");
}
@Test
public void aopTest2() {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
IUserService bean = ac.getBean(IUserService.class);
System.out.println(bean.run("xiaoping"));
System.out.println("****************分割线*********************");
}
}
输出:
hello, xiaoping
****************分割线*********************
running, xiaoping
****************分割线*********************
切面类
切入点表达式
表达式类型 | 说明 |
---|
execution | 定位到目标对象的方法上 | within | 切入指定类型的所有方法,默认不支持继承,需要使用 + 符号来支持继承关系 | this | 切入的代理对象类型,使用JDK代理时,指向接口和代理类proxy,cglib 代理时 指向接口和子类 | target | 切入目标对象的类型,即被代理对象的类型(包括接口和子类) | args | 参数的类型 | @target | | @args | 传入的参数有被该注解修饰 | @within | 类型修饰的注解 | @annotation | 方法修饰的注解 |
execution 表达式
语法: execution([访问权限类型] 返回值类型 [全限定类名] 方法名(参数名) [抛出的异常类型])
符合 | 含有 |
---|
* | 0到多个符合 | .. | 方法参数中表示任意个参数,用在包名后表示当前包及其子包 | + | 用在类名后表示当前类及其子类,用在接口后表接口及其实现 |
实例:
execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service..*.*(..))
execution(* *.service.*.*(..))
execution(* *..service.*.*(..))
示例
package org.spring.ss.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspectJ01 {
@Pointcut("execution(* org.spring.ss.service.impl.*.fun2(..))")
public void pointcut1() {
}
@Before("pointcut1()")
public void aspectMethod01() {
System.out.println("before ....");
}
@Before("within(org.spring.ss.service.impl.UserServiceImpl)")
public void aspectMethod02() {
System.out.println("before ....222");
}
@Before("this(org.spring.ss.service.impl.UserServiceImpl)")
public void aspectMethod03() {
System.out.println("before ....333");
}
@Before("target(org.spring.ss.service.impl.UserServiceImpl)")
public void aspectMethod04() {
System.out.println("before ....444");
}
@Before("target(org.spring.ss.service.impl.UserServiceImpl) && args(String)")
public void aspectMethod05() {
System.out.println("before ....555");
}
@Before("@args(org.spring.ss.annotation.Annotation1)")
public void aspectMethod06() {
System.out.println("before ....666");
}
@Before("@within(org.spring.ss.annotation.Annotation3)")
public void aspectMethod07() {
System.out.println("before ....7777");
}
@Before("@annotation(org.spring.ss.annotation.Annotation3)")
public void aspectMethod08() {
System.out.println("before ....888");
}
}
通知类型
通知类型 | 说明 |
---|
前置通知 | 目标方法执行之前调用 | 后置通知 | 目标方法执行完成之后调用 | 环绕通知 | 目标方法执行前后都会调用方法,且能增强结果 | 异常处理通知 | 目标方法出现异常调用 | 最终通知 | 无论程序执行是否正常,该通知都会执行。类似于try…catch中finally代码块 |
package org.spring.ss.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspectJ02 {
@Before("execution(* org.spring.ss.service.impl.*.*(..))")
public void before(){
System.out.println("before ...");
}
@AfterReturning(value = "within(org.spring.ss.service.impl.*)",returning = "res")
public void afterReturning(Object res){
System.out.println("后置通知..." + res);
}
@Around(value = "within(org.spring.ss.service.impl.*)")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("目标对象before....");
Object[] args = proceedingJoinPoint.getArgs();
Object res = proceedingJoinPoint.proceed();
System.out.println("目标对象after...." + res);
return res;
}
@AfterThrowing(value = "within(org.spring.ss.service.impl.*)",throwing = "ex")
public void afterThrowing(Exception ex){
System.out.println("异常通知产生了..." + ex);
}
@After(value = "within(org.spring.ss.service.impl.*)")
public void after(){
System.out.println("最终通知...");
}
}
|