赞
踩
AOP( Aspect Orient Programming
)是一种设计思想
是软件设计领域中的 面向切面编程
,它是面向对象编程(OOP)的一种补充和完善。
实际项目中我们通常将面向对象理解为一个静态过程
(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性)
面向切面理解为一个动态过程
(在对象运行时动态织入一些扩展功能或控制对象执行)。
如图-1所示:
图-1
.
- logging - 日志切面
- security - 安全切面
实际项目中通常会将系统分为两大部分
在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助AOP进行实现。
AOP就是要基于OCP(开闭原则)
在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以"控制"对象的执行。
# 常见设计原则:
- 单一职责原则 - ioc
- 依赖倒置原则 - ioc
- 开闭原则(ocp) - aop
例如AOP应用于项目中的 日志处理
, 事务处理
, 权限处理
, 缓存处理
等等。
如图-2所示:
图-2
思考:现有一业务,在没有AOP编程时,如何基于OCP原则实现功能扩展?
Spring AOP底层基于代理机制实现功能扩展:
图-3
说明:Spring boot2.x 中AOP现在默认使用的CGLIB代理,假如需要使用JDK动态代理可以在配置文件(applicatiion.properties)中进行如下配置:
spring.aop.proxy-target-class=false
切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
切入点(pointcut):对连接点拦截内容的一种定义,一般可以理解为多个连接点的结合。
连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
连接点与切入点定义如图-4所示:
图-4
说明:概念很晦涩难懂,多做例子,做完就会清晰。先可以按白话去理解。
基于项目中的核心业务,添加简单的日志操作,借助SLF4J日志API输出目标方法的执行时长。
创建maven项目或在已有项目基础上添加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.aspect; /** * @Aspect 注解描述的类型为一个 AOP 应用中的切面类型,此类的构成: * 1)切入点 (在哪些方法执行时,我们要植入扩展功能):例如所有地铁站的入口 * 2)通知 (要植入的扩展功能):例如安检 * * @author Administrator * */ @Aspect @Component @Slf4j public class SysLogAspect { @Pointcut 注解用于描述方法,描述的方法不需要写具体内容 , 底层运行时,会获取方法上的注解,并拿到注解中定义的表达式, 如:bean() 为一种切入点表达式的定义方式 。 ()中的内容为一个bean对象或者一堆bean对象 (例如*ServiceImpl) 系统底层会认为bean表达式描述的对象中所有方法的集合为切入点, --------------------------------------------------- 当 spring管理的 bean - sysUserServiceImpl 所有方法 被执行, 调用目标方法 ↓ @Pointcut("bean(sysUserServiceImpl)") public void logPointCut() {} @Around 环绕方法 - 切入切点前后 在切面类型中要使用特殊标记进行描述,这个标记为注解 ↓ ↓ jp 连接点(JoinPoint):对应切入点描述的方法中的一个目标方法 ↓ 调用的方法 (ProceedingJoinPoint - 执行中插入的切点) ↓ ↓ | @Around("logPointCut()") ↓ public Object around(ProceedingJoinPoint jp) throws Throwable{ ↑ 扩展功能的实现 ↓ try { log.info("start:"+System.currentTimeMillis()); Object result=jp.proceed(); ←--- 调用下一个切面方法或目标方法 log.info("after:"+System.currentTimeMillis()); return result; ←- - - - 目标方法的执行结果 返回 给 controller }catch(Throwable e) { log.error(e.getMessage()); ←--- 日志打印异常 throw e; } } }
说明:
@Aspect
注解用于标识或者描述AOP中的切面类型@Pointcut
注解用于描述切面中的方法@Around
注解用于描述切面中方法ProceedingJoinPoint类
为一个连接点类型启动项目测试或者进行单元测试,其中Spring Boot项目中的单元测试代码如下:
@SpringBootTest
public class AopTests {
@Autowired
private SysUserService userService;
@Test
public void testSysUserService() {
PageObject<SysUserDeptVo> po=userService.findPageObjects("admin",1);
System.out.println("rowCount:"+po.getRowCount());
}
}
对于测试类中的 userService
对象而言,它有可能指向JDK代理,也有可能指向CGLIB代理,具体是什么类型的代理对象,要看application.yml配置文件中的配置.
在业务应用,AOP相关对象分析,如图-5所示:
图-5
假如目标对象有实现接口,则可以基于JDK为目标对象创建代理代理对象,然后为目标对象进行功能扩展,如图-6所示:
图-6
#spring
spring:
aop:
proxy-target-class: false
# true 默认 使用继承代理
# false 使用 组合代理(jdk代理)
假如目标对象没有实现接口,可以基于CGLIB代理方式为目标织入功能扩展,如图-7所示:
图-7
说明:目标对象实现了接口也可以基于CGLIB为目标对象创建代理对象。
在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知,它们分别是:
说明:所有的通知,在切面对象中都会有对应的方法,这些方法会使用特定注解进行描述,不同注解描述的方法,它的执行时间点是不一样的
假如这些通知全部写到一个切面对象中,其执行顺序及过程,如图-8所示:
图-8
另外
如果 @Round 中如果没有 try catch ,在出现异常后,会不会回到 Round 切面
代码实践分析如下:
@Service @Aspect public class SysTimeAspect { @Pointcut("bean(sysUserServiceImpl)") public void doTime(){} @Before("doTime()") public void doBefore(JoinPoint jp){ System.out.println("time doBefore()"); → 可以做:参数校验 } 核心业务出现异常时执行 说明:假如有after,先执行after,再执行Throwing (类似 finally ) ↓ @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 (类似 finally ) ↓ @AfterThrowing("doTime()") public void doAfterThrowing(){ System.out.println("time doAfterThrowing"); } @Around("doTime()") public Object doAround(ProceedingJoinPoint jp) throws Throwable{ System.out.println("doAround.before"); Object obj=jp.proceed(); System.out.println("doAround.after"); return obj; } }
说明:对于 @AfterThrowing
通知只有在出现异常时才会执行,所以当做一些异常监控时可在此方法中进行代码实现。
课堂练习:定义一个异常监控切面,对目标页面方法进行异常监控,并以日志形式输出异常信息?
package com.cy.pj.common.aspect; import lombok.extern.slf4j.Slf4j; @Slf4j @Aspect @Component public class SysExceptionAspect { 异常通知: jp 连接点对象(指向了正在执行的 目标方法) e 此变量 用于接收执行目标方法出现的异常 ↓ @AfterThrowing(pointcut="bean(*ServiceImpl)",throwing = "e") public void doHandleException(JoinPoint jp,Exception e) { MethodSignature ms=(MethodSignature)jp.getSignature(); log.error("{}'exception msg is {}",ms.getName(),e.getMessage()); } 监控:监控什么? 1 cpu、磁盘、内存、网络 2 Tomcat(线程)、JVM 3 应用(service,...) // 作业 将异常日志信息,写入到日志文件 (扩展:播放音频,发送短信,发送email-java) }
Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
表-1 Spring AOP 中切入点表达式说明
指示符 | 作用 |
---|---|
bean | 用于匹配指定 bean 对象的 所有方法 |
within | 用于匹配指定 包 下所有类内的 所有方法 |
execution | 用于按指定语法规则 匹配到具体方法 (具体) |
@annotation | 用于匹配 指定注解修饰的方法 (具体) |
bean
表达式一般应用于类级别,实现粗粒度的切入点定义
案例分析:
bean("userServiceImpl")
- 指定一个userServiceImpl类中所有方法。bean("*ServiceImpl")
- 指定所有后缀为ServiceImpl的类中所有方法。说明:
bean 表达式内部的对象是由 spring 容器管理的一个 bean 对象,
表达式内部的内部的名字应该是 spring 容器中某个 bean 的 name 。
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within("aop.service.UserServiceImpl")
指定当前包中这个类内部的所有方法。within("aop.service.*")
指定当前目录下的所有类的所有方法。within("aop.service..*")
指定当前目录以及子目录中类的所有方法。execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
execution(void aop.service.UserServiceImpl.addUser())
- 匹配addUser方法。execution(void aop.service.PersonServiceImpl.addUser(String))
- 方法参数必须为String的addUser方法。execution(* aop.service..*.*(..))
- 万能配置。(..)
- 任意参数
@annotaion
表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
@annotation(anno.RequiredLog)
匹配有此注解描述的方法。@annotation(anno.RequiredCache)
匹配有此注解描述的方法。其中:RequestLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行扩展操作。
定义一 Cache相关切面
使用注解表达式定义切入点,并使用此注解对需要使用cache的业务方法进行描述,
代码分析如下:
第一步:定义注解 RequiredCache
package com.cy.pj.common.annotation ;
**
* 自定义注解,一个特殊的类,所有注解都默认继承Annotation接口
*
@Retention(RetentionPolicy.RUNTIME) <------ 运行时生效
@Target(ElementType.METHOD) <------ 可以描述 方法
public @interface RequiredCache {
//...
}
第二步:定义SysCacheAspect切面对象。
package com.cy.pj.common.aspect; @Aspect @Component public class SysCacheAspect { // @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)") // public void doCache() {} // @Around("doCache()") ↑ 当方法比较多,上面这种方法比较方便 @Around("@annotation(com.cy.pj.common.annotation.RequiredCache)") public Object around(ProceedingJoinPoint jp)throws Throwable{ System.out.println("Get data from cache"); Object obj = cacheMap.get(jp.getSignature().getName()); ← 获取缓存 if(obj!=null) ← 有缓存,直接返回 return obj; Object obj=jp.proceed(); ← 没有缓存到这里... 这里从数据库获取数据 cacheMap.put(jp.getSignature().getName() , obj ); ← 数据加入缓存 System.out.println("Put data to cache"); return obj; } 这里不用HashMap , 因为线程不安全 ↓ private Map<<String,Object> cacheMap = new ConcurrentHashMap<>(); }
第三步:使用 @RequiredCache
注解对特定业务目标对象中的查询方法进行描述。
@RequiredCache
@Override
public List<Map<String, Object>> findObjects() {
….
return list;
}
切面的优先级需要借助 @Order
注解进行描述,数字越小优先级越高,默认优先级比较低。
例如:
定义日志切面并指定优先级。
@Order(1)
@Aspect
@Component
public class SysLogAspect {
…
}
定义缓存切面并指定优先级:
@Order(2)
@Aspect
@Component
public class SysCacheAspect {
…
}
说明:
当多个切面作用于同一个目标对象方法时,这些切面会构建成一个切面链,类似过滤器链、拦截器链
其执行分析如图-9所示:
图-9
Spring 基于AspectJ框架实现AOP设计的关键对象概览,如图-10所示:
图-10
本小节作为课堂练习,以 AOP
方式记录项目中的用户行为信息,并将其存储到数据库。
package com.edut.springboot.tarena.common.aspect; import java.lang.reflect.Method; import java.util.Arrays; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.edut.springboot.tarena.common.annotation.RequiredLog; import com.edut.springboot.tarena.dao.SysLogDao; import com.edut.springboot.tarena.pojo.SysLog; import com.edut.springboot.tarena.service.SysLogService; import lombok.extern.slf4j.Slf4j; /** * @Aspect 注解描述的类型为一个 AOP 应用中的切面类型,此类的构成: * 1)切入点 (在哪些方法执行时,我们要植入扩展功能):例如所有地铁站的入口 * 2)通知 (要植入的扩展功能):例如安检 * * @author Administrator * */ @Aspect @Component @Slf4j public class SysLogAspect { @Autowired private SysLogService sysLogService ; //sysLogServiceImpl @Pointcut("bean(sysUserServiceImpl)") public void joinPoint() {} ; @Around("joinPoint()") public Object around(ProceedingJoinPoint pj ) throws Throwable { try { long start = System.currentTimeMillis(); Object result = pj.proceed(); long end = System.currentTimeMillis() ; //写正常日志 saveLog(pj , end-start) ; return result ; } catch (Throwable e) { //写异常日志 log.error(e.getMessage()); throw e; } } /*将用户行为信息写入到数据库 saveLog */ private void saveLog(ProceedingJoinPoint pj, long time) throws NoSuchMethodException, SecurityException { // 1.获取行为日志(借助 连接点 对象 - JoinPoint) MethodSignature signature =(MethodSignature) pj.getSignature(); // (MethodSignature) : 提供了参数的 字符串到类型的转换 Class<?> clazz = pj.getTarget().getClass(); String ip = "192.168.0.1"; String username = "GBK1910"; //1.3 获取目标方法实际参数 String params = Arrays.toString(pj.getArgs()); //1.4 获取操作名称(由此注解RequiredLog指定) //1.4.1 获取目标方法 Method method = clazz.getDeclaredMethod(signature.getName() , signature.getParameterTypes()); String methodName =clazz.getName() +"."+ method.getName(); /* * 老师加了判断。。。。 */ String operation ="operation" ; RequiredLog annotation = method.getAnnotation(RequiredLog.class); if(annotation!=null) { operation = annotation.operation() ; } //2. 封装为日志 SysLog entity = new SysLog() .setIp(ip) .setUsername(username) .setMethod(methodName) .setParams(params) .setOperation(operation) .setTime(time); //3.写日志 sysLogService.saveObject(entity); } }
事务(Transaction)是一个业务,是一个不可分割的逻辑工作单元,基于事务可以更好的保证业务的正确性。
事务具备ACID特性,分别是:
Atomicity
):一个事务中的多个操作要么都成功要么都失败。Consistency
):(例如存钱操作,存之前和存之后的总钱数应该是一致的。Isolation
):事务与事务应该是相互隔离的。Durability
):事务一旦提交,数据要持久保存。说明:
目前市场上在事务一致性方面,通常会做一定的优化,
比方说只要最终一致就可以了,这样的事务我们通常会称之为柔性事务(只要最终一致就可以了).
现有两个订单业务操作,在下单时需要执行更新库存。
当库存充足时
如图-11所示:
图-11
当库存不足时,如图12所示:
图-12
Spring 提供了两种事务管理方式:
- 编程式事务指的是 通过编码方式实现事务; (显式声明/关闭事务)
声明式事务
基于 AOP,将具体业务逻辑与事务处理解耦
。
通过声明式事务管理可以更好使业务代码逻辑不受污染或少量污染,
因此在实际使用中声明式事务用的比较多。
Spring 声明式事务管理将开发者从繁复的事务管理代码中解脱出来,专注于业务逻辑的开发上,
这是一件可以被拿来顶礼膜拜的事情。
从具体配置实现上,Spring框架提供了两种配置方式:
- 一种是基于
xml
方式做配置实现,- 另一种是基于
@Transactional
注解进行配置实现。
SpringBoot
项目,其内部提供了事务的自动配置机制,
当我们使用spring-boot-starter-jdbc
或spring-boot-starter-data-jpa
依赖的时候,
框架会自动为我们的项目注入DataSourceTransactionManager
或JpaTransactionManager
。
这两个事务管理器都实现了Spring中提供的 PlatformTransactionManager
接口,它是Spring的事务核心接口。
本小节重点讲解实际项目中最常用的声明式事务管理,
以注解 @Transactional
配置方式为例,进行实践分析。
基于
@Transactional
注解进行声明式事务管理的实现步骤分为两步:
启用声明式事务管理,在配置类上添加,新版本中也可不添加@EnableTransactionManagement
(例如新版Spring Boot项目)。- 将
@Transactional
注解添加到合适的业务类或方法上,并设置合适的属性信息。
== 其 【业务类中】 代码示例如下: == @Transactional(timeout = 30, readOnly = false, isolation = Isolation.READ_COMMITTED, rollbackFor = Throwable.class, propagation = Propagation.REQUIRED) @Service public class SysUserServiceImpl implements SysUserService { @Transactional(readOnly = true) @Override public PageObject<SysUserDeptVo> findPageObjects( String username, Integer pageCurrent) { … } }
其中,代码中的@Transactional注解用于描述类或方法,告诉spring框架我们要在此类的方法执行时进行事务控制
其具体说明如下:
@Transactional
注解应用在类上时表示类中所有方法启动事务管理,并且一般用于事务共性的定义。@Transactional
描述方法时表示此方法要进行事务管理,假如类和方法上都有@Transactional注解,则方法上的注解一般用于事务特性的定义。[ @Transactional
常用属性应用说明 ]:
timeout
事务的超时时间,默认值为-1,表示没有超时显示。read-only
指定事务是否为只读事务,默认值为 false;rollback-for
用于指定能够触发事务回滚的异常类型no-rollback- for
抛出 no-rollback-for 指定的异常类型,不回滚事务。isolation
事务的隔离级别,默认值采用 DEFAULT
。 建议READ_COMMITTED
(可以避免脏读)可重复读- 锁行
可序列化 - 锁表
Spring 中事务控制过程分析,如图-13所示:
图-13
事务传播( Propagation
)特性指"不同业务(service)对象"中的事务方法之间相互调用时,
事务的传播方式,如图-14所示:
图-14
.
其中,常用事务传播方式如下: ( 默认)
@Transactional(propagation=Propagation.REQUIRED)
如果没有事务创建新事务, 如果当前有事务参与当前事务,
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:
.
Service1#method1()
⇒Service2#method2()
⇒Service3#method3()
.
那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
.
如图-15所示:
图-15
代码示例如下:
@Transactional(propagation = Propagation.REQUIRED)
@Override
public List<Node> findZtreeMenuNodes() {
return sysMenuDao.findZtreeMenuNodes();
}
.
@Transactional(propagation=Propagation.REQUIRES_NEW)
。
图-16
代码示例如下:
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void saveObject(SysLog entity) {
sysLogDao.insertObject(entity);
}
Spring 事务管理是基于接口代理(JDK)或动态字节码(CGLIB)技术,然后通过 AOP 实施事务增强的。
以 DataSourceTransactionManager
为例
DataSourceTransactionManager
中,在事务开始的时候,会调用 doBegin
方法,首先会得到相对应的 Connection
Connection
的相关属性进行配置,比如将 Connection
的 autoCommit
功能关闭等。Spring 声明式事务是 Spring 最核心
最常用的功能。
由于 Spring 通过 IOC
和 AOP
的功能非常透明地实现了声明式事务的功能
对于一般的开发者基本上无须了解 Spring
声明式事务的内部细节,仅需要懂得如何配置就可以了。
但对于中高端开发者还需要了解其内部机制。
在开发系统的过程中,通常会考虑到系统的性能问题,
提升系统性能的一个重要思想就是“串行”改“ 并行
”。
说起“并行”自然离不开“异步”,今天我们就来聊聊如何使用 Spring
的 @Async
的异步注解。
NIO
在基于注解方式的配置中,借助 @EnableAsync
注解进行异步启动声明
Spring Boot版的项目中,代码示例如下:
@EnableAsync <------- spring容器启动时会创建线程池 (系统底层会进行异步的自动配置)
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#server server: port: 80 servlet: context-path: / tomcat: uri-encoding: utf-8 max-threads: 1000 min-spare-threads: 100 # spring spring: aop: proxy-target-class: false # task: execution: pool: core-size: 3 # 推荐 CPU核数+1 max-size: 5 # 最大吞吐量 = max-size+queue-capacity . 超了报异常 queue-capacity: 100 #默认100多个亿 INTEGER.MAX_VALUE keep-alive: 30 #30s后 释放 多余 core-size 的线程 allow-core-thread-timeout: false #默认false 不释放core-size thread-name-prefix: spring.async.task- # 默认 task- 默认配置 ctrl+点击 ↑ ----- 在 TaskExecutionProperties 类中看
在需要异步执行的业务方法上,使用 @Async
方法进行异步声明。
service中
核心线程,最大线程,任务队列,线程名称,拒绝策略,任务执行时出现异常
↓
@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) {}
}
假如需要获取业务层异步方法的执行结果,可参考如下代码设计进行实现:
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
@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
方法获取结果。
说明:
.
对于@Async
注解默认会基于ThreadPoolTaskExecutor
对象获取工作线程,
然后调用由@Async
描述的方法,让方法运行于一个工作线程,以实现异步操作。
但是假如系统中的默认拒绝处理策略,
.
任务执行过程的异常处理不能满足我们自身业务需求的话,
.
我可以对异步线程池进行自定义.
( SpringBoot
中默认的异步配置可以参考自动配置对象 TaskExecutionAutoConfiguration
).
为了让 Spring
中的异步池更好的服务于我们的业务,同时也尽量避免 OOM
(Out Of Memory),
可以自定义线程池优化设计如下:
关键代码如下:
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); //线程安全的 Integer @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("当前任务线程池队列已满."); }); // 上面 lambda 表达式 等于 ↓ // executor.setRejectedExecutionHandler(new RejectedExecutionHandler() { // // @Override // public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 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
池对象中的线程执行异步任务。
自动配置原理
执行流程
name
匹配 - 》 key
生成 - 原则
key 表达式
在业务方法中我们可能调用数据层方法获取数据库中数据,假如访问数据的频率比较为高,为了提高的查询效率,降低数据库的访问压力,可以在业务层对数据进行缓存.
在项目( SpringBoot
项目)的启动类上添加 @EnableCaching
注解,以启动缓存配置。关键代码如下:
package com.cy;
/**
* 异步的自动配置生效).
* @EnableCaching 注解表示启动缓存配置
*/
@EnableCaching // 启动缓存配置
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Cacheable
注解对方法进行相关描述.表示方法的@CacheEvict
注解对方法进行描述。例如:
第一步:在用户模块查询相关业务方法中,使用缓存,关键代码如下:
将查询到的数据存储到cache 中, value值menuCache为具体cache对象 - [别名:cacheNames] ↓ @Cacheable(value = "userCache") SinpleKey存储实际参数的值,将这些值的组合作为key <-------- value 指定 cache 的名称 @Transactional(readOnly = true) @Override public Map<String,Object> findObjectById(Integer id) { 问题1:menuCache为谁? -> 方法的返回值 问题2:manuCache中存储数据时使用的key ? .... }
其中
value
属性的值表示要使用的缓存对象,名字自己指定
其中底层为一个 map
对象
当向 cache
中添加数据时, key
默认为方法实际参数的组合。
第二步:在用户模块更新时,清除指定缓存数据,关键代码如下:
@CacheEvict( value="userCache", <--- 清理的cache
allEntries=true, < ----- 清除所有元素
beforeInvocation=false) < ---- 在调用前 true /后 false [invocation /ˌɪnvəˈkeɪʃn 调用
// @CacheEvict(value="userCache",key = "#entity.id") <------ key 指定元素
@Override
public int updateObject(SysUser entity, Integer[] roleIds) {}
其中,key表示要清除记录时,使用的具体key值是什么( #entity.id
表示按照参数对象中entity的id值进行缓存记录清除)。
说明:spring中的缓存应用原理,如下图所示:
如何查看上面的直观看到 - 上面 - 运行流程?
C:\Users\Administrator.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.2.RELEASE\spring-boot-autoconfigure-2.2.2.RELEASE.jar 中
spring.factories
在 Spring
中默认 cache
底层实现是一个 Map
对象,假如此 map
对象不能满足我们实际需要,
在实际项目中我们可以将数据存储到第三方缓存系统中.
Spring
整合 AspectJ
框架实现 AOP
只是 Spring
框架中 AOP
的一种实现方式,此方式相对比较简单,实现方便。
但此方式底层还是要转换为 Spring
原生 AOP
的实现, Spring AOP
原生方式实现的核心有三大部分构成,分别是:
本小节以Spring中一种原生AOP架构的基本实现为例进行原理分析和说明,其简易架构如图-16所示:
图-16
其中 DefaultAdvisorAutoProxyCreator
这个类功能更为强大,这个类的奇妙之处是他实现了 BeanProcessor
接口,
当 ApplicationContext
读如所有的 Bean
配置信息后,这个类将扫描上下文,寻找所有的 Advistor
(一个 Advisor
是一个切入点和一个通知的组成),将这些 Advisor
应用到所有符合切入点的 Bean
中。
创建Spring Boot项目,并基于Spring原生AOP的实现为特定业务对象添加简易日志实现。
定义RequiredLog注解,用于描述目标业务对象
package com.cy.spring.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 {
}
定义搜索业务接口,用于定义搜索业务规范
package com.cy.spring.aop;
public interface SearchService {
Object search(String key);
}
定义搜索业务接口实现,并使用requiredLog注解描述
package com.cy.spring.aop;
import org.springframework.stereotype.Service;
import com.cy.spring.annotation.RequiredLog;
@Service
public class DefaultSearchService implements SearchService {
@RequiredLog <----------- 注解
@Override
public Object search(String key) {
System.out.println("search by "+key);
return null;
}
}
定义LogAdvice对象,基于此对象为目标业务对象做日志增强。
切面:类似 LogAspect 中的 @Around
package com.cy.spring.advisor; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /**Advice对象:用于为目标方法添加日志操作*/ public class LogAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("start:"+System.currentTimeMillis()); Object result=invocation.proceed(); System.out.println("after:"+System.currentTimeMillis()); return result; } }
其中, MethodInterceptor
对象继承 Advice
对象,基于此对象方法可以对目标方法进行拦截。
创建日志 Advisor
对象,在对象内部定义要切入扩展功能的点以及要应用的通知(Advice)对象。
切点配置
package com.cy.spring.advisor; import java.lang.reflect.Method; import org.springframework.stereotype.Component; import com.cy.spring.annotation.RequiredLog; //Advisor @Component <------交给spring管理!! public class LogAdvisor extends StaticMethodMatcherPointcutAdvisor { private static final long serialVersionUID = 7022316764822635205L; public LogMethodMatcher() { 注册:在特定切入点上要执行的通知 ↓ setAdvice(new LogAdvice()); } //Pointcut //方法返回值为true时,则可以为目标方法对象创建代理对象 @Override public boolean matches(Method method,Class<?> targetClass) { try { Method targetMethod=targetClass.getMethod(method.getName(), method.getParameterTypes()); return targetMethod.isAnnotationPresent(RequiredLog.class); <----检测有没有注解 }catch(Exception e) { return false; } } }
其中,StaticMethodMatcherPointcutAdvisor类为Spring框架中定义的一种Advisor,我们自己写的Advisor可以直接继承此类进行资源整合。
基于Spring boot项目进行单元测试:
package com.cy; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.cy.spring.aop.SearchService; @SpringBootTest public class CgbSbootAop01ApplicationTests { @Autowired private SearchService searchService; @Test public void testSearch() { //System.out.println(searchService); searchService.search("tedu"); } }
说明:在spring 框架中,很多功能都是原生AOP进行了功能的扩展和实现。
AOP 是什么,解决了什么问题,实现原理,应用场景。
AOP 编程基本步骤及实现过程(以基于AspectJ框架实现为例)。
AOP 编程中的核心对象及应用关系。
AOP 思想在Spring中的实现原理分析。
AOP 编程中基于注解注解方式的配置实现。
AOP 编程中基于注解方式的事务控制。
AOP 编程中异步操作的实现?
AOP 编程中的缓存应用?
OCP
原则(开闭原则)?DIP
原则 (依赖倒置)?SRP
)?AOP
?Spring
中 AOP
的有哪些配置方式?Spring
中AOP 的通知有哪些基本类型?AOP
是如何为Bean对象创建代理对象的 ?Code Generator Library
)Spring
中基于 AOP
编程时创建的代理对象是何时创建 ?Spring
中 AOP
切面的执行顺序如何指定?@Order
注解进行描述Spring
中基于 AOP
实现的声明式事务控制原理是怎样的?Spring
单体架构项目中事务的控制要通过Connection对象实现?切入点应用错误,如图-17所示:
图-17
问题分析:检查切入点的引入是否丢掉了"()".
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。