SpringBoot强化篇(八)-- Spring AOP

Spring AOP简介

AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。它以经过预编译方式和运行期动态代理方式,实如今不修改源代码的状况下给程序动态统一添加额外功能的一种技术。
image8.png
AOP与OOP字面意思相近,但其实二者彻底是面向不一样领域的设计思想。实际项目中咱们一般将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,能够在对象运行时动态织入一些扩展功能或控制对象执行。java

AOP 应用场景分析?

实际项目中一般会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时咱们首先要完成的是核心业务的实现,非核心业务通常是经过特定方式切入到系统中,这种特定方式通常就是借助AOP进行实现。算法

AOP就是要基于OCP(开闭原则),在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并能够"控制"对象的执行。例如AOP应用于项目中的日志处理,事务处理,权限处理,缓存处理等等。
image11.pngspring

Spring AOP 应用原理分析

Spring AOP底层基于代理机制(动态方式)实现功能扩展:sql

  1. 假如目标对象(被代理对象)实现接口,则底层能够采用JDK动态代理机制为目标对象建立代理对象(目标类和代理类会实现共同接口)。
  2. 假如目标对象(被代理对象)没有实现接口,则底层能够采用CGLIB代理机制为目标对象建立代理对象(默认建立的代理类会继承目标对象类型)。

image7.png
说明:Spring boot2.x 中AOP如今默认使用的CGLIB代理,假如须要使用JDK动态代理能够在配置文件(applicatiion.properties)中进行以下配置:数据库

#cglib aop proxy
#spring.aop.proxy-target-class=true
#jdk aop proxy
spring.aop.proxy-target-class=false

Spring 中AOP 相关术语分析

  • 切面(aspect): 横切面对象,通常为一个具体类对象(能够借助@Aspect声明)。
  • 通知(Advice):在切面的某个特定链接点上执行的动做(扩展功能),例如around,before,after等。
  • 链接点(joinpoint):程序执行过程当中某个特定的点,通常指向被拦截到的目标方法。
  • 切入点(pointcut):对多个链接点(Joinpoint)一种定义,通常能够理解为多个链接点的集合。

image15.png

Spring AOP快速实践

项目建立及配置

第一步建立maven项目或在已有项目基础上添加AOP启动依赖:编程

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

第二步定义业务层接口api

package com.cy.pj.common.service;
public interface MailService {
    boolean sendMail(String msg);
}

第三步定义业务层实现类缓存

@Service
public class MailServiceImpl implements MailService{
      @Override
     public boolean sendMail(String msg) {//ocp(开闭原则-->对扩展开放,对修改关闭)
         long t1=System.currentTimeMillis();
         System.out.println("send->"+msg);
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return true; 
     }
}

咱们本身计算了执行时间,可是违反了ocp原则,因此须要无侵入式扩展这个记录执行时间的功能。咱们在这个类中添加内部类来实现两种方式的扩展。架构

package com.cy.pj.common.service;
import org.springframework.stereotype.Service;
@Service
public class MailServiceImpl implements MailService{
        @Override
         public boolean sendMail(String msg) {//ocp(开闭原则-->对扩展开放,对修改关闭)
            //long t1=System.currentTimeMillis();
             System.out.println("send->"+msg);
            //long t2=System.currentTimeMillis();
            //System.out.println("send time:"+(t2-t1));
             return true;
     }
}
//下面的两种设计了解?(基于原生方式实现功能扩展)
//本身动手写子类重写父类方法进行功能扩展
class TimeMailServiceImpl extends MailServiceImpl{//这种写法的原型就是CGLIB代理机制的方式(继承)
     @Override
     public boolean sendMail(String msg) {
         long t1=System.currentTimeMillis();
         boolean flag=super.sendMail(msg);
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return flag;
     }
}
//本身写兄弟类对目标对象(兄弟类)进行功能扩展,这种方式又叫组合
class TimeMailServiceImpl2 implements MailService{//这种写法的原型就是JDK代理机制的方式(实现)
     private MailService mailService;
     public TimeMailServiceImpl2(MailService mailService){
            this.mailService=mailService;
     }
     @Override
     public boolean sendMail(String msg) {
         long t1=System.currentTimeMillis();
         boolean flag=mailService.sendMail(msg);
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return flag;
     }
}

下来经过aop方式完成业务并发

package com.cy.pj.common.service.aspect;
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.springframework.stereotype.Component;
@Aspect//告诉spring我是一个切面(封装了扩展逻辑对象),这样的对象中要包含两部份内容(1.切入点,2.扩展逻辑-advice)
@Component//表示在spring中作一个注册
public class SysLogAspect {
     //定义切入点
     //bean表达式为spring中的一种粗粒度切入点表达式(不能精确到具体方法)
     //这里的mailServiceImpl名字为spring容器中一个bean对象的名字
     @Pointcut("bean(mailServiceImpl)")//多个bean的定义形式(bean(*ServiceImpl))
     public void doLogPointCut(){}//这个方法仅仅是承载切入点注解的一个载体,方法体内不须要写任何内容
     
     /*按照Aspect规范定义一个@Around环绕通知*/
     //@Around("bean(mailServiceImpl)")//直接在advice注解内部定义切入点表达式
     //对于@Around注解描述的方法器规范要求
     //1)返回值类型为Object(用于封装目标方法的执行结果)
     //2)参数类型为ProceedingJoinPoint(用于封装执行的目标方法信息)
     //3)抛出的异常Throwable(用于封装执行目标方法时抛出的异常)
     //4)在@Around注解描述的方法内部,能够手动调用目标方法
     @Around("doLogPointCut()")//也能够在advice注解内部经过方法引用引入切入点表达式
     public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
         long t1=System.currentTimeMillis();
         Object result = joinPoint.proceed();//表示调用目标方法
         long t2=System.currentTimeMillis();
         System.out.println("send time:"+(t2-t1));
         return result;
     }
}

编写测试类来测试实现

package com.cy.pj.common.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MailServiceTests {
     @Autowired
     private MailService mailService;
     @Test//面向切面的测试类
     void testSendMail03(){
            mailService.sendMail("hello mailService");
     }
     @Test//本身动手写的子类测试
     void testSendMail01(){
    //new TimeMailServiceImpl().sendMail("hello aop");
     }
     @Test//本身动手写的兄弟类测试
     void testSendMail02(){
    //new TimeMailServiceImpl2(new MailServiceImpl()).sendMail("hello CGB2007");
     }
}

整个aop面向切面编程的过程图示
01-aop-start.png
debug追踪cglib代理的注入对象
02-aop-cglib.png
debug追踪jdk代理的注入对象
03-aop-jdk.png

应用总结分析

代理过程图示分析

image6.png

基于JDK代理方式实现

假如目标对象有实现接口,则能够基于JDK为目标对象建立代理对象,而后为目标对象进行功能扩展
image1.png
说明:假如目标对象类型没有实现接口,则不容许使用JDK代理。

基于CGLIB代理方式实现

假如目标对象没有实现接口(固然实现了接口也是能够的),能够基于CGLIB代理方式为目标对象织入功能扩展
image3.png
说明:目标对象实现了接口也能够基于CGLIB为目标对象建立代理对象。可是目标对象类型假如使用了final修饰,则不可使用CGBLIB。

切面通知应用加强

通知类型

在基于Spring AOP编程的过程当中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知-Advice描述的是一种扩展业务),它们分别是:

  • @Before。(目标方法执行以前执行)
  • @AfterReturning。(目标方法成功结束时执行)
  • @AfterThrowing。(目标方法异常结束时执行)
  • @After。(目标方法结束时执行)
  • @Around.(重点掌握,目标方法执行先后均可以作业务拓展)(优先级最高)
    说明:在切面类中使用什么通知,由业务决定,并非说,在切面中要把全部通知都写上。
package com.cy.pj.common.aspect;
@Component
@Aspect
public class SysTimeAspect {
    @Pointcut("bean(sysUserServiceImpl)")
    public void doTime(){}
 
    @Before("doTime()")
    public void doBefore(){
        System.out.println("time doBefore()");
    }
    @After("doTime()")
    public void doAfter(){
        System.out.println("time doAfter()");
    }
    /**核心业务正常结束时执行* 说明:假若有after,先执行after,再执行returning*/
    @AfterReturning("doTime()")
    public void doAfterReturning(){
        System.out.println("time doAfterReturning");
    }
    /**核心业务出现异常时执行说明:假若有after,先执行after,再执行Throwing*/
    @AfterThrowing("doTime()")
    public void doAfterThrowing(){
        System.out.println("time doAfterThrowing");
    }
    @Around("doTime()")
    public Object doAround(ProceedingJoinPoint jp)
            throws Throwable{
        System.out.println("doAround.before");
         try{
         Object obj=jp.proceed();
           System.out.println("doAround.after");
          return obj;
         }catch(Throwable e){
          System.out.println(e.getMessage());
          throw e;
         }
        
    }
}

切入点表达式加强

bean表达式(重点)

bean表达式通常应用于类级别,实现粗粒度的切入点定义,案例分析:

  • bean("userServiceImpl")指定一个userServiceImpl类中全部方法。
  • bean("*ServiceImpl")指定全部后缀为ServiceImpl的类中全部方法。

说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。
缺陷:不能精确到具体方法,也不能针对于具体模块包中的方法作切入点设计

within表达式

within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:

  • within("aop.service.UserServiceImpl")指定当前包中这个类内部的全部方法。
  • within("aop.service.*") 指定当前目录下的全部类的全部方法。
  • within("aop.service..*") 指定当前目录以及子目录中类的全部方法。

within表达式应用场景分析:
1)对全部业务bean都要进行功能加强,可是bean名字又没有规则。
2)按业务模块(不一样包下的业务)对bean对象进行业务功能加强。

execution表达式

execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:

语法:execution(返回值类型 包名.类名.方法名(参数列表))。

  • execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
  • execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
  • execution( aop.service...*(..)) 万能配置。
@annotation表达式(重点)

@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析

  • @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
  • @annotation(anno.RequiredCache) 匹配有此注解描述的方法。

切面优先级设置实现

切面的优先级须要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。例如:
定义日志切面并指定优先级。

@Order(1)
@Aspect
@Component
public class SysLogAspect {
 …
}
定义缓存切面并指定优先级:
@Order(2)
@Aspect
@Component
public class SysCacheAspect {
…
}

说明:当多个切面做用于同一个目标对象方法时,这些切面会构建成一个切面链,相似过滤器链、拦截器链

关键对象与术语总结

Spring 基于AspectJ框架实现AOP设计的关键对象概览
image13.png

Spring AOP事务处理

Spring 中事务简介

事务定义

事务(Transaction)是一个业务,是一个不可分割的逻辑工做单元,基于事务能够更好的保证业务的正确性。

事务特性(ACID)

  • 原子性(Atomicity):一个事务中的多个操做要么都成功要么都失败。
  • 一致性(Consistency): 例如存钱操做,存以前和存以后的总钱数应该是一致的。
  • 隔离性(Isolation):事务与事务应该是相互隔离的。
  • 持久性(Durability):事务一旦提交,数据要持久保存。

说明:目前市场上在事务一致性方面,一般会作必定的优化,比方说只要最终一致就能够了,这样的事务咱们一般会称之为柔性事务(只要最终一致就能够了).

Spring 中事务管理

Spring 中事务方式概述

Spring框架中提供了一种声明式事务的处理方式,此方式基于AOP代理,能够将具体业务逻辑与事务处理进行解耦。也就是让咱们的业务代码逻辑不受污染或少许污染,就能够实现事务控制。
在SpringBoot项目中,其内部提供了事务的自动配置,当咱们在项目中添加了指定依赖spring-boot-starter-jdbc时,框架会自动为咱们的项目注入事务管理器对象,最经常使用的为DataSourceTransactionManager对象。

Spring 中事务管理实现

实际项目中最经常使用的注解方式的事务管理,以注解@Transactional配置方式为例,进行实践分析。
基于@Transactional 注解进行声明式事务管理的实现步骤分为两步:
1) 启用声明式事务管理,在项目启动类上添加@EnableTransactionManagement,新版本中也可不添加(例如新版Spring Boot项目)。
2) 将@Transactional注解添加到合适的业务类或方法上,并设置合适的属性信息。

@Transactional(timeout = 30,
 readOnly = false,
 isolation = Isolation.READ_COMMITTED,
 rollbackFor = Throwable.class,
 propagation = Propagation.REQUIRED)
 @Service
 public class implements SysUserService {
 @Transactional(readOnly = true)
 @Override
 public PageObject<SysUserDeptVo> findPageObjects(
 String username, Integer pageCurrent) {
 …
 }
}

其中,代码中的@Transactional注解用于描述类或方法,告诉spring框架咱们要在此类的方法执行时进行事务控制,其具体说明以下:。
▪ 当@Transactional注解应用在类上时表示类中全部方法启动事务管理,而且通常用于事务共性的定义。
▪ 当@Transactional描述方法时表示此方法要进行事务管理,假如类和方法上都有@Transactional注解,则方法上的事务特性优先级比较高。

@Transactional经常使用属性应用说明:
▪ timeout:事务的超时时间,默认值为-1,表示没有超时显示。若是配置了具体时间,则超过该时间限制但事务尚未完成,则自动回滚事务。这个时间的记录方式是在事务开启之后到sql语句执行以前。
▪ read-only:指定事务是否为只读事务,默认值为 false;为了忽略那些不须要事务的方法,好比读取数据,能够设置read-only为true。对添加,修改,删除业务read-only的值应该为false。
▪ rollback-for:用于指定可以触发事务回滚的异常类型,若是有多个异常类型须要指定,各种型之间能够经过逗号分隔。
▪ no-rollback- for: 抛出no-rollback-for 指定的异常类型,不回滚事务。
▪ isolation:事务的隔离级别,默认值采用 DEFAULT。当多个事务并发执行时,可能会出现脏读,不可重复读,幻读等现象时,但假如不但愿出现这些现象可考虑修改事务的隔离级别(但隔离级别越高并发就会越小,性能就会越差)

Spring中事务控制过程分析
image2.png

Spring事务管理是基于接口代理(JDK)或动态字节码(CGLIB)技术,而后经过AOP实施事务加强的。当咱们执行添加了事务特性的目标方式时,系统会经过目标对象的代理对象调用DataSourceTransactionManager对象,在事务开始的时,执行doBegin方法,事务结束时执行doCommit或doRollback方法。

Spring 中事务传播特性
事务传播(Propagation)特性指"不一样业务(service)对象"中的事务方法之间相互调用时,事务的传播方式
image16.png
其中,经常使用事务传播方式:

@Transactional(propagation=Propagation.REQUIRED)。

若是没有事务建立新事务, 若是当前有事务参与当前事务, Spring 默认的事务传播行为是PROPAGATION_REQUIRED,它适合于绝大多数的状况。假设 ServiveX#methodX() 都工做在事务环境下(即都被 Spring 事务加强了),假设程序中存在以下的调用链:
Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法经过 Spring 的事务传播机制都工做在同一个事务中。

image17.png

@Transactional(propagation = Propagation.REQUIRED)
    @Override
    public List<Node> findZtreeMenuNodes() {
        return sysMenuDao.findZtreeMenuNodes();
    }

当有一个业务对象调用如上方法时,此方法始终工做在一个已经存在的事务方法,或者是由调用者建立的一个事务方法中。

@Transactional(propagation=Propagation.REQUIRES_NEW)。

必须是新事务, 若是有当前事务, 挂起当前事务而且开启新事务,
image9.png

@Transactional(propagation = Propagation.REQUIRES_NEW)
 @Override
 public void saveObject(SysLog entity) {
    sysLogDao.insertObject(entity);
 }

当有一个业务对象调用如上业务方法时,此方法会始终运行在一个新的事务中。

Spring AOP 异步操做实现

异步场景分析

在开发系统的过程当中,一般会考虑到系统的性能问题,提高系统性能的一个重要思想就是“串行”改“并行”。提及“并行”天然离不开“异步”,今天咱们就来聊聊如何使用Spring的@Async的异步注解。

Spring 业务的异步实现

启动异步配置

在基于注解方式的配置中,借助@EnableAsync注解进行异步启动声明,Spring Boot版的项目中,将@EnableAsync注解应用到启动类上

@EnableAsync //spring容器启动时会建立线程池
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Spring中@Async注解应用

在须要异步执行的业务方法上,使用@Async方法进行异步声明。

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void saveObject(SysLog entity) {
     System.out.println("SysLogServiceImpl.save:"+
     Thread.currentThread().getName());
     sysLogDao.insertObject(entity);
     //try{Thread.sleep(5000);}catch(Exception e) {}
 }

假如须要获取业务层异步方法的执行结果,可参考以下代码设计进行实现:

@Transactional(propagation = Propagation.REQUIRES_NEW)
    @Async
    @Override
    public Future<Integer> saveObject(SysLog entity) {
        System.out.println("SysLogServiceImpl.save:"+
        Thread.currentThread().getName());
        int rows=sysLogDao.insertObject(entity);
        //try{Thread.sleep(5000);}catch(Exception e) {}
        return new AsyncResult<Integer>(rows);
    }

其中,AsyncResult对象能够对异步方法的执行结果进行封装,假如外界须要异步方法结果时,能够经过Future对象的get方法获取结果。

当咱们须要本身对spring框架提供的线程池进行一些简易配置

spring:
  task:
    execution:
      pool:
        queue-capacity: 128
        core-size: 5
        max-size: 128
        keep-alive: 60000
      thread-name-prefix: db-service-task-

对于spring框架中线程池配置参数的涵义,能够参考ThreadPoolExecutor对象中的解释。

说明:对于@Async注解默认会基于ThreadPoolTaskExecutor对象获取工做线程,而后调用由@Async描述的方法,让方法运行于一个工做线程,以实现异步操做。可是假如系统中的默认拒绝处理策略,任务执行过程的异常处理不能知足咱们自身业务需求的话,我能够对异步线程池进行自定义.(SpringBoot中默认的异步配置能够参考自动配置对象TaskExecutionAutoConfiguration).

Spring 自定义异步池的实现(拓展)

为了让Spring中的异步池更好的服务于咱们的业务,同时也尽可能避免OOM,能够自定义线程池优化设计以下:

package com.cy.pj.common.config
@Slf4j
@Setter
@Configuration
@ConfigurationProperties("async-thread-pool")
public class SpringAsyncConfig implements AsyncConfigurer{
    /**核心线程数*/
    private int corePoolSize=20;
    /**最大线程数*/
    private int maximumPoolSize=1000;
    /**线程空闲时间*/
    private int keepAliveTime=30;
    /**阻塞队列容量*/
    private int queueCapacity=200;
    /**构建线程工厂*/
    private ThreadFactory threadFactory=new ThreadFactory() {
        //CAS算法
        private AtomicInteger at=new AtomicInteger(1000);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, 
"db-async-thread-"+at.getAndIncrement());
        }
    };    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maximumPoolSize);
        executor.setKeepAliveSeconds(keepAliveTime);
        executor.setQueueCapacity(queueCapacity);
        executor.setRejectedExecutionHandler((Runnable r, 
 ThreadPoolExecutor exe) -> {
                log.warn("当前任务线程池队列已满.");
        });
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler 
getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex ,
 Method method , Object... params) {
                log.error("线程池执行任务发生未知异常.", ex);
            }
        };
    }}

其中:@ConfigurationProperties("async-thread-pool")的含义是读取application.yml配置文件中以"async-thread-pool"名为前缀的配置信息,并经过所描述类的set方法赋值给对应的属性,在application.yml中链接器池的关键配置以下:

async-thread-pool:
       corePoolSize: 20
       maxPoolSize: 1000
       keepAliveSeconds: 30
       queueCapacity: 1000

后续在业务类中,假如咱们使用@Async注解描述业务方法,默认会使用ThreadPoolTaskExecutor池对象中的线程执行异步任务。

Spring AOP中Cache操做实现

缓存场景分析

在业务方法中咱们可能调用数据层方法获取数据库中数据,假如访问数据的频率比较高,为了提升的查询效率,下降数据库的访问压力,能够在业务层对数据进行缓存.

Spring 中业务缓存应用实现过程

启动缓存配置

在项目(SpringBoot项目)的启动类上添加@EnableCaching注解,以启动缓存配置。

package com.cy;
/**
* 异步的自动配置生效).
 * @EnableCaching 注解表示启动缓存配置
 */
@EnableCaching
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

业务方法上应用缓存配置

在须要进行缓存的业务方法上经过@Cacheable注解对方法进行相关描述.表示方法的返回值要存储到Cache中,假如在更新操做时须要将cache中的数据移除,能够在更新方法上使用@CacheEvict注解对方法进行描述。
第一步:在相关模块查询相关业务方法中,使用缓存

@Cacheable(value = "menuCache")
@Transactional(readOnly = true)
public List<Map<String,Object>> findObjects() {
....
}

其中,value属性的值表示要使用的缓存对象,名字本身指定,其中底层为一个map对象,当向cache中添加数据时,key默认为方法实际参数的组合。
第二步:在相关模块更新时,清除指定缓存数据
allEntries表示清除全部。

@CacheEvict(value="menuCache",allEntries=true)
 @Override
 public int saveObject(SysDept entity) {...}

spring中的缓存应用原理
image14.png

进行一个小案例

第一步定义业务接口

package com.cy.pj.module.service;
import java.util.List;
public interface ModuleService {
    List<String> findPermissions();
}

第二步定义业务实现类

package com.cy.pj.module.service;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ModuleServiceImpl implements ModuleService{
    @Override
     public List<String> findPermissions() {
         System.out.println("select permissions from database");
         List<String> list=new ArrayList<>();
         list.add("sys:log:delete");
         list.add("sys:log:select");//假设这些数据来自数据库
         return list;
     }
}

第三步定义切面

package com.cy.pj.commom.aspect;

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.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Aspect
public class CacheAspect {//系统底层会将这个切面中的内容转换为Advisor对象

    //假设这个map就是咱们的一个小cache,咱们从数据库取出的数据能够存储到此cache中
    private Map<String,Object> cache=new ConcurrentHashMap<>();

    @Pointcut("bean(moduleServiceImpl)")
    public void doCache(){}

    @Around("doCache()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //1.从cache中取数据
        Object obj = cache.get("userPer");//假设key为userPer
        if(obj!=null) return obj;
        //2.cache中没有则查数据
        obj= joinPoint.proceed();
        //3.将数据存储到cache
        cache.put("userPer", obj);
        return obj;
    }
}

第四步编写测试类测试

package com.cy.pj.module.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class ModuleServiceTests {

    @Autowired
    private ModuleService moduleService;

    @Test
    void testFindPermissions(){
        List<String> permissions = moduleService.findPermissions();
        permissions = moduleService.findPermissions();
        permissions = moduleService.findPermissions();
    }
}

Spring AOP原生方式实现

Spring 整合AspectJ框架实现AOP只是Spring框架中AOP的一种实现方式,此方式相对比较简单,实现方便。但此方式底层仍是要转换为Spring原生AOP的实现,Spring AOP原生方式实现的核心有两大部分构成,分别是:
▪ 代理(JDK,CGLIB)。
▪ org.aopalliance包下的拦截体系。

以Spring中一种原生AOP架构的基本实现为例进行原理分析和说明,其简易架构
image10.png
其中DefaultAdvisorAutoProxyCreator这个类功能更为强大,这个类的奇妙之处是他实现BeanPostProcessor接口,当ApplicationContext读取全部的Bean配置信息后,这个类将扫描上下文,寻找全部的Advisor对象(一个Advisor由切入点和通知组成),将这些Advisor应用到全部符合切入点的Bean中。

核心业务接口定义及实现

定义邮件业务接口,用于定义搜索业务规范

package com.cy.pj.common.service;
public interface MailService {
    boolean sendMsg(String message);
}

定义邮件业务接口实现

package com.cy.pj.common.service;

import org.springframework.stereotype.Service;

@Service
public class MailServiceImpl implements MailService{

    @Override
    public boolean sendMsg(String message) {
        System.out.println("send->"+message);
        return true;
    }

}

定义LogAdvice对象,基于此对象为目标业务对象作日志加强。

其中,MethodInterceptor对象继承Advice对象,基于此对象方法能够对目标方法进行拦截。

package com.cy.pj.common.advisor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**封装了扩展业务逻辑的对象,这样的对象在原生的aop中须要在advisor中注册*/
public class LogAdvice implements MethodInterceptor {//Advice

    /**此方法能够在目标业务方法执行以前和以后添加扩展逻辑*/
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("start:"+System.nanoTime());
        Object result = methodInvocation.proceed();//执行目标方法
        System.out.println("end:"+System.nanoTime());
        return result;
    }
}

日志Advisor对象定义及实现

其中,StaticMethodMatcherPointcutAdvisor类为Spring框架中定义的一种Advisor,咱们本身写的Advisor能够直接继承此类进行资源整合。

package com.cy.pj.common.advisor;

import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 此Advisor中定义了一种规范
 * 1)定义了哪些方法为切入点方法
 * 2)定义了在切入点方法执行时要植入的通知(扩展逻辑)
 */
@Component
public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor {
    //定通知 advice
    public LogAdvisor(){
        setAdvice(new LogAdvice());
    }

    //定切点 pointcut
    /**
     * matches方法中能够获取咱们要执行的目标方法,而且咱们能够在此判断这个目标方法是否为咱们的一个切入点方法
     * 1)返回值为true表示目标方法为切入点方法(在此方法执行时能够植入扩展逻辑)
     * 2)返回值为false表示目标方法为非切入点方法
     */
    @Override
    public boolean matches(Method method, Class<?> aClass) {
        try {
            Method targetMethod =
                    aClass.getMethod(method.getName(), method.getParameterTypes());
            return targetMethod.getName().equals("sendMsg");
        }catch (Exception e){
            return false;
        }
    }
}

BeanPostProcessor类型对象初始化

在项目启动类中,添加DefaultAdvisorAutoProxyCreator对象初始化方法,基于此对象在容器启动时扫描全部Advisor对象,而后基于切入点描述的目标方法为目标对象建立代理对象

package com.cy;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

    @Bean //@Bean注解描述方法时,这个方法的返回值交给spring管理
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        return new DefaultAdvisorAutoProxyCreator();
    }
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

基于Spring boot项目进行单元测试

package com.cy.pj.common.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MailServiceTests {
    @Autowired
    private MailService mailService;

    @Test
    void testSendMsg(){
        System.out.println(mailService.sendMsg("hello spring aop"));
    }
}

原生实现AOP的过程分析图

04-aop-spring.png

相关文章
相关标签/搜索