浅谈 Spring AOP 思想

Spring AOP

AOP 切面编程

AOP 底层是基于动态代理思想实现的 可以把核心业务业务逻辑看作是一个圆增强的业务逻辑也看成是一个圆,核心业务逻辑在增强的业务逻辑的中央,这样就形成了一个同心圆(切面), 这种编程思想的好处是 无侵入 即 不会对源码进行修改。以一个为核心业务逻辑增加日志的例子来逐步引出动态代理和AOP面向切面编程思想。

普通代理类

最原始的方式是利用代理类的方式为核心业务逻辑增添打印日志的功能,这就要求被代理类和增强类要同时实现一个接口来保证方法的一致性,但是还需要为每一个代理类都创建一个对象,当增强的方法都相同时就造成了代码的重复编写,造成了开发效率的降低。下面这种是修改源码的方式。

目前代码存在两个问题

代码耦合性高 :业务代码和日志代码耦合在了一起

代码复用性低:日志代码在每个方法都要书写一遍

JDK动态代理

为了解决基本代理方式的不足,产生了JDK动态代理的方式,但是这样的方式还有一个不足,就是只能对接口的实现类进行代理,在此背景下,Cglib动态代理又出现了,Cglib动态代理可以为类动态创建代理类。

PS: 在创建代理对象时 jdk的速度要高于cglib

所以选择的时候:

当被代理类有接口 的时候,使用jdk动态代理

当被代理类没有接口的时候,使用cglib动态代理

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {

    @Autowired
    private DeptService deptService;

    @Autowired
    private Logger logger;

    @Test
    public void test1() {

        InvocationHandler myHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                logger.m1();

                try {

                    method.invoke(deptService,args);

                } catch (Exception e) {

                    logger.m3();
                }
                logger.m2();

                return null;
            }
        };


      DeptService deptServiceproxy = (DeptService) Proxy.newProxyInstance(
                deptService.getClass().getClassLoader(),
                new Class[]{DeptService.class},
               myHandler
        );


        deptServiceproxy.save(null);
        deptServiceproxy.findAll();
        deptServiceproxy.findById(null);
    }

}

Cglib动态代理

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DeptServiceTest {


    @Autowired
    private DeptServiceImpl deptService;

    @Autowired
    private Logger logger;

    @Test
    public void test01() {

        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                logger.m1();

                try {
                    method.invoke(deptService, args);
                } catch (Exception e) {
                    logger.m3();
                }
                logger.m2();
                return null;
            }
        };


        DeptServiceImpl deptServiceProxy = (DeptServiceImpl) Enhancer.create(DeptServiceImpl.class, invocationHandler);
        //注意: Enhancer有个硬伤,就是无法对已经封装过的targetclass,再继续封装多一次,无法实现多个interceptor的链式调用。


        deptServiceProxy.findAll();
        System.out.println("=============================");
        deptServiceProxy.save(null);
        System.out.println("=============================");
        deptServiceProxy.findById(null);
    }
}

首先通过enhancer创建代理类,再创建代理类的实例对象

AOP

切面类要使用两个注解 @Component 和 @Aspect 声明该类为一个切面类,并且将该切面类加入到Spring容器中,在切面类中可以定义切点 @ PointCut()和通知 (5种),下面会展开详细叙述

AOP术语

AOP切面编程的优势

Advice通知类型(5种)

java 复制代码
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

@Around

java 复制代码
@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回结果  Object result = pjp.proceed();

通知的执行顺序 @Order

不同切面类中,默认按照切面类的全类名 字母排序:

目标方法前的通知方法:字母排名靠前的先执行

目标方法后的通知方法:字母排名靠前的后执行

相当于一个嵌套的效果一样,类比Filter中的过滤器链 的执行顺序,也是按照全类名字母靠前的先执行

切入点表达式

切入点表达式:描述切入点方法@PointCut()的一种表达式

作用:主要用来决定项目中的哪些方法需要加入通知

常见形式:

表达式@execution

execution(......):根据方法的签名来匹配,常与通配符(* 和 ...)搭配使用 ,...用在参数表示0或n个参数 *模糊匹配

使用切入点表达式 需要指定 权限修饰符 返回值类型 方法名的全名(带全类名) 参数 ,因此excution常与通配符搭配用来定义方法名有规律的切入点

注解@annotation

@annotation(......) :根据注解匹配,使用注解方式,需要自定义注解,常用来定义方法名无规律的切入点pointcut,自定义注解,如下所示:@annotation(注解全类名)

在切点配置自定义注解,用于识别对那些方法进行增强

配置切点注解

Spring事务管理

在Spring容器中使用事务时还需要在Spring配置类中配置@EnableTransactionMangement注解和@Bean配置事务管理器

@Transactional 及 @Transactional 的两个属性

位置:业务(service)层的方法上、类上、接口上

作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务

注解用在方法上,表示为方法开启事务,用在类上表示为类中所有方法开启事务,用在接口上表示为接口的所有实现类开启事务

@Transactional 的 rollback 回滚 属性

默认情况下,只有出现 RuntimeException 才回滚异常,rollbackFor属性用于控制让非运行时异常也回滚。

因此,需要为 @Transactional的rollback属性指定异常类类型让其对所有异常都进行回滚

@Transactional 的 propagation 传播行为 属性

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。通俗点说就是:一个有事务注解修饰的方法在被另一个开启了事务的方法调用时,该方法的事务怎么办的问题。默认值是required

2中比较常用的是 required (默认) 有事务就加入,无事务就创建新的 和 requires_new 事务不影响

AOP相关注解总结(十一)

配置切面相关注解

@Component

@Aspect

@PointCut

@annotation

通知相关注解

@Before

@AfterReturning

@AfterThrowing

@After

@Around

@Oredr

事务相关注解

@EnableTransactionMangement

@Transactional

相关推荐
苏三说技术1 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎2 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode2 小时前
Redis 在生产项目的使用
前端·后端
用户559822481222 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode2 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战2 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha2 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn2 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425912 小时前
ShardingJDBC
后端
行者全栈架构师2 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端