- AOP(Aspect-Oriented programming,面向切面编程)是对传统OOP(Object-Oriented programming)的补充
- AOP的主要编程对象是切面(aspect)
- 切点:切面模板化横切关注点。也就是我们在切面上面有选择的处理部分关注点。如我们一个类里面有15个方法,也主是对应了15个连接点,我们只选择其中的5个连接点来处理通知,事务等,那么选择的这5个连接点组全就是一个切点。
- 在应用AOP的时候,仍然要定义公共功能,但可以明确定义这个功能在什么地方,以什么方式应用,并且不影响通知的类。这样一来,切点就被模块化切面(类)里面了。
- AOP的好处:每个业务逻辑位于一个位置,代码不分散,便于维护和升级;业务模块只关注业务,只处理核心代码。
- AOP的使用:
- 在spring的配置文件中加入aop的命名空间:
- 基于注解的方式来使用 AOP,在配置文件中配置自动扫描的包 :<context:component-scan base-package="com.dadi.aop"></context:component-scan>
- 加入AspectJ注解起作用的配置:<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 编写切面类:一个一般的 Java 类,在其中添加要额外实现的功能.
- 配置切面: ①切面必须是 IOC 中的 bean: 实际添加了 @Component 注解; ②声明是一个切面: 添加 @Aspect; ③声明通知(@Before,@After,@Around,@AfterRunnig,@AfterThrowing): 即额外加入功能对应的方法. ;
- 在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.
- 关于五个是@Before: 前置通知, 在方法执行之前执行; @After: 后置通知, 在方法执行之后执行 ; @AfterRunning: 返回通知, 在方法返回结果之后执行; @AfterThrowing: 异常通知, 在方法抛出异常之后;@Around: 环绕通知, 围绕着方法执行
- 通知中的表达式:execution(访问类型 返回值 包路径.类路径.方法名.(参数类型,参数类型)),例如:①execution(public int com.dadi.aop.CalculationService.div(int, int));②可以用通配符,如返回任意类型,任意类,任意方法,任意参数的切面表达式execution(* com.dadi.aop.*.*(..))
下面是代码的示例
一、配置spring的配置文件
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 自动扫描指定包下面的带注解的类 -->
<context:component-scan base-package="com.dadi.aop"></context:component-scan>
<!-- 配置切面注解起作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
二、编写java类
①定义接口
package com.dadi.aop;
/**
* 计算接口
*/
public interface CalculationService {
/**
* 加
*
* @param i
* @param j
* @return
*/
int add(int i, int j);
/**
* 减
*
* @param i
* @param j
* @return
*/
int sub(int i, int j);
/**
* 乘
*
* @param i
* @param j
* @return
*/
int mul(int i, int j);
/**
* 加
*
* @param i
* @param j
* @return
*/
int div(int i, int j);
}
②接口的实现类,这个类只关心核心业务的代码,不校验数据的合法性和记录日志,数据的合法性和日志的记录交给切面来实现
package com.dadi.aop;
import org.springframework.stereotype.Component;
@Component("calculationService")
public class CalculationServiceImpl implements CalculationService {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) { return i / j; }
}
③定义一个切面类,其中@Order(int j)是指定切面的优先级,值越小优先级越高
package com.dadi.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Objects;
/**
* 验证数据的合法性
*/
// @Order中的值越小,优先级越高
@Order(1)
@Aspect
@Component
public class VlidationAspect {
/**
* 验证数据传入的合法性
*
* @param joinPoint
*/
// 环绕通知
@Around("execution(* com.dadi.aop.CalculationService.div(int, int))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
// 得到代理对象
String s = joinPoint.getTarget().getClass().getName();
// 得到代理的对象的方法
String methodName = joinPoint.getSignature().getName();
// 得到代理的对象的方法的属性值
Object[] args = joinPoint.getArgs();
// 取得第二个参数的值
System.out.println("前置通知:对象的路径:" + s + " 拦截到的方法:" + methodName + ",参数: " + Arrays.asList(args));
String value = String.valueOf(args[1]);
if (Objects.equals("div", joinPoint.getSignature().getName()) && Objects.equals("0", value)) {
System.out.println("被除数验证为0,修改为1");
args[1] = 1;
}
Object result = null;
//执行目标方法
try {
result = joinPoint.proceed(args);
//返回通知
System.out.println("返回通知:对象的路径:" + s + " 拦截到的方法:"+methodName + " ,结果 " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception:" + e);
e.printStackTrace();
}
//后置通知
System.out.println("后置通知:对象的路径:" + s + " 拦截到的方法:" + methodName + ",参数: " + Arrays.asList(args));
return result;
}
}
日志通知切面类
package com.dadi.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// 记录日志通知
@Order(2)
@Aspect
@Component
public class LoggingAspect {
// exceution对应的表达式 类型 返回值 包路径 方法 类型参数
/**
* 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数.
*/
@Before("execution(public int com.dadi.aop.CalculationService.add(int, int))")
public void before(JoinPoint joinPoint) {
// 得到代理对象
String s = joinPoint.getTarget().getClass().getName();
// 得到代理的对象的方法
String methodName = joinPoint.getSignature().getName();
// 得到代理的对象的方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知:对象的路径:" + s + " 拦截到的方法:" + methodName + ",参数: " + Arrays.asList(args));
}
// 任意类型,任意返回值,包下面的任意对象的任意方法都会被后置通知处理
@After("execution(* com.dadi.aop.*.sub(..))")
public void after(JoinPoint joinPoint) {
// 得到代理对象
String s = joinPoint.getTarget().getClass().getName();
// 得到代理的对象的方法
String methodName = joinPoint.getSignature().getName();
// 得到代理的对象的方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("后置通知:对象的路径:" + s + " 拦截到的方法:" + methodName + ",参数: " + Arrays.asList(args));
}
}
④测试用例
@Test
public void add() {
CalculationService cs = (CalculationService) ac.getBean("calculationService");
int result = cs.add(3, 5);
System.out.println(String.format("结果:%s",result));
}
@Test
public void sub() {
}
@Test
public void mul() {
}
@Test
public void div() {
CalculationService cs = (CalculationService) ac.getBean("calculationService");
int result = cs.div(3, 1);
System.out.println(String.format("结果:%s",result));
int result = cs.div(3, 0);
System.out.println(String.format("结果:%s",result));
}
切面的核心思想是动态代理,java的动态代理请参考https://blog.csdn.net/u010285684/article/details/80265745