1,AOP的概念:
A,* AOP的开发思想:
- 在执行代码的时候将共性功能与非共性的功能织入到一起,生成代理对象;
- **简单来说,就是将复用性高的功能提取出来,做成切面类在程序执行的时候动态地添加到业务程序中去,使得这个切面类可以在不同的地方复用;**
- 提取到的地方,写好实现该功能的代码,我们称其为切面代码,在以后需要调用该功能的时候,只需要利用代理模式调用就好了;
B,AOP的主要目的及应用场景:
- AOP真正目的是:你写代码的时候,只需考虑主流程,而不用考虑那些不重要的,但又必须要写的其它相同的代码,这些其它的相同代码所在的类就是切面类。
- 主要应用场景:
- 事务处理;
- 日志记录;
- 用户权限(登录);
- 等等……
2,* AOP的结构术语:
结构术语: | 作用: |
---|---|
JoinPoint(连接点) | 在程序执行过程中的某个阶段点,就是指主业务方法的调用,它是客观存在的。指的是接口或实现类中所有的方法; |
Pointcut(切入点) | 满足某一规则的类或方法都是切入点,通过切入点表达式来制定规则。是连接点中的部分方法。 |
Advice(通知) | 切入点处所要执行的程序代码,即要执行的公共方法。通知的类型有: 前置通知、后置通知、异常通知、最终通知、环绕通知。 |
Aspect(切面) | 切面指的是切入点(规则)和通知(织入方法)的类。切面类 = 切入点规则+通知方法; |
Target(目标对象) | 被代理的对象,就是主业务类;如:被经纪人代理的明星; |
Weaving(织入) | 织入指的是:把新增的功能用于目标对象,创建代理对象的过程; |
Proxy(代理) | 一个类被AOP织入增强产生的结果类,就是代理类;如:为明星代理的经纪人; |
- **案例图:**
3,* AOP的创建步骤:
A,导入所需依赖:(pom.xml)
<?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>top.jztice5</groupId>
<artifactId>SSM-day03</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.version>5.3.16</spring.version>
</properties>
<dependencies>
<!-- 导入spring框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 导入AOP,aspect框架 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!-- spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- spring-jdbc事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</project>
B,定义业务层(service)接口与实现类:
/**
* 业务层实现类
*/
@Service("bookService")
public class BookServiceImpl implements BookService {
@Override
public void UpdateBook () {
System.out.println("这是一个连接点");
}
}
/**
* 业务层接口
*/
public interface BookService {
void UpdateBook();
}
C,定义通知类,创建通知(Aspect):
public class MyAspect {
//定义切入点
@Pointcut("execution(public void top.jztice5.service.BookService.UpdateBook())")
public void pt(){}
//创建通知并与切入点进行绑定
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
D,定义通知类受Spring容器管理,并定义为切面类:
/**
* 切面类
*/
//放到Spring容器中
@Component
//表明该类是一个切面类
@Aspect
public class MyAspect {
//定义切入点
@Pointcut("execution(public void top.jztice5.service.BookService.UpdateBook())")
public void pt(){}
//创建通知并与切入点进行绑定
@Before("pt()")
public void before(){
System.out.println(System.currentTimeMillis());
}
}
注意:没有使用@Aspect注解的情况下,该类依然是切面类,且未被定义为切面类之前,无法实现AOP操作;
E,在核心配置类中开启AOP注解驱动支持:
- **@EnableAspectJAutoProxy :启用切面类自动代理模式**
@Configuration
//设置要扫描的包路径
@ComponentScan("top.jztice5")
@Import({JdbcConfig.class,MyBatisConfig.class})
//使用AOP就一定要加上这个:切面自动代理
@EnableAspectJAutoProxy
public class SpringConfig {
}
F,启用AOP:
这里使用测试类进行举例;使用整合的Junit4
//Junit提供的方法
@RunWith (SpringJUnit4ClassRunner.class)
@ContextConfiguration (classes = SpringConfig.class)
public class AopTest01 {
@Autowired
private BookService bookService;
@Test
public void setTextService(){
bookService.UpdateBook();
}
}
4,AOP的切入点表达式:
execution:后面写切入点函数;
- **AOP使用通配符描述的切入点表达式:**
A,例:
5,* AOP的通知类型:
A,5种通知类型:
通知类型 | 描述 |
---|---|
1,前置通知 | 原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行 |
2,后置通知 | 原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行 |
3,最终通知 | 原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知 |
4,异常通知 | 原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行 |
5,环绕通知 | 在原始方法执行前后均有对应执行,还可以阻止原始方法的执行 |
B,* 通知类型的注解 (环绕通知:重点):
所有通知注解都放在方法的上方;
- **前置通知:@Before**
- **后置通知:@AfterReturning**
- *** 环绕通知(重点,常用):@Around**
- **@Around 注意事项**:
- 1,环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 2,通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
- 3,对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
- 4,原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
- 5,由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
- **最终通知:@After**
- **异常通知:@AfterThrowing**
C,* 环绕通知的使用:
@Component
@Aspect
public class UrlAspect {
@Around("execution(Boolean top.jztice5.service.URLService.setUrl(String,String))")
//ProceedingJoinPoint:进行连接点接口 (环绕通知与该接口的配合使用)
public Object setTextUrl(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//将传来的password字符串封装到目标数组
Object[] args = proceedingJoinPoint.getArgs();
//遍历数组
for (int i = 0 ; i < args.length ; i++) {
//Object类型的元素转为字符串并将前后的空格去除
args[i]=args[i].toString().trim();
}
//返回处理后的结果
return proceedingJoinPoint.proceed(args);
}
}
6,AOP通知获取数据:
A,AOP通知获取目标方法的数据:
B,AOP通知获取参数数据
C,AOP通知获取返回值数据:
D,AOP通知获取异常数据(了解):
aop主要使用在业务层;
定义切入点的空方法唯一的作用就是定义切入点表达式;
绑定切入点与通知的关系
@Aspect:定义为切面类
事务:
@Transactional
propagation :设置事务传播行为