面向切面编程,又叫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>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<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
// 启动 aop , 默认的代理模式为 JDK 代理,只支持有接口继承的代理
// @EnableAspectJAutoProxy
// 修改代理模式为 cglib 代理
@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 * *(..))
// 指定切入点为:任何一个以“set”开始的方法。
execution(* set*(..))
// 指定切入点为:定义在service包里的任意类的任意方法。
execution(* com.xyz.service.*.*(..))
// 指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时,
// 后面必须跟“*”,表示包、子包下的所有类。
execution(* com.xyz.service..*.*(..))
// 指定只有一级包下的serivce子包下所有类(接口)中的所有方法为切入点
execution(* *.service.*.*(..))
// 指定所有包下的serivce子包下所有类(接口)中的所有方法为切入点
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 // 将该对象加载到IoC容器中
public class MyAspectJ01 {
// 声明切入点,可以复用
@Pointcut("execution(* org.spring.ss.service.impl.*.fun2(..))")
public void pointcut1() {
}
/**
* 增强目标对象的方法
* before 标识是前置通知
*
* @Before 的参数是一个切入点表达式
* execution 定位到具体的方法上
* execution([访问权限类型] 返回值类型 [全限定类名] 方法名(参数名) [抛出的异常类型])
*/
//@Before("execution(* *.*(..))")
//@Before("execution(* r*(..))")
//@Before("execution(* org.spring.ss.service.*.*(..))")
//@Before("execution(* org..service..UserServiceImpl.*(..))")
//@Before("execution(* org..service.*.*(String))")
@Before("pointcut1()")
public void aspectMethod01() {
System.out.println("before ....");
}
/**
* within 指定要切入的类型
* 会切入指定类型的所有方法
*
* within 不支持继承关系,需要使用 + 符号来启动支持继承关系
* this和target 支持继承关系
*/
@Before("within(org.spring.ss.service.impl.UserServiceImpl)")
public void aspectMethod02() {
System.out.println("before ....222");
}
/**
* this 通过判断代理类是否匹配指定类来决定是否和切点匹配
* this JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类
* cglib代理时效果和 target 相同
*/
@Before("this(org.spring.ss.service.impl.UserServiceImpl)")
public void aspectMethod03() {
System.out.println("before ....333");
}
/**
* target 匹配目标对象的类型,即被代理对象的类型(包括接口和子类),用于指明拦截哪个
* 类型的方法
*
*/
@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");
}
/**
* 调用某个方法的入参被Annotation1注解所修饰
*/
@Before("@args(org.spring.ss.annotation.Annotation1)")
public void aspectMethod06() {
System.out.println("before ....666");
}
/**
* 拦截被Annotation3所修饰的类型
*/
@Before("@within(org.spring.ss.annotation.Annotation3)")
public void aspectMethod07() {
System.out.println("before ....7777");
}
/**
* 拦截被Annotation3所修饰的方法
*/
@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 ...");
}
/**
* 后置通知 获取返回结果
* @param res
*/
@AfterReturning(value = "within(org.spring.ss.service.impl.*)",returning = "res")
public void afterReturning(Object res){
System.out.println("后置通知..." + res);
}
/**
* 环绕通知
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@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;
}
/**
* 异常通知
* @param ex
*/
@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("最终通知...");
}
}