面向切面编程(AOP)
简介
在软件开发中散布于应用中多处的功能被称为横切关注点(crossing-cutting concern)。通常这也横切关注点一般是与业务逻辑相分离的。而面向切面编程将会解决如何将横切关注点与与业务逻辑分离的问题。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRIEcgxi-1571404087194)(en-resource://database/9500:1)] 横切关注点往往是影响应用多处的功能
通知(Advice)
通知定义了切面是什么,以及何时使用 Spring 的切面通知 Spring 的切面可以应用 5 种通知类型:
- 前置通知:在目标方法被调用之前调用通知功能
- 后置通知:在目标方法完成之后调用通知,此时不会关心对方输出是什么
- 返回通知:在目标方法成功执行之后调用通知
- 异常通知:在目标方法抛出异常之后调用通知
- 环绕通知:通知包裹了被通知的方法,在被通知的方法被调用之前和之后执行自定义的行为。
连接点(Join Point)
在程序运行过程中,有数以千记的地方可以产生通知的点,这个点可以是一个函数的调用,或是异常的抛出,又或者是某个字段值的改变。可以产生这些通知的事件被称为连接点
切点(Poincut)
切点会匹配和通知所要织入的一个或多个连接点
切面(Aspect)
切面是通知和切点的结合。
引入(Introduction)
引入允许我们向现有的类添加新的方法和属性。
织入(Weaving)
织入就是把切面应用到目标对象并创建代理对象的过程。
实际应用
在实际开发过程中有两种方式实现 AOP
- 基于 AspectJ 注解的方式
- 基于 xml 的方式实现。
开发前准备
开发时实际需要的包:
- commons-logging-1.1.1.jar
- spring-aop-5.1.5.RELEASE.jar
- spring-aspects-5.1.5.RELEASE.jar
- spring-beans-5.1.5.RELEASE.jar
- spring-context-5.1.5.RELEASE.jar
- spring-core-5.1.5.RELEASE.jar
- spring-expression-5.1.5.RELEASE.jar
- aopalliance-1.0.jar
- aspectjweaver-1.9.2.jar
基于 AspectJ注解的方式实现 AOP
Bean 类(接口)
首先定义一个接口,用以被继承。该类以商店为模型接口,包含三个购买动作和一个找零动作。
代码语言:javascript复制package cn.edu.stu.Demo1.Bean;
import org.springframework.stereotype.Component;
@Component
public interface ShopInterface {
public void buyPen(String name);
public void buyBook(String name);
public void buyBike(String name);
public double getCharge(double pay,double price, int num);
}
代码语言:javascript复制Bean 类的接口实现
package cn.edu.stu.Demo1.Bean;
import org.springframework.stereotype.Component;
@Component
public class ShopImpl implements ShopInterface{
@Override
public void buyPen(String name) {
System.out.println(name " buy a Pen");
}
@Override
public void buyBook(String name) {
System.out.println(name " buy a Book");
}
@Override
public void buyBike(String name) {
System.out.println(name " buy a Bike");
}
@Override
public double getCharge(double pay, double price, int num) {
return pay-(price*num);
}
}
切面类
首先使用 @Componment 注解将切面标注为一个 Bean 组件。
然后在使用 @Aspect 属性将该类标注为切面类。然后在切面类中写切面的方法。
当然在实际开发过程中,不同切面类的优先级别也是不同的,此时可以通过 @Order 注解来为切面类设置优先级
使用 @Before 注解将该方法标注为一个前置通知的方法。在 @Before 后面加上参数 execution(返回类型 包名.类名.方法名(参数类型))
用以指定需要将该切面函数应用到那些类上。方法名 * 代替,可以指代该包下的所有的类。参数类型用 ..
来表示任意的参数列表皆可。对于经常使用的函数可以使用 @Pointcut 注解为其起一个简化的函数名字。使用 注解的函数不需要往函数中添加任何的代码块。
使用 @After 注解可以将方法声明为后置通知的方法。
使用 @AfterReturning 注解可以将函数声明返回通知类型的方法。returing 属性可以标注返回的值,returning 的值要与参数列表中获取的对象的参数名保持一致。
使用 @AfterThrowing 注解可以将函数声明为异常通知类型的方法。throwing 属性可以标注返回异常的值,throwing 的值要与参数列表中的名称保持一致。
使用 @Around 注解将方法标注为环绕通知的方法。
package cn.edu.stu.Demo1.Bean;
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order
@Aspect
@Component
public class ShopAspect {
@Pointcut("execution(public void cn.edu.stu.Demo1.Bean.ShopInterface.*(String))")
public void aspectFunction() {}
@Before("aspectFunction()")
public void beforeShopping(JoinPoint joinPoint) {
//获取方法名称
String method = joinPoint.getSignature().getName();
//获取方法参数
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The methood (" method ") Begins with args:" args);
}
@After("aspectFunction()")
public void afterShopping(JoinPoint joinPoint) {
String method = joinPoint.getSignature().getName();
System.out.println("The methood (" method " ) Ends");
}
@AfterReturning(value="execution(public double cn.edu.stu.Demo1.Bean.ShopInterface.getCharge(double,double,int))",
returning="result")
public void afterChargeReturning(JoinPoint joinPoint,Object result) {
String method = joinPoint.getSignature().getName();
System.out.println("The methood (" method " ) returning" result);
}
@AfterThrowing(value="execution(public double cn.edu.stu.Demo1.Bean.ShopInterface.getCharge(double,double,int))",
throwing="ex")
public void afterException(JoinPoint joinPoint,Exception ex) {
String method = joinPoint.getSignature().getName();
System.out.println("The methood (" method " ) occurs: " ex);
}
@Around(value="execution(public double cn.edu.stu.Demo1.Bean.ShopInterface.getCharge(double,double,int))")
public void aroundShopping(ProceedingJoinPoint pjp) {
Object result = null;
String method = pjp.getSignature().getName();
try {
// 此处可添加前置通知
result = pjp.proceed();
// 此处可添加返回通知
}catch(Throwable ex) {
// 此处可添加异常通知
}
// 此处可添加后置通知
}
}
代码语言:javascript复制xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<context:component-scan base-package="cn.edu.stu.Demo1.Bean"></context:component-scan>
<!-- 使得 AspectJ 注解起作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试
由于 AOP 注解是作用在接口上的,所以测试时需要通过接口来获取 Bean 的实例,这样的话 AOP 通知才有效。
代码语言:javascript复制 @Test
public void Test1() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("Bean1Context.xml");
ShopInterface shop = ctx.getBean(ShopInterface.class);
shop.buyBike("小明");
shop.buyBook("小红");
shop.getCharge(200, 43.5, 3);
}
代码语言:javascript复制运行结果
The methood (buyBike) Begins with args:[小明]
小明 buy a Bike
The methood (buyBike ) Ends
The methood (buyBook) Begins with args:[小红]
小红 buy a Book
The methood (buyBook ) Ends
get charge $ 69.5
The methood (getCharge ) returningnull
基于 xml 配置文件实现 AOP
xml 配置文件
在使用 xml 配置文件实现 AOP 时,其中所有有关于 AOP 的配置均放在 aop:config标签中。 aop:pointcut 标签可以为切点起别名。 有关于切面的配置放在 aop:aspect 标签中,ref 属性用以加载切面类的 Bean,order 属性用以设置切面的优先级。 其余有关于方法的配置均放在 aop:before、aop:after、aop:after-returning、aop:after-throwing 标签中。
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<bean id="shopAspect" class="cn.edu.stu.Demo2.Bean.ShopAspect"></bean>
<bean id="shop" class="cn.edu.stu.Demo2.Bean.ShopImpl"></bean>
<aop:config>
<aop:pointcut expression="execution(public void cn.edu.stu.Demo2.Bean.ShopInterface.*(String))" id="shopping"/>
<aop:aspect ref="shopAspect" order="1">
<aop:before method="beforeShopping" pointcut-ref="shopping" />
<aop:after method="afterShopping" pointcut-ref="shopping"/>
<aop:after-returning method="afterChargeReturning" pointcut="execution(public double cn.edu.stu.Demo1.Bean.ShopInterface.getCharge(double,double,int))" returning="result"/>
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
代码语言:javascript复制测试
@Test
public void Test1() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("Bean2Context.xml");
ShopInterface shop = ctx.getBean(ShopInterface.class);
shop.buyBike("小明");
shop.buyBook("小红");
shop.getCharge(200, 43.5, 3);
}
代码语言:javascript复制结果
The methood (buyBike) Begins with args:[小明]
小明 buy a Bike
The methood (buyBike ) Ends
The methood (buyBook) Begins with args:[小红]
小红 buy a Book
The methood (buyBook ) Ends
get charge $ 69.5