概述
AOP(Aspect Orient Programming)是一种设计思想 ,是软件设计领域中面向切面的编程,它是面向对象(OOP)编程的一种补充和完善 它可以通过通过预编译方式和运行期动态代理的方式,实现在不修改源代码的情况下给程序动态添加额外功能的程序
什么是Aop
公式 :AOP = 通知方法 + 切入点表达式
说明 :当程序的执行满足aop表达式的时候,程序的代码会发生变化
场景: 日志记录 监控程序执行的时间 实现事物的控制 异常监控 缓存处理
特点 : 公共的业务
启动依赖
spring boot 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
原理分析
spring Aop 底层给予代理机制实现功能扩展:
- 假如目标对象(就是需要增强功能的对象 类)实现接口,则底层可以采用jdk动态代理机制为目标对象创建代理对象($Proxy33… )
- 假如目标对象(被代理对象) 没有实现接口 则底层采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理对象会继承目标对象的类型)
spring中默认使用CGLIB代理 假如需要jdk动态代理 可以在配置文件中进行如下配置
spring.aop.proxy-target-class=false
AOP相关术语分析
切面(Aspect):该注解描述的类型为spring AOP中切面对象类型,用于定义一个切面对象,此对象类型可以封装:
a)切入点
b)通知
切入点(pointcut):注解用于定义切入点,具体方式可以基于特定表达式实现。例如:
- bean 为一种切入点表达式
- bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法。 - sysUserServiceImple 为spring容器中的一个bean对象的名字(首字母小写) 当这个类中的任一方法执行时,都由本切面的通知方法进行功能增强
//注释方式的切入点表达式
@Pointcut("bean(sysUserServiceImpl)")
public void doLongPointCut(){};
@annotation 用于匹配具体方法的注释
com.cy.pj.common.annotation.RequiredCache 为注解的全路径
@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")
public void doLongPointCut(){};
execution(返回值类型 包名.类名.方法名(参数列表))
@Pointcut("execution(int com.cy.pj.add(int))")
public void doLongPointCut(){};
// ..代表 任意参数类型 * 代表任意返回值类型
@Pointcut("execution(* com.cy.pj.add(..))")
public void doLongPointCut(){};
//返回值类型任意 并位于 com.cy.pj的所有子包中的所有类的所有方法
@Pointcut("execution(* com.cy.pj..*.*(..))")
public void doLongPointCut(){};
扩展 自定义注解
修饰符 @Interface
/**
* 自定义注解:(使用@interface定义注解,默认所有注解都是一个Annotation类型的对象)
* @Target 注解用于告诉jdk我们自己写的注解可以描述的对象。
* @Retention 注解用于告诉JDK我们自己写的注解何时有效。
* 说明:所有的注解都是一种元素据(Meta Data)-一种描述数据的数据(例如使用注解描述类,
* 描述方法,描述属性,描述方法参数等等)
*/
@Retention(RetentionPolicy.RUNTIME) //运行时有效
@Target(ElementType.METHOD) //可以修饰方法
public @interface RequiredCache {
String value() default "operation"; //定义注解中的属性 加上default就表明此值用户可以指定 也可以不用指定
}
连接点(joinpoint)
程序执行中某个特定的点,一般指被拦截到的方法,
- ProceedingJoinPoint 一般用于@Around方法中的参数
- JoinPoint 用于别的注解方法中
可以通过 该对象调用proceed()方法 表示执行 目标方法
Signature 封装了连接点信息的api 接口 MethodSignature 是该接口的一个实现类
@Around("doLongPointCut()")
public Object around(ProceedingJoinPoint jp){
//获取目标方法的类名
String methodName=jp.getSignature().getDeclaringTypeName();
//获取目标方法参数 返回值为一个Object 数组
Object[] objs=jp.getArgs();
Objecet result =jp.proceed();//表示执行被增强的方法 返回值为Object类型
//获取目标对象(要执行的那个目标业务对象)的类 ,返回值为Class<?> 基于此通过反射可以获取信息
Class<?> targetClass=jp.getTarget().getClass();
//获取方法签名,基于此签名 可以获取目标方法的方法名称 ,封装了要执行目标方法的信息
MethodSignature ms=(MethodSignature)jp.getSignature();
//获取方法的返回值类型
Class returnClass = ms.getReturnType();
//通过反射获取目标方法对象 基于此对象 获取方法上的注解,进而取到操作名
//方法名 通过方法签名获取 方法类型也是
//返回值为方法 Method
Method tartgetMethod=targetClass.getMethod(ms.getName(),ms.getParameterTypes());
//通过方法对象 获取该方法上的指定注解 假设注解为RequestMapping
RequestMapping re=targetMethod.getAnnotation(RequestMapping.class);
//通过注解对象 获取里面的值
re.value();
...
}
@Before("doLongPointCut()")
public void before(JoinPoint jp){
...
}
//通过连接点 获取目标方法上的信息 如果注解类名 为@RequiredCache
//这样写的意思 是 拦截名字为aaa的类型的注解描述的方法 注意 名字自己起但是必须相同(aaa)
@Arount(@annotation(aaa))
public Object around(ProceedingJoinPoint jp , RequiredCache aaa){
// 通过注解类型 aaa 获取 注解中的值
aaa.value();
}
案例:redis +aop 实现缓存
//=====================aop切面
@Autowired(required = false) //告知spring容器 ,该注入不是必需的,如果该对象被程序调用时才会被注入只
private Jedis jedis;
@Around("@annotation(cacheTree)")
public Object befor(ProceedingJoinPoint jp,CacheTree cacheTree) throws Throwable {
Object result = null;
ObjectMapper json = new ObjectMapper();
//获取方法参数 返回值为一个Object 数组
Object[] objs = jp.getArgs();
String key = cacheTree.key() +"::"+objs[0];
if (jedis.exists(key)) {
String treecontext = jedis.get(key);
System.out.println("缓存");
MethodSignature ms = (MethodSignature) jp.getSignature();
//获取方法的返回值类型
Class returnType = ms.getReturnType();
result = json.readValue(treecontext, returnType);
return tree;
}else {
Object result = jp.proceed();
if (cacheTree.seconds()>0){
jedis.setex(key, cacheTree.seconds(), ObjectMapperUtil.toJson(result));
}else{
jedis.set(key, ObjectMapperUtil.toJson(result));
}
return result;
}
}
//================================== 目标方法
@Override
@CacheTree(key="ITEM_CAT_LIST")//使用注解自定义前缀
public List<EasyUITree> findItemCatByParentId(Long parentId) {
System.out.println("查询");
QueryWrapper<ItemCat> queryWrapper =new QueryWrapper<>();
queryWrapper.eq("parent_Id", parentId);
List<ItemCat> selectList = catMapper.selectList(queryWrapper);
List<EasyUITree> tree =new ArrayList<>();
for (ItemCat itemcat : selectList) {
Long id = itemcat.getId();
String text = itemcat.getName();
String state = itemcat.getIsParent() ? "closed" : "open";
tree.add(new EasyUITree(id, text, state));
}
return tree;
}
//=========================================注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheTree {
//设定key
String key();
//超时时间
int seconds() default -1; //加上defailt 用户可写可不写
}
通知(Advice)
在切面的某个特定的连接点上 执行的动作
@Around 环绕通知 目标方法执行前后都要执行的方法(重点掌握) 可以影响程序的执行轨迹
- 优先级最高, 方法返回值为Object
- 方法参数 为ProcedingingJoinPoint类型
- 方法抛出异常 Throwable
- 可以在目标方法执行之前或者之后进行执行 如果是之前执行 优先级比@Before还要高,优先执行。
1.@Before 前置通知 在目标方法执行之前执行
2.@AfterReturning 后置通知 在目标方法执行之后执行
3.@AfterThrowing 异常通知 抛出异常时执行
4.@After 最终通知 无论用户发生了什么变化 都执行的操作 最后执行
Aop 缓存(自己定义的一个小Map)示例
@Aspect
@Component
public class SysCacheAspect {
@Autowired
private SimpleCache simpleCache;
/**
* @annotation(..)为注解方式的切入点表达式,这里表示由RequiredCache注解描述
* 的方法为切入点方法。
*/
@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")
public void doCachePointCut() {}
//@Pointcut("execution(* com.cy.pj.sys.service..*.updateObject(com.cy.pj.sys.pojo.SysDept))")
@Pointcut("@annotation(com.cy.pj.common.annotation.ClearCache)")
public void doClearCachePointCut() {}
@AfterReturning("doClearCachePointCut()")
public void doAfterReturning() {//目标方法正常结束以后执行
System.out.println("==clear cache==");
simpleCache.clearObject();
}
// @AfterReturning("doClearCachePointCut()")
// public Object doAroundClearCache(ProceedingJoinPoint jp)throws Throwable{
// Object result=jp.proceed();
// simpleCache.clearObject();
// return result;
// }
@Around("doCachePointCut()")
public Object around(ProceedingJoinPoint jp)throws Throwable{
System.out.println("Get data from cache");
Object obj=simpleCache.getObject("deptCache");//这个key的名字先自己写个固定值
if(obj!=null){
return obj;
}
Object result=jp.proceed();//最终要执行目标方法
System.out.println("put data to cache");
simpleCache.putObject("deptCache", result);
return result;
}
}
spring AOP中 Cache操作实现
启动缓存配置
这里的缓存是AOP自带的 需要在springboot 的启动类上 添加@EnableCaching注解,就可以启动缓存配置
/**
* 异步的自动配置生效).
* @EnableCaching 注解表示启动缓存配置
*/
@EnableCaching
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
在具体方法上 应用缓存配置
在需要进行缓存的方法上通过@Cacheable注解进行描述,表示要将方法的返回值存储到Cache中
//缓存中的数据结构 是key value
//@Cacheable 表示要将该方法的返回值List<Map<String,Object>> 存储到缓存中的value
// 这里的deptCache 将作为缓存中的key
@Cacheable(value = "deptCache")
@Transactional(readOnly = true)
public List<Map<String,Object>> findObjects() {
....
}
在需要清除缓存的方法上 加注解@CacheEvict
//@CacheEvict 注解描述的方法 表示要清除缓存
//value 表示要清除的缓存的名字 key
//allEntries 表示要清除缓存对象中的那些属性 true为所有
// beforeInvocation 表示要在方法执行前清除 还是在执行后清除 true为在前
@CacheEvict(value = "menuCache",allEntries = true,beforeInvocation = false)
@Override
public int saveObject(SysDept entity) {...}
Aop 异步操作实现
1. 启动异步配置
基于注解的方式进行配置,在springboot 的启动类上加上@EnableAsync注解 表示 启动线程池
@EnableAsync //spring容器启动时会创建线程池
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.在需要异步操作的业务方法上加@Async注解
为了提高程序的效率 ,可以将一些不需要同步更新的数据的写入数据库的操作来异步执行,从而提高项目的效率
@Async //使用此注解描述的方法会运行在由spring框架提供的一个线程中。
@Override
public void saveObject(SysLog entity) {
String tName=Thread.currentThread().getName();
System.out.println("SysLogServiceImpl.saveObject.thread.name="+tName);
try{Thread.sleep(5000);}catch(Exception e) {}
sysLogDao.insertObject(entity);
}
异步执行的方法 如果需要返回值 可以进行如下修改:
AsyncResult对象可以对异步方法的执行结果进行封装,假如外界需要异步方法结果时,可以通过Future对象的get方法获取结果。
@Async //使用此注解描述的方法会运行在由spring框架提供的一个线程中。
@Override
public Futrue<Integer> saveObject(SysLog entity) {
String tName=Thread.currentThread().getName();
System.out.println("SysLogServiceImpl.saveObject.thread.name="+tName);
try{Thread.sleep(5000);}catch(Exception e) {}
int rows=sysLogDao.insertObject(entity);
return new AsyncResult<Integer>(rows);
}
Spring Aop事务处理
事物的定义
事务(Transaction)是一个业务,是一个不可分割的逻辑工作单元,基于事务可以更好的保证业务的正确性。
事物的特性:
- 原子性(Atomicity):一个事务中的多个操作要么都成功要么都失败
- 一致性(Consistency): (例如存钱操作,存之前和存之后的总钱数应该是一致的。
- 隔离性(Isolation):事务与事务应该是相互隔离的
- 持久性(Durability):事务一旦提交,数据要持久保存。
1. 启动事物
1.在启动类上 加上注解@EnableTransactionManagement,新版本中也可以不用添加。
2.将@Transactional注解 ,并设置合适的属性信息
@Transactional(//事物的超时时间 默认值为-1 表示没有超时限制,如果设定了时间,超过具体时间还没有完成则自动回滚事物
timeout = 30,
//指定事物为只读事物,默认值为false:设置为true时表示为只读,只能读取数据,不能修改 删除等业务
readOnly = false,
//issolation 表示事物的隔离级别
isolation = Isolation.READ_COMMITTED,
// rollbackFor 指定事物能够触发回滚的异常类型
//no-rollbackFor 抛出指定异常的类型 不用回滚事物
rollbackFor = Throwable.class,
//propagation=Propagation.REQUIRES_NEW 表示为新事物 挂起当前事物,开启新的事物
//表示新的事物参与当前事物
propagation = Propagation.REQUIRED
)
@Service
public class SysUserServiceImpl implements SysUserService {
@Transactional(readOnly = true)
@Override
public PageObject<SysUserDeptVo> findPageObjects(
String username, Integer pageCurrent) {
…
}
}
缓存穿透:
特点:
- 用户 在高并发环境下 访问数据库中根本不存在的数据 缓存中没有 会一直查询数据库 可能发生宕机的风险
解决方案:
- 通过API网关解决
- 限定ip的访问次数 限定ip每秒的次数
缓存击穿:
特点
- 由于用户高并发访问 ,访问的数据 刚开始有缓存,但是由于特殊原因,导致(少量的)缓存失效 ,也会一直查询数据库,造成宕机风险。 只影响一段时间 例如:redis 的缓存设定时间正好清除 恰好又来了访问
解决方法:
- 通过缓存(redis)串联的方式解决 造成时间差 不会让缓存 同时消失
缓存雪崩:
特点:
- 高并发的环境下 ,大量用户访问服务器,redis中有大量的数据在同一时间超时(删除)**。导致用户直接访问数据库
解决方案:
- 通过缓存(redis)串联的方式解决 造成时间差 不会让缓存 同时消失
Redis 持久化问题
问题说明
Redis中的数据都保存在 内存中,如果服务关闭或者道济,则内存资源直接丢失,导致缓存失效
RDB模式
特点:
- 采用定期持久化的方式(例如:每隔10持久化一次) 可能会丢数据
- 记录的是当前redis的内存记录快照 ,只记录当前状态 (快照只有一张)持久化效率最高
- redis的默认持久化方式
持久化命令
1. save : 立即持久化 同步操作 可能对现有的操作 造成堵塞
2. bgsave : 异步操作 开启单独的线程 实现持久化任务 不能保证执行的时间
Aof 模式
特点
- 默认关闭状态
- 能够记录程序的执行过程,可以实现数据的实时持久化 AOF文件占用的空间较大,回复的速度较慢
- AOF模式开启之后 RDB模式将不会再生效
如何选择 持久化方式
如果允许少量数据丢失 选择 RDB
如果不允许 数据丢失 选择 AOP
redis中如果 误操作了flushAll操作 如果持久化 命令是aof
解决方法 : 快速停止redis 修改aof文件 将flushAll 删除 重启redis
redis 内存的优化
修改redis的内存
场景说明
1.redis 运行的空间是内存,内存资源比较紧缺,所以,应该维护ridis的内存数据 ,让redis保存 热门数据