Spring——AOP

软件工程有个基本原则,即关注点分离,不同的问题交给不同的部分去解决,面向切面编程 AOP 正是此种技术的体现,通用化功能代码的实现对应的就是所谓的切面(Aspect),业务功能代码和切面代码分开后,架构将变得高内聚低耦合。

AOP 的主要名词概念:

  • Aspect:通用功能的代码实现;
  • Target:被织入 Aspect 的对象;
  • Join Point:可以作为切入点的机会,所有方法都可以作为切入点;
  • Pointcut:Aspect 实际被应用在的 Join Point,支持正则;
  • Advice:类里的方法以及这个方法如何织入到目标方法的方式;
  • Weaving:AOP 的实现过程。

Advice 的种类:

  • 前置通知(Before);
  • 后置通知(AfterReturning);
  • 异常通知(AfterThrowing);
  • 最终通知(After);
  • 环绕通知(Around)
Spring AOP的使用

基于 Spring Boot 构建项目,使用 AOP 需要添加 Maven 依赖:

<!-- aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

这里我们创建 Aspect 文件通过 AOP 统一处理 request 请求日志:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

@Aspect // 定义为切面类
@Component
public class RequestLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);
    // 定义切入点
    @Pointcut("execution(public * com.example.server.soa.web.controller..*.*(..))") // 支持正则
    public void webLog() {
    }
    // 方法执行之前切入
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        // 接收到请求, 记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.info("url: ", request.getRequestURL().toString());
        logger.info("ip: ", request.getRemoteAddr());
    }
    @After("webLog()")
    public void doAfter() {
        logger.info("doAfter");
    }
    // 方法执行return之后切入
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
        // 处理完请求, 返回内容
        logger.info("response: ", ret);
    }
    @AfterThrowing("webLog()")
    public void doAfterThrowing() {
        logger.info("doAfterThrowing");
    }
}

Spring AOP的原理

Spring 提供了两种方式来生成代理对象:JdkProxy 和 Cglib,具体使用哪种方式生成,由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类实现了接口,则用 JdkProxy 来实现,否则使用 Cglib。

说明实现机制优点
JdkProxy(Jdk 动态代理技术)1、通过反射接受被代理的类,并且要求被代理的类必须实现一个接口;
2、JdkProxy 的核心是 InvocationHandler 接口和 Proxy 类;
通过 Java 的内部反射机制实现的反射机制在生成类的过程中比较高效
Cglib(一个代码生成的类库)1、以继承的方式动态生成目标类的代理;
2、是通过修改字节码来实现代理的,可以在运行时动态的生成某个类的子类;
3、如果某个类被 final 修饰,是无法使用 Cglib 来做动态代理的;
借助 ASM 实现,ASM 是一种能够操作字节码的框架ASM 在生成类之后的执行过程中比较高效

掌握 Spring AOP 的原理,需要先看下代理模式:

代理模式就是接口 + 真正实现类 + 代理类组成的,其中真实实现类和代理类都是需要实现接口的,实例化的时候要使用代理类,所以 Spring AOP 需要做的是生成一个代理类来替换掉真实实现类以对外服务。

Spring事务

Transactional注解

@Transactional的常用配置参数总结:

属性名说明
propagation事务的传播行为,默认值为 REQUIRED
isolation事务的隔离级别,默认值采用 DEFAULT
timeout事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly指定事务是否为只读事务,默认值为 false。
rollbackFor用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。

如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。


常见问题

@Transactional 只能应用到 public 方法才有效?

只有@Transactional 注解应用到 public 方法,才能进行事务管理。这是因为在使用 Spring AOP 代理时,Spring 在调用 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取@Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

这个方法会检查目标方法的修饰符是不是 public,若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。

Spring AOP 自调用问题

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

spring自调用为啥失效?

spring会扫描所有添加了@Transcational注解的方法,添加了@Transcational的类的对象都会生成一个代理对象,spring事务会判断代理类执行的方法是否是添加了@Transcational注解的,而非事务方法调用事务方法属于自调用,自调用方法并不是由代理而是通过反射来执行的

自调用方法,调用类是原对象,而aop中只有调用类是代理对象时,才能出发各种通知,spring 事务就是基于通知来实现的。

@Service
public class OrderService {
    private void insert() {
    insertOrder();
}
@Transactional
public void insertOrder() {
        //insert log info
        //insertOrder
        //updateAccount
       }
}

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。

正确的设置@Transactional 的 propagation 属性

需要注意下面三种 propagation 可以不启动事务。本来期望目标方法进行事务管理,但若是错误的配置这三种 propagation,事务将不会发生回滚。

  1. TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  2. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  3. TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值