一、本文概览
二、AOP简介
1、概念
AOP(Aspect Oriented Program),即面向切面编程,它提供另一种考虑程序结构的方法,补充了面向对象编程(OOP)。面向对象编程中模块化的关键单元是类,而在AOP中模块化的单元是切面。切面支持跨多个类型和对象的关注点(如事务管理)的模块化。
2、引入原因
OOP的特点
- 封装
- 继承
- 多态
OOP的局限性
- Java是一种静态语言:类结构一旦定义,不容易被修改
- 因为JVM定义字节码读取的标准只是字节流,不关心来源是哪里,可以在运行时动态生成字节码给到虚拟机加载起到运行时更改类结构的形式
- 但是上述内容对于普通开发者来说,这种会比较难,提供出来一个API给到开发者会更方便一些
- 侵入性扩展:为了在某一刻前后执行特定的行为,比如关键业务执行前后的耗时记录等,可以通过继承和组合组织新的类结构,但这种就导致了项目代码被侵入了。
所以AOP便被引入了。
三、AOP使用场景
1、日志场景
- 诊断上下文,如:log4j或logback中的 _x0008_MDC
- 辅助信息,如:方法的执行时间
2、统计场景
- 方法的调用次数
- 执行异常次数
- 数据抽样
- 数值累加
3、安防场景
- 熔断,如:Netflix Hystrix
- 限流和降级,如:Alibaba Sentinel
- 认证和授权,如:Spring Security
- 监控,如JMX
4、性能场景
- 缓存,如Spring Cache
- 超时控制
四、Demo演示
1、maven依赖
<dependencies>
<!-- 支持aop相关注解等 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<!-- 支持切入点表达式等 -->
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!-- aop核心功能,例如代理工厂等 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
</dependencies>
2、xml配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id="testBeanAOP" class="com.markus.aop.TestBeanAOP"/>
<bean class="com.markus.aop.TestBeanAOPAspect"/>
</beans>
3、代码示例
package com.markus.aop;
/**
* @author: markus
* @date: 2022/10/9 4:09 PM
* @Description: AOP示例
* @Blog: http://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class TestBeanAOP {
public void test(){
System.out.println("AOP Hello World!");
}
}
package com.markus.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
/**
* @author: markus
* @date: 2022/10/9 4:14 PM
* @Description: 切面
* @Blog: http://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@Aspect
public class TestBeanAOPAspect {
@Pointcut("execution(* *.test(..))")
public void test() {
}
/**
* 切入点执行之前执行
*/
@Before("test()")
public void before() {
System.out.println("before invoke test method!");
}
/**
* 切入点执行之后执行
*/
@After("test()")
public void after() {
System.out.println("after invoke test method!");
}
/**
* 切入点环绕执行
* 1. 切入点执行前
* 2. 切入点执行后
* 3. 切入点异常后
*
* @param point
* @return
*/
@Around("test()")
public Object around(ProceedingJoinPoint point) {
System.out.println("before");
Object o = null;
try {
o = point.proceed();
} catch (Throwable e) {
System.out.println("exception");
// e.printStackTrace();
}
System.out.println("after");
return o;
}
}
package com.markus.aop;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author: markus
* @date: 2022/10/9 4:30 PM
* @Description: 容器执行
* @Blog: http://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class TestClient {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/aop-application-context.xml");
TestBeanAOP bean = context.getBean("testBeanAOP", TestBeanAOP.class);
bean.test();
context.close();
}
}
- 执行结果
五、AOP常见词以及常见注解
1、常见词
- Aspect
- 它是用于横切关注点的模块化单位,表现出来的形式类似于Java类,不过内容会包括切入点、通知和类型间声明
- Join point
- 程序执行期间的一个点,如方法的执行或异常的处理。在Spring AOP中,连接点总是代表一个方法的执行
- Pointcut
- 匹配连接点的条件
- Advice
- 切面在特定连接点上采取的动作。不同类型的通知包括"around"、"before"和"after"等。许多AOP框架(包括Spring)将通知建模为拦截器,并维护连接点周围的拦截器链
- Introduction
- 代表类型声明额外的方法或字段。Spring AOP允许你向任何被通知的对象引入新的接口(和相应的实现)。
2、常用注解
2、常用注解
1、@Aspect
- 使用:标注在一个类上
- 作用:表明该类是一个切面
2、@Before-前置通知
- 使用:标注在一个方法上
- 属性:
- value:用于指定切入点表达式,还可以指定切入点表达式的引用
- 作用:前置通知,在关注点执行前执行
3、@AfterReturning-后置通知
- 使用:标注在一个方法上
- 属性:
- value:用于指定切入点表达式,还可以指定切入点表达式的引用
- 作用:后置通知,在关注点正常执行后执行
4、@AfterThrowing-异常通知
- 使用:标注在一个方法上
- 属性:
- value:用于指定切入点表达式,还可以指定切入点表达式的引用
- 作用:异常通知,在关注点执行时出现异常后执行
5、@After-最终通知
- 使用:标注在一个方法上
- 属性:
- value:用于指定切入点表达式,还可以指定切入点表达式的引用
- 作用:最终通知,在关注点执行之后执行
6、@Around-环绕通知
- 使用:标注在一个方法上
- 属性:
- value:用于指定切入点表达式,还可以指定切入点表达式的引用
- 作用:环绕通知,包括前置、后置等通知
7、@Pointcut
- 使用:标注在一个方法上
- 属性:
- value:用于指定切入点表达式的内容
- 作用:指定切入点表达式
3、切入点表达式
@Pointcut(value = “execution(* *.test(…))”)
上面叙述的常见注解都需要用到了一个内容,即切入点表达式,那表达式的写法是如何的呢,下面就来简单记录下:
- 表达式:
- 访问修饰符 返回值 包名.包名…类名.方法名(参数列表)
- 其中
访问修饰符
可省略 - 其中
返回值
可使用通配符* - 其中
包名
可使用通配符*,表示任意包- 有几级包就写几个*
- *…表示当前包及其子包
- 其中
类名和方法名
都可以使用通配符* - 其中
参数
比较特殊- 当使用通配符*表示时,则代表一定有参数
- 使用 … 表示时,则有无参数均可
- 标准写法
- public void com.markus.aop.TestBeanAOP.test()
- 全通配写法:
- *…*.*(…)
4、注解使用
示例
- 关注点
package com.markus.aop;
/**
* @author: markus
* @date: 2022/10/9 4:09 PM
* @Description: AOP示例
* @Blog: http://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class TestBeanAOP {
public void test() {
System.out.println("【target method】AOP Hello World!");
int i = 1 / 0;
}
}
- 切面
package com.markus.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
/**
* @author: markus
* @date: 2022/10/9 4:14 PM
* @Description: 切面
* @Blog: http://markuszhang.com
* It's my honor to share what I've learned with you!
*/
@Aspect
public class TestBeanAOPAspect {
@Pointcut(value = "execution(* *.test(..))")
public void test() {
}
/**
* 切入点执行之前执行
*/
@Before(value = "test()")
public void before() {
System.out.println("@Before");
}
/**
* 切入点执行之后执行
*/
@After(value = "test()")
public void after() {
System.out.println("@After");
}
@AfterReturning(value = "test()")
public void afterReturning() {
System.out.println("@AfterReturning");
}
@AfterThrowing(value = "test()")
public void afterThrowing(){
System.out.println("@AfterThrowing");
}
/**
* 切入点环绕执行
* 1. 切入点执行前
* 2. 切入点执行后
* 3. 切入点异常后
*
* @param point
* @return
*/
@Around(value = "test()")
public Object around(ProceedingJoinPoint point) {
System.out.println("【Around】before");
Object o = null;
try {
o = point.proceed();
System.out.println("【Around】after");
} catch (Throwable e) {
// 因为这里有异常捕捉,所以@AfterThrowing注解不生效
System.out.println("【Around】exception");
// e.printStackTrace();
} finally {
System.out.println("【Around】finally");
}
return o;
}
}
- 客户端
package com.markus.aop;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author: markus
* @date: 2022/10/9 4:30 PM
* @Description: 容器执行
* @Blog: http://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class TestClient {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/aop-application-context.xml");
TestBeanAOP bean = context.getBean("testBeanAOP", TestBeanAOP.class);
bean.test();
context.close();
}
}
- 程序结果