Spring AOP 你应该了解的知识

欢迎访问个人博客www.huangrd.top,浏览更多内容。
此部分见于SSM复习提升

对于 AOP 的如何
  • AOP 的定义
  • AOP 的作用
  • AOP 是怎么实现的(动态代理的一些细节)

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,这个模块被命名为切面Aspect),便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

SpringBoot 默认使用 Cglib

在这里插入图片描述

当然你也可以使用 AspectJ(动态代理) !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

AOP 切面编程设计到的一些专业术语:

术语含义
目标(Target)被通知的对象
代理(Proxy)向目标对象应用通知之后创建的代理对象
连接点(JoinPoint)目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut)被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice)增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect)切入点(Pointcut)+通知(Advice)
Weaving(织入)将通知应用到目标对象,进而生成代理对象的过程动作
Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

AspectJ 定义的通知类型有哪些?
  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知) :目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
多个切面的执行顺序如何控制?
  1. 通常使用@Order 注解直接定义切面顺序
  2. 实现Ordered 接口重写 getOrder 方法。
AOP 原理

AOP 是 IOC 的增强。

在 IOC 进行 Bean 的初始化构建时,也就是 Bean 的生命周期,在后置处理器中,完成 AOP 的增强步骤。具体过程如下:
在这里插入图片描述
在这里插入图片描述

AOP 项目中是如何使用的?
  • 记录日志
  • 事务管理
  • 其他方面的增强

在大学生求职项目中,使用 AOP 来做统一的记录日志,源码见 github https://github.com/A-JAVAer-HRD/community。

编写切面类

package ...............;

import ................

@Component
@Aspect
@Slf4j
public class ServiceLogAsAspect {

    @Pointcut("execution(* com.nowcoder.community.service.*.*(..))")
    public void pointCut() {

    }

    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        // 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()]
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        String ip = request.getRemoteHost();
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        log.info(String.format("用户[%s],在[%s],访问了[%s]", ip, now, target));
    }

}

这样就定一个切面类,关键的注解:

  • @Aspect:定义一个切面
  • @Pointcut(“execution(* com.nowcoder.community.service..(…))”):定义切点表达式,表示所有的 Service 方法都是切点;
  • @Before(“pointCut()”):定义切点的后置处理函数,也就是同一记录日志

在 Lottery 分布式抽奖项目中,通过 AOP 来在使用分库分表的路由组件时,先计算分库分表的路由位置,放入ThreadLocal 中,线程隔离。

package ............;

import .............

@Aspect // 切面类
public class DBRouterJoinPoint {

    private Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class);

    private DBRouterConfig dbRouterConfig;

    private IDBRouterStrategy dbRouterStrategy;

    public DBRouterJoinPoint(DBRouterConfig dbRouterConfig, IDBRouterStrategy dbRouterStrategy) {
        this.dbRouterConfig = dbRouterConfig;
        this.dbRouterStrategy = dbRouterStrategy;
    }

    // 切点表达式
    @Pointcut("@annotation(cn.bugstack.middleware.db.router.annotation.DBRouter)")
    public void aopPoint() {
    }

    /**
     * 环绕通知
     * @param jp        包含了增强方法的信息
     * @param dbRouter  路由注解
     */
    @Around("aopPoint() && @annotation(dbRouter)")
    public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws Throwable {
        String dbKey = dbRouter.key();
        if (StringUtils.isBlank(dbKey) && StringUtils.isBlank(dbRouterConfig.getRouterKey())) {
            throw new RuntimeException("annotation DBRouter key is null!");
        }
        dbKey = StringUtils.isNotBlank(dbKey) ? dbKey : dbRouterConfig.getRouterKey();
        // 路由属性
        String dbKeyAttr = getAttrValue(dbKey, jp.getArgs());
        // 路由策略
        dbRouterStrategy.doRouter(dbKeyAttr);
        // 返回结果
        try {
            return jp.proceed();
        } finally {
           dbRouterStrategy.clear();
        }
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    public String getAttrValue(String attr, Object[] args) {
        if (1 == args.length) {
            Object arg = args[0];
            if (arg instanceof String) {
                return arg.toString();
            }
        }

        String filedValue = null;
        for (Object arg : args) {
            try {
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                filedValue = BeanUtils.getProperty(arg, attr);
            } catch (Exception e) {
                logger.error("获取路由属性值失败 attr:{}", attr, e);
            }
        }
        return filedValue;
    }

}

Spring 的声明式事务也是基于 AOP 来完成的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值