前言
Spring AOP被称为面向切面编程。
它可以在某一个或者一类方法调用的前后添加其他的处理方法与逻辑,对参数或者流程进行修改,提供对同一类型处理进行批量改动的能力。
同时,也可以实现修改第三方class文件已编写好的类、jar包等无法直接编辑源码但又想做改动的功能。
一. 配置
首先,需要再Spring maven项目中添加aspect的引用jar包
<!-- aop切面编程包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
在Spring.xml配置文件中,添加下面的配置
<beans
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- 开启aop注解 @Aspect -->
<aop:aspectj-autoproxy/>
</beans>
之后就可以使用aspect编程了
二. 编程
使用@Aspect注解,编写一个切面bean
//使用@Aspect注解使此类成为一个aspect类
@Aspect
@Component //本身aspect类也得是个bean在Spring注册启动,否则不会生效
public class ServiceAspect {
//定义一个切点,下面可以直接用这个切点作为连接点,在它前后进行切面编程操作
@Pointcut("execution(** serviceMethod1(..)) ")
public void method1(){}
@Pointcut("execution(** serviceMethod2(String)) ")
public void method2(){}
@Before("method1()") //使用切点
public void before(){
System.out.println("AOP serviceMethod1 BEFORE reach");
}
@Before("method2() && args(param)") //调用有参数方法
public void before2(String param){
System.out.println("AOP BEFORE2 reach");
System.out.println("AOP param=="+param);
}
@Around("method2()")
public String roundAspect(ProceedingJoinPoint jp){
String rString = "";
try{
System.out.println("round before Join Point");
rString = (String) jp.proceed(); //调用方法,需要将方法的调用值返回,否则controller调用service得到的结果为null
System.out.println("round after Join Point");
} catch(Throwable exception) {
System.err.println("ASPECT error");
rString = "error";
}
return rString;
}
//类方法拓展,将MyService接口实现类扩展为实现了ExtendInterface接口的ExtendClass类的实例,使用时强转成ExtendClass类型就可以
@DeclareParents(value = "com.service.MyService+",defaultImpl=ExtendClass.class)
public ExtendInterface extendInterface;
}
连接点是serviceMethod1()和serviceMethod2(String)两个方法,定义在接口MyService中
public interface MyService {
public String serviceMethod1();
public String serviceMethod2(String param);
}
实现类
@Service()
public class MyServiceImpl implements MyService {
@Override
public String serviceMethod1() {
return "method1";
}
@Override
public String serviceMethod2(String param) {
return "method2, param=="+param;
}
}
则在切入点的方法被调用前后,Aspect切面里面的相关的日志就会打印。
三. 切入类型
1. @Before
- 在切点方法调用前执行
- value值:切入点规则表达式
- @Before执行完后切点方法会自动执行,除非@Before抛异常
- @Before方法无法访问目标返回值
2. @AfterRunning
- 在目标方法正常完成后执行
- value/pointcut:切入点规则表达式
- returning:该属性指定一个形参名,方法可以定义一个同名的形参,来接收目标方法的返回值
- returning可以限制返回值类型(相同类型或void没有返回值),辅助value属性进行方法筛选
- @AfterRunning无法修改最终的返回值
3. @AfterThrowing
- 处理程序中抛出的异常
- value/pointcut:切入点规则表达式
- throwing:指定一个形参名,方法可定义同名形参,用于接收抛出的异常Exception
- @AfterThrowing无法完全处理异常。即使进行处理,切入点方法的原先调用上层仍然会得到抛出的异常
4. @After
- 与@AfterRunning相似
- 与@AfterRunning不同的是,不管切入方法是否正常完成,都会进入@After方法
- @After方法最好分为两部分,正常处理和异常处理,并且在异常处理内释放资源
5. @Around
- 近似于@Before处理和@AfterRunning处理总和
- 可以决定在哪里进行前后增强操作,甚至组织目标方法执行(不调用)
- 可以改变入参和返回值
- 需要在线程安全的情况下使用
- 第一个参数必须是ProceedingJoinPoint类型,并且必须调用proceed()方法才能执行切入点的方法,否则切入点方法不执行
- 切入点方法的原先调用上层得到的返回值是Around的返回值。因此必须将proceed()方法结果返回,原先调用上层才能获取到切入点的返回值
得到入参:
Object[] args = jp.getArgs() ;
四. 切入点表达式
表达式知识可以参考文章《spring AspectJ的Execution表达式-备忘笔记》
总结下最常用的execution()切点函数
execution() 用于匹配执行方法的连接点,语法规则如下
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
第一个是修饰符(modifiers-pattern) :public、protected、private
第二个是返回值(ret-type-pattern) : 不可为空,最少为*
第三个方法的包名(declaring-type-pattern)
第四个是方法名(name-pattern) :不可为空,最少为*
第五个方法参数(param-pattern) : 不可为空,任意类型用".."表示
第六个抛出的异常类型(throws-pattern)
因此,最简单的表达式:execution("* *(..)") 表示任意方法