SpringAOP简介
AOP(Aspect Oriented Program):即面向切面编程
在面向切面编程的思想里面,把功能分为核心业务功能和周边功能
-
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
- 所谓的周边功能,比如性能统计,日志,事务管理等等
在SpringAOP中将这些周边功能称为切面,将核心业务逻辑与这些周边功能分别独立开始,然后将它们交织在一起就是AOP需要做的事情
AOP将业务模块锁公共调用的、非业务逻辑的功能封装起来;这样的好处?
一:所以这些非核心业务功能都集中在一起,而不是分散到多处代码中,便于减少系统的重复代码,降低模块间的耦合度,有利于维护
二:服务模块更简洁,因为它们只包含主要关注点或核心功能的代码,次要的非业务功能(周边功能)被转移到切面中
AOP当中的概念
- 通知(Advice):通知定义了切面是什么以及何时使用
- 连接点(Join point):连接点指的是在应用执行过程中插入切面的一个点。
- 切点(Pointcut):在哪些类或方法中加入周边功能
- 切面(Aspect):切面是通知和切点的结合,简单来说:切面是什么以及在何时和何处完成其功能
- 织入(Weaving):将切面在指定的连接点织入到目标对象中
使用注解来实现SpringAOP
首先我们新建一个SpringAop的包,创建一个HavingDinner类
package SpringAop;
import org.springframework.stereotype.Component;
@Component
public class HavingDinner{
public void eat(){
System.out.println("吃晚饭!");
}
}
然后创建我们的切面类AOP
package SpringAop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AOP {
@Before("execution(* SpringAop.HavingDinner.eat(..))")
public void BuyIngredients(){
System.out.println("购买食材!");
}
@Before("execution(* SpringAop.HavingDinner.eat(..))")
public void Cook(){
System.out.println("将食材进行烹饪!");
}
@After("execution(* SpringAop.HavingDinner.eat(..))")
public void WashDishes(){
System.out.println("吃完后洗碗!");
}
}
然后创建一个配置类进行显示配置一下
package SpringAop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AopConfig {
}
创建我们的测试类
import SpringAop.AopConfig;
import SpringAop.HavingDinner;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAop {
@Test
public void Test(){
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
HavingDinner havingDinner = (HavingDinner)context.getBean("havingDinner");
havingDinner.eat();
}
}
先看一下运行结果
简单解释一下
- @Component注解表明这是一个组件类,并告诉Spring为这个类创建一个bean
- @Aspect注解表明这不仅仅是一个Java类,还是一个切面
- 至于AopConfig的作用以及@Component注解建议看我的这篇文章:Spring IoC和DI详解以及装配Bean
- @EnableAspectJAutoProxy注解:启用自动代理功能
- 如果不使用@EnableAspectJAutoProxy,那么AOP类即使使用@AspectJ注解,Spring也不会将其视为切面,AOP类中的注解也不会被解析,Spring也不会创建将其转换为切面的代理
下面仔细解释一下AOP类中的注解
下面是AspectJ的一些注解,上面只用了@Before和@After
注解 | 通知 |
@After | 通知方法会在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
@Before | 通知方法会在目标方法调用之前执行 |
定义切点
execution(* SpringAop.HavingDinner.eat(..))
- execution:代表执行方法的时候会触发
- *:代表任意返回类型
- SpringAop.HavingDinner:代表类的全限定名
- eat():被拦截的方法名称
不过在上面的表达式中每个方法都拦截eat()方法,表达式因此很长,我们也可以改成下面这样
package SpringAop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AOP {
@Pointcut("execution(* SpringAop.HavingDinner.eat(..))")
public void eat(){}
@Before("eat()")
public void BuyIngredients(){
System.out.println("购买食材!");
}
@Before("eat()")
public void Cook(){
System.out.println("将食材进行烹饪!");
}
@After("eat()")
public void WashDishes(){
System.out.println("吃完后洗碗!");
}
}
上面我们使用@Pointcut注解,在切面中定义了一个可重用的切点
下面一起来看看功能最强大的环绕通知,它集成了前置通知和后置通知
package SpringAop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AOP {
@Pointcut("execution(* SpringAop.HavingDinner.eat(..))")
public void eat(){}
@Around("eat()")
public void havingEat(ProceedingJoinPoint joinPoint){
System.out.println("购买食材!");
System.out.println("将食材进行烹饪!");
try{
joinPoint.proceed();
} catch (Throwable e ){
e.printStackTrace();
}
System.out.println("吃完后洗碗!");
}
/*@Before("eat()")
public void BuyIngredients(){
System.out.println("购买食材!");
}
@Before("eat()")
public void Cook(){
System.out.println("将食材进行烹饪!");
}
@After("eat()")
public void WashDishes(){
System.out.println("吃完后洗碗!");
}
@AfterThrowing("eat()")
public void exe(){
System.out.println("方法调用出现异常");
}*/
}
运行代码
我们可以在havingEat方法中做任何事,当我们需要将控制权交给被通知方法时,调用proceed()方法即可,所以这个新的通知方法必须要接受一个ProceedingJoinPoint对象
通过XML配置开发SpringAOP
AOP 配置元素 | 用途 |
<aop:advisor> | 定义AOP通知器 |
<aop:after> | 定义AOP后置通知(不管被通知的方法是否执行成功) |
<aop:after-returning> | 定义AOP返回通知 |
<aop:after-throwing> | 定义AOP异常通知 |
<aop:around> | 定义AOP环绕通知 |
<aop:aspect> | 定义一个切面 |
<aop:aspectJ-autoproxy> | 启用@AspectJ注解驱动的切面 |
<aop:before> | 定义一个AOP前置通知 |
<aop:config> | 顶层的AOP配置元素 |
<aop:declare-parents> | 以透明的方式为被通知的对象引入额外的接口 |
<aop:pointcut> | 定义切点 |
下面将上述例子改成XML配置的形式
首先是HavingDinner类
package SpringAop;
public class HavingDinner{
public void eat(){
System.out.println("吃晚饭!");
}
}
AOP类
package SpringAop;
import org.aspectj.lang.ProceedingJoinPoint;
public class AOP {
public void havingEat(ProceedingJoinPoint joinPoint){
System.out.println("购买食材!");
System.out.println("将食材进行烹饪!");
try{
joinPoint.proceed();
} catch (Throwable e ){
e.printStackTrace();
}
System.out.println("吃完后洗碗!");
}
public void BuyIngredients(){
System.out.println("购买食材!");
}
public void Cook(){
System.out.println("将食材进行烹饪!");
}
public void WashDishes(){
System.out.println("吃完后洗碗!");
}
public void exce(){
System.out.println("方法调用出现异常");
}
}
spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="havingDinner" class="SpringAop.HavingDinner"/>
<bean id="aop" class="SpringAop.AOP" />
<!--启用AspectJ自动代理 相当于注解@EnableAspectJAutoProxy-->
<aop:aspectj-autoproxy />
<aop:config>
<!--where 在哪些地方做增加 -->
<aop:pointcut id="eat" expression="execution(* SpringAop.HavingDinner.eat())"/>
<!-- what 做什么增加-->
<aop:aspect id="eatAspect" ref="aop">
<!-- when:在什么时机(方法前/后/前后) -->
<aop:around pointcut-ref="eat" method="havingEat"/>
<aop:before method="BuyIngredients" pointcut-ref="eat"/>
<aop:before method="Cook" pointcut-ref="eat"/>
<aop:after method="WashDishes" pointcut-ref="eat"/>
<aop:after-throwing method="exce" pointcut-ref="eat" />
</aop:aspect>
</aop:config>
</beans>
运行结果
参考资料
- 《Java实战(第四版)》
- 谷歌&&百度