SpringBoot工程中AOP应用实践


AOP 简介

对于一个业务而言,我们如何在不修改源代码的基础上对对象功能进行拓展,例如现有一个公告(通知)业务接口及实现:

public interface NoticeService {
    int deleteById(Integer... ids);
}
public class NoticeServiceImpl implements NoticeService {
    @Override
    public int deleteById(Integer... ids) {
        System.out.println(Arrays.toString(ids));
        return 0;
    }
}

需求:基于OCP(开闭原则-对扩展开放对修改关闭)设计原则对NoticeServiceImpl类的功能进行扩展,例如在deleteById业务方法执行之前和之后输出一下系统时间.

方案1:基于继承方式实现其功能扩展,关键设计如下:

public class CglibLogNoticeService extends NoticeServiceImpl{
    public int deleteById(Integer... ids){
        System.out.println("Start:"+System.currentTimeMillis());
        int rows=super.deleteById(ids);
        System.out.println("After:"+System.currentTimeMillis());
        return rows;
    }
}

测试类如下:

public class NoticeServiceTests{
     public static void main(String[] args){
         NoticeService ns=new CglibLogNoticeService();
         ns.deleteById(10,20,30);
     }
}

这种基于继承方式实现功能扩展,代码简单,容易理解,但是不够灵活,耦合性比较强。

方案2:基于组合方式实现其功能扩展,关键代码设计如下:

public class JdkLogNoticeService implements NoticeService{
      private NoticeService noticeService;//has a 
      public  JdkLogNoticeService(NoticeService noticeService){
        this.noticeService=noticeService;
      }
     public int deleteById(Integer…ids){
         System.out.println("Start:"+System.currentTimeMillis());
         int rows=this.noticeService.deleteById(ids);
         System.out.println("After:"+System.currentTimeMillis());
         return rows;
      }
}

测试类:

public class NoticeServiceTests{
     public static void main(String[] args){
         NoticeService ns=
         new JdkLogNoticeService(new NoticeServiceImpl());
         ns.deleteById(10,20);
     }
}

基于组合方式实现功能扩展,代码比较灵活,耦合低,稳定性强,但理解相对比较困难。

AOP概述

AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行)。如图所示:图片: 在这里插入图片描述

AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。

实现原理

AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.如图所示:图片: 在这里插入图片描述

其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):

  • 第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
  • 第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.
@Aspect:作用是把当前类标识为一个切面供容器读取
 
@PointcutPointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,
	一是表达式,二是方法签名。方法签名必须是 publicvoid型。
	可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,
	因此我们可以通过方法签名的方式为 此表达式命名。
	因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行

Spring AOP 快速入门

在项目中定义一个日志切面,通过切面中的通知方法为目标业务对象做日志功能增强。
添加AOP依赖:

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

说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了 AOP 的一些语法,有一个专门的字节码生成器来生成遵守 java 规范的 class 文件。

运行原理:
在这里插入图片描述

业务切面对象设计
通过设计切面对象,为目标业务方法做功能增强,关键步骤如下:

第一步:创建注解类型,应用于切入点表达式的定义,关键代码如下:

package com.cy.pj.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
       String operation();
}

第二步:创建切面对象,用于做日志业务增强,关键代码如下:

package com.cy.pj.sys.service.aspect;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class SysLogAspect {

    private static final Logger log= LoggerFactory.getLogger(SysLogAspect.class);
    /**
     * @Pointcut注解用于定义切入点
     * @annotation(注解)为切入点表达式,后续由此注解描述的方法为切入
     * 点方法
     */
    @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
    public void doLog(){}//此方法只负责承载切入点的定义,方法中不写任何内容,只是切入点表达式

    /**
     * @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
     * @param jp 连接点对象,此对象封装了要执行的目标方法信息.
     * 可以通过连接点对象调用目标方法.
     * @return 目标方法的执行结果
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp)throws Throwable{
        long t1=System.currentTimeMillis();
        try {
            //执行目标方法(切点方法中的某个方法)
            Object result = jp.proceed();
            long t2=System.currentTimeMillis();
            log.info("opertime:{}",t2-t1); return result;//目标业务方法的执行结果
        }catch(Throwable e){
            e.printStackTrace();
            long t2=System.currentTimeMillis();
            log.info("exception:{}",e.getMessage());
            throw e;
        }
    }

说明:

  • @annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义
    -@annotation(anno.RequiredLog) 匹配有此注解描述的方法。

  • 其中:RequiredLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日志扩展操作。

第三步:通过注解RequiredLog注解描述日志查询或删除业务相关方法,此时这个方法为日志切入点方法,例如:

@RequiredLog(operation="公告查询")
@Override
public List<SysLog> findLogs(SysLog sysLog) {
List<SysLog> list=syslogDao.selectLogs(sysLog);
    return list;
}

Spring AOP 技术进阶

通知类型

Spring框架AOP模块定义通知类型,有如下几种:

  • @Around (优先级最高的通知,可以在目标方法执行之前,之后灵活进行业务拓展.)
  • @Before (目标方法执行之前调用)
  • @AfterReturning (目标方法正常结束时执行)
  • @AfterThrowing (目标方法异常结束时执行)
  • @After (目标方法结束时执行,正常结束和异常结束它都会执行)

切面执行顺序

切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值