Spring AOP 简介
AOP(Aspect Orient Programming)是一种设计思维,是软件设计畛域中的面向切面编程,它是面向对象编程 (OOP) 的一种补充和欠缺。它以通过预编译形式和运行期动静代理形式,实现在不批改源代码的状况下给程序动静对立增加额定性能的一种技术。
AOP 与 OOP 字面意思相近,但其实两者齐全是面向不同畛域的设计思维。理论我的项目中咱们通常将面向对象了解为一个动态过程(例如一个零碎有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理形式,了解为一个动静过程,能够在对象运行时动静织入一些扩大性能或管制对象执行。
AOP 利用场景剖析?
理论我的项目中通常会将零碎分为两大部分,一部分是外围业务,一部分是非核业务。在编程实现时咱们首先要实现的是外围业务的实现,非核心业务个别是通过特定形式切入到零碎中,这种特定形式个别就是借助 AOP 进行实现。
AOP 就是要基于 OCP(开闭准则),在不扭转原有系统核心业务代码的根底上动静增加一些扩大性能并能够 ” 管制 ” 对象的执行。例如 AOP 利用于我的项目中的日志解决,事务处理,权限解决,缓存解决等等。
Spring AOP 利用原理剖析
Spring AOP 底层基于代理机制 (动静形式) 实现性能扩大:
- 如果指标对象 (被代理对象) 实现接口,则底层能够采纳 JDK 动静代理机制为指标对象创立代理对象(指标类和代理类会实现独特接口)。
- 如果指标对象 (被代理对象) 没有实现接口,则底层能够采纳 CGLIB 代理机制为指标对象创立代理对象(默认创立的代理类会继承指标对象类型)。
阐明: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) 一种定义, 个别能够了解为多个连接点的汇合。
Spring AOP 疾速实际
我的项目创立及配置
第一步创立 maven 我的项目或在已有我的项目根底上增加 AOP 启动依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步定义业务层接口
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 面向切面编程的过程图示
debug 追踪 cglib 代理的注入对象
debug 追踪 jdk 代理的注入对象
利用总结剖析
代理过程图示剖析
基于 JDK 代理形式实现
如果指标对象有实现接口, 则能够基于 JDK 为指标对象创立代理对象, 而后为指标对象进行性能扩大
阐明:如果指标对象类型没有实现接口,则不容许应用 JDK 代理。
基于 CGLIB 代理形式实现
如果指标对象没有实现接口 (当然实现了接口也是能够的),能够基于 CGLIB 代理形式为指标对象织入性能扩大
阐明:指标对象实现了接口也能够基于 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 设计的要害对象概览
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 中事务管制过程剖析
Spring 事务管理是基于接口代理(JDK)或动静字节码(CGLIB)技术,而后通过 AOP 施行事务加强的。当咱们执行增加了事务个性的指标形式时,零碎会通过指标对象的代理对象调用 DataSourceTransactionManager 对象,在事务开始的时,执行 doBegin 办法,事务完结时执行 doCommit 或 doRollback 办法。
Spring 中事务流传个性
事务流传 (Propagation) 个性指 ” 不同业务 (service) 对象 ” 中的事务办法之间互相调用时, 事务的传播方式
其中, 罕用事务传播方式:
@Transactional(propagation=Propagation.REQUIRED)。
如果没有事务创立新事务, 如果以后有事务参加以后事务, Spring 默认的事务流传行为是 PROPAGATION_REQUIRED,它适宜于绝大多数的状况。假如 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务加强了),假如程序中存在如下的调用链:
Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个办法通过 Spring 的事务流传机制都工作在同一个事务中。
@Transactional(propagation = Propagation.REQUIRED)
@Override
public List<Node> findZtreeMenuNodes() {return sysMenuDao.findZtreeMenuNodes();
}
当有一个业务对象调用如上办法时,此办法始终工作在一个曾经存在的事务办法,或者是由调用者创立的一个事务办法中。
@Transactional(propagation=Propagation.REQUIRES_NEW)。
必须是新事务, 如果有以后事务, 挂起以后事务并且开启新事务,
@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 中的缓存利用原理
进行一个小案例
第一步定义业务接口
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 架构的根本实现为例进行原理剖析和阐明,其繁难架构
其中 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 的过程剖析图