文章目录
1、AOP相关概念的介绍
Spring框架的关键组件之一是AOP框架,虽然Spring IoC容器不依赖于AOP,但在Spring应用中,经常会使用AOP来简化编程。
优势:
- 提供声明式企业服务,可以进行声明式事务管理
- 允许实现自定义的切面,某些不适用OOP编程的场景中,采用AOP来补充。
- 可以对业务逻辑的各个部分进行隔离,从而降低业务逻辑之间的耦合度,提高程序的可用性,同时提高了开发的小路。
Spring要使用AOP模块,需要导入spirng-aop模块对应的jar。
AOP核心概念:
AOP的概念使用大多数AOP框架,如AspectJ
、Sprng AOP
- Aspect(切面):将关注点模块化,可以横跨多个对象,例如事务管理。在SpringAOP中,切面可以使用常规类(基于模式的方法)或者使用@Apsect注解的常规类来实现。
- Join Point(连接点):在程序执行中抽取出来的特定点,例如方法调用时或处理处理时。Spring AOP中,连接点总是代表一个方法的执行。
- Advice(通知):在切面的某个特定的连接点上执行的动作,通知的类型有before、after等通知
- Pointcut(切入点):匹配连接点的断言,通知和一个切入点表达式关联。并在满足这个连接的上运行。(切入点表达式和连接点的匹配时AOP的核心)。
- Introduction(引入):声明额外的方法或者某个类型库的字段,SpringAOP的使用从允许一个新的接口(加上对应的实现类,使用的是JDK动态代理)到任何被通知的对象(不需要实现接口,普通的Java类,使用的是CGLIB代理)。
- Target Object(目标对象):被一个及以上的切面所通知的对象。Spring主要时通过运行代理实现的,所以目标对象来永远是一个Proxied(被代理)对象
- AOP Proxy(AOP代理):AOP框架创建的对象,用来实现Apsect Contract(切面契约)包含通知方法执行功能等。
- Weaving(织入):把切面连接到对象上,并创建一个Advised对象(代理对象)的过程。可以在编译时(AspectJ编译器)、类加载时和运行时完成(Spring AOP)
支持的通知:
- Before(前置通知):在连接点之前执行的通知(这个通知不能阻止连接前的执行,除非出异常)
- AferReturning(返回通知):在方法(连接点)正常执行后,没有异常的时候,就会执行到该方法
- AfterThrowing(抛出异常通知):在方法抛出异常推出的时候执行的通知
- After(最后通知):当方法退出时执行的语句,无论是否出现异常,都会执行
- Around(环绕通知):包围一个连接点的通知,是前面四个通知的总和。
Spriing目前只支持方法调用作为连接点使用,默认使用的JDK动态代理
,如果代理类没有实现接口
,再使用CGLIB代理
。
2、使用注解配置Spring AOP
Java配置类:
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration // 表明该类是配置类
@EnableAspectJAutoProxy // 启用Spring AOP
@ComponentScan("com.example") // 指定扫描的包
public class SpringAopConfig {
}
2.1、目标对象没有是接口
目标类:
package com.example.aop;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Component
public class GeneralPerson {
public void say(){
System.out.println("说活");
}
public void goHome(String tool){
System.out.println("使用交通工具"+tool+"回家");
}
public double pay(double money){
System.out.println("已经支付了"+String.format("%.2f",money)+"元");
BigDecimal bg = new BigDecimal(money);
/**
*
* 方法:setScale
* 参数:
* newScale - 要返回的 BigDecimal 值的标度。
* roundingMode - 要应用的舍入模式。
* 返回:
* 一个 BigDecimal,其标度为指定值,其非标度值可以通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定。
*/
double result = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
System.out.println(result);
return result;
}
/**
* 如果number为零,出现异常,被除数不能为0
* */
public int divide(int number){
return 10/number;
}
}
切面类
package com.example.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order(1) // 指定执行顺序
@Aspect // 定义为一个切面
@Component
public class AdviceForPerson {
/**
* 对GeneralPerson的所有方法进行拦截
* */
@Pointcut("execution(* com.example.aop.GeneralPerson.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("--------------------------------");
System.out.println("前置通知-----------");
String name = joinPoint.getSignature().getName();
System.out.println(name);
Object[] args = joinPoint.getArgs();
if(args.length> 0){
for (Object o: args){
System.out.println("-----------参数分隔符---------------");
System.out.println("参数类型:" + o.getClass().getName());
System.out.println("参数值:" + o);
}
}else{
System.out.println("该方法没有参数");
}
}
/**
* 使用returning,将目标对象方法的返回绑定到参数result中,
* */
@AfterReturning(value = "pointcut()",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
System.out.println("后置后置通知--------");
if(result != null ){
System.out.println("返回值类型:"+result.getClass().getName());
}else{
System.out.println("返回值类型:void");
}
}
/**
* 绑定出现的异常:throwing = "ex"
* */
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void afterThrowException(JoinPoint joinPoint , Exception ex){
System.out.println("异常通知----------");
System.out.println("ex"+ex.getMessage());
}
@After("pointcut()")
public void after(JoinPoint joinPoint){
System.out.println("后置通知----------");
}
}
测试:(普通类,没有实现接口的,使用CGLIB实现)
@Test
public void testSpringAop(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopConfig.class);
GeneralPerson person = context.getBean(GeneralPerson.class);
person.say();
person.goHome("地铁");
person.pay(6.23556);
person.divide(0);
}
2.2、目标对象继承接口的:
接口:
package com.example.interfaces;
public interface Say {
void say(int number);
}
实现类:
package com.example.aop;
import com.example.interfaces.Say;
import org.springframework.stereotype.Component;
@Component
public class AroundPerson implements Say {
@Override
public void say(int number) {
System.out.println("测试Around");
int divide = 10/number;
System.out.println("divide:"+divide);
}
}
切面类:
package com.example.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AdviceForAround {
/**
* 对AroundPerson的所有方法进行拦截
* */
@Pointcut("execution(* com.example.aop.AroundPerson.say(..))")
public void pointcutAround(){
}
@Around("pointcutAround()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint){
Signature signature = proceedingJoinPoint.getSignature();
System.out.println("获取方法签名:"+signature.getDeclaringTypeName());
// 获取参数
Object[] args = proceedingJoinPoint.getArgs();
if(args!=null && args.length >0){
for (Object o: args){
System.out.println("-----------参数分隔符---------------");
System.out.println("参数类型:" + o.getClass().getName());
System.out.println("参数值:" + o);
}
}else{
System.out.println("该方法没有参数");
}
Object result = null; // 返回值
try {
System.out.println("Around-前置通知:");
result = proceedingJoinPoint.proceed();
System.out.println("Around-后置返回通知:");
} catch (Throwable throwable) {
System.out.println("Around-出现异常:"+throwable.getMessage());
// throwable.printStackTrace();
}finally {
System.out.println("Around-最终通知");
return result;
}
}
}
测试:(目标对象实现接口,使用的是动态代理)
// 主要是测试Around通知
@Test
public void testSpringAopAround(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopConfig.class);
Say aroundPerson = context.getBean(Say.class);
aroundPerson.say(10);
}
备注:在目标类实现接口的时候,获取的需要是通过接口来获取bean,如果通过目标类来获取,会出现异常,例如上面的获取需要用到类型是com.example.interfaces.Say
2.3、表达式:常见的execution表达式:
更多的表达式:
- 在目标方法上使用自定义的注解进行AOP:
@PointCut("@annotation(自定义注解)")
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void pointcut(){
}
- 在目标类上使用自定义的注解进行AOP:
@Pointcut("@within(自定义注解)")
该类的所有方法(不包含子类方法)执行aop方法
@Pointcut("@within(com.example.annotations.MyAnnotation)")
public void pointcut(){
}
- 指定目标包(不含子包)下所有的类的所有方法进行AOP:
@Pointcut("within(包名前缀.*)")
// 在com.example.aop.inter下的类的所有方法都会执行AOP(这里是不包含子包的)
@Pointcut("within(com.example.aop.inter.*)")
public void pointcut(){
}
- 指定目标包以及子包下所有的类的所有方法进行AOP:
@Around("within(包名前缀..*")
// 在com.example.aop.inter下的类的所有方法都会执行AOP(这里是含子包的)
@Pointcut("within(com.example.aop.inter..*)")
public void pointcut(){
}
- 实现了该接口的类、继承该类、该类本身的类—的所有方法:
@Pointcut("this(java类或接口)")
// 包括不是接口定义的方法,但不包含父类的方法)都会执行aop方法
@Around("this(com.example.service.TestService)")
public void pointcut(){
}
- 实现了该接口的类、继承该类、该类本身的类的所有方法:
@PointCut("target(java类或接口)")
// (包括不是接口定义的方法,包含父类的方法)
@PointCut("target(com.aop.service.TestService)")
public void pointcut(){
}
备注:上面的@PointCut可以和@Before、@AfterThrowing、@AfterReturning、@After和@Around进行替换。
3、基于XML的Spring AOP:
上面的提供是完全基于Java注解的方式进行配置Spring AOP的方法,Spring提供了基于XML的AOP支持,提供类新的 "aop"命名空间
可以在xml文件中使用下面的配置,取代上文中com.example.config.SpringAopConfig配置类的,同样可以在Spring扫描的包下使用注解配置AOP。
<!--指定扫描的包-->
<context:component-scan base-package="com.example"></context:component-scan>
<!--使用注解进行AOP的配置-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
如果需要完全通过XML进行配置:
配置:所有的apsect 和advisor元素必须配置在aop:config元素中
- 一个程序的XML配置文件中可以有多个aop:config元素
- 元素中可以配置pointcut、advisor、aspect等元素
声明一个pointcut:在aop:config元素中,这样可以在apsect和advice之间使用
声明advice:支持的前面提到的5种类型的advice
在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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 指定Spring扫描的包及其子包 -->
<!-- <context:component-scan base-package="com.example"></context:component-scan>-->
<!-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->
<bean id="genericPerson" class="com.example.aop.GeneralPerson"></bean>
<bean id="adviceForPerson" class="com.example.aop.AdviceForPerson"></bean>
<aop:config>
<aop:pointcut id="pointPerson" expression="execution(* com.example.aop.GeneralPerson.*(..))"/>
<aop:aspect id="personAdvice" ref="adviceForPerson">
<aop:before method="before" pointcut-ref="pointPerson"></aop:before>
<aop:after-returning method="afterReturning" pointcut-ref="pointPerson" returning="result"></aop:after-returning>
<aop:after-throwing method="afterThrowException" pointcut-ref="pointPerson" throwing="ex"></aop:after-throwing>
<aop:after method="after" pointcut-ref="pointPerson"></aop:after>
</aop:aspect>
</aop:config>
<bean id="aroundPerson" class="com.example.aop.AroundPerson"></bean>
<bean id="adviceForAround" class="com.example.aop.AdviceForAround"></bean>
<aop:config>
<aop:pointcut id="aroundPoint" expression="execution(* com.example.aop.AroundPerson.*(..))"/>
<aop:aspect id="aroundAdvice" ref="adviceForAround">
<aop:around method="around" pointcut-ref="aroundPoint"></aop:around>
</aop:aspect>
</aop:config>
</beans>
测试:
@Test
public void testSpringAopXml(){
System.out.println("xml配置的方式");
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
GeneralPerson person = context.getBean(GeneralPerson.class);
person.say();
person.goHome("地铁");
person.pay(6.23556);
person.divide(10);
}
@Test
public void testSpringAopAroundXml(){
System.out.println("xml配置的方式");
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
Say aroundPerson = context.getBean(Say.class);
aroundPerson.say(10);
}
4、Spring AOP与AspectJ,如何选择
决定因素有很多,其中最主要的的是应用程序的需求、开发工具和团队对AOP的熟悉程度等
相同点:目的一致,都是处理横切业务
不同点:
- Spring AOP并不提供完整的AOP功能,主要是在方法层面的AOP功能,这里注重于Spring IOC容器相结合,和Spring的框架的结合有天生的优势
- 在AOP的功能方面,Aspect的功能更为完善,在完善性方面各有优势