AOP简单介绍
名称: 面向切面编程
作用: 降低系统中代码的耦合性,并且在不改变原有代码的条件下对原有的方法进行功能的扩展.
公式: AOP = 切入点表达式 + 通知方法
通知类型
1.前置通知 目标方法执行之前执行
2.后置通知 目标方法执行之后执行
3.异常通知 目标方法执行过程中抛出异常时执行
4.最终通知 无论什么时候都要执行的通知
特点: 上述的四大通知类型 不能干预目标方法是否执行.一般用来做程序运行状态的记录.【监控】
5.环绕通知 在目标方法执行前后都要执行的通知方法 该方法可以控制目标方法是否运行.joinPoint.proceed(); 功能作为强大的.
切入点表达式
切入点表达式就是一个程序是否进入通知的一个判断(IF)。
作用:当程序运行过程中,满足了切入点表达式时才回去执行通知方法,实现业务的扩展。
种类(写法):
1.bean(bean的名称 bean的ID) 只能拦截具体的某个bean对象,只能匹配一个对象。
例如:bean("itemServiceImpl")
2.within(包名.类名) within("com.jt.service.*")表示可以匹配多个对象。
3.execution(返回值类型 包名.类名.方法名(参数列表))最为强大的用法
例如 : execution( com.jt.service...*(..))返回值类型任意 com.jt.service包下的所有的类的所有的方法都会被拦截.
4.@annotation(包名.注解名称) 按照注解匹配.
AOP入门案例
package com.jt.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //我是一个AOP切面类
@Component //将类交给spring容器管理
public class CacheAOP {
//公式: 切入点表达式+通知方法
/**
* 关于切入点表达式的使用说明
* 粗粒度
* 1.bean(bean的id) 一个类
* 2.within(包名.类名) 多个类
* 细粒度
* 1.
*/
//@Pointcut("bean(itemCatSercviceImpl)")
// .*表示当前包下面的一级目录。
// ..*表示当前包下面的多级目录
/*@Pointcut("within(com.jt.service..*)")
public void pointCut(){
//定义切入点表达式, 为了占位
}*/
//定义前置通知,与切入点进行绑定 @Before("pointCut()")
//或者用更简单的方法:不用定义切入点方法pointCut(),
// 直接绑定bean:@before(“bean(itemCatSercviceImpl)”)
/*两者的区别:
* 1.@Before("pointCut()")表示切入点表达式的引用,适用于多个通知同用切入点的情况
* 2.@before(“bean(itemCatSercviceImpl)”)适用于单个通知,不需要复用的
* */
/**@Before("pointCut()")
public void before(){
System.out.println("我是前置通知");
}
@AfterReturning("pointCut()")
public void afterreturn(){
System.out.println("我是后置通知");
}*/
@Before("pointCut()")
public void before(JoinPoint joinPoint){
//JoinPoint:连接点方法
//getStaticPart()代表连接点的方法签名
//getName()方法的名称
String methodName = joinPoint.getSignature().getName();
//getDeclaringTypeName()获取类的路径全名com.jt.service.ItemCatSercviceImpl
String className = joinPoint.getSignature().getDeclaringTypeName();
System.out.println(className+"."+methodName);
//getTarget()代表目标对象
//getTarget().getClass()获取目标对象的类型
Class<?> targetClass = joinPoint.getTarget().getClass();
//获取对象的参数
Object[] args = joinPoint.getArgs();
//获取执行时间
Long startTime= System.currentTimeMillis();
System.out.println("目标方法类型"+targetClass);
System.out.println("执行时间:"+startTime+"毫秒");
}
/**
* 环绕通知说明
* 注意事项:
* ProceedingJoinPoint只能用在环绕通知里
* 1.环绕通知必须添加参数ProceedingJoinPoint
* 2.ProceedingJoinPoint只能环绕通知使用
* 3.ProceedingJoinPoint如果当做参数则必须位于参数的第一位
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
System.out.println("环绕通知开始!!!");
Object result=null;
try {
result=joinPoint.proceed();//执行下一个通知或者目标方法
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("环绕通知结束!!!");
return result;
}
}
关于AOP实现Redis
自定义缓存注解
问题:如何控制,哪些方法需要使用缓存?【cacheFind()查询方法】
解决方案:采用自定义注解的形式进行定义,如果方法执行需要使用缓存,则标识注解即可。
关于注解的说明:
1.注解名称:cacheFind
2.属性参数:
1.key:应该由用户自己手动添加,一般添加业务名称之后动态拼接形成唯一的key。
2.seconds:用户可以指定数据的超时时间。
//自定义一个注解
@Target(ElementType.METHOD)//该注解只对方法有效
@Retention(RetentionPolicy.RUNTIME)//运行期有效
public @interface CacheFind {
String preKey();//用户标识key的前缀。
int seconds() default 0;//如果用户不写表示不需要超时,如果写了就以用户为准。
}
编辑CacheAOP
package com.jt.aop;
import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.util.Arrays;
@Aspect //我是一个AOP切面类
@Component //将类交给spring容器管理
public class CacheAOP {
@Autowired
private Jedis jedis;
/** AOP实现Redis缓存
* 切面=切入点+通知方法
* 注解相关+环绕通知 控制目标方法是否执行
*
* 难点:
* 1.获取注解的对象,将注解作为参数传过来
* 2.动态生成key prekey+用户参数数组
* 3.如何获取方法的返回值类型
*/
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind){
System.out.println("注解拦截生效");
Object result=null;
try {
//拼接redis存储数据的key
Object[] args=joinPoint.getArgs();
String key=cacheFind.preKey()+"::"+ Arrays.toString(args);
//查询redis 之后判断是否有数据
if(jedis.exists(key)){
//表示redis中有数据,无需执行目标方法
String json = jedis.get(key);
//动态获取方法的返回值类型 向上转型 向下转型
MethodSignature methodSignature=(MethodSignature)joinPoint
.getSignature();
//表示客户端传来什么类型就返回什么类型
Class returnType = methodSignature.getReturnType();
//需要将json串转化为对象
result=ObjectMapperUtil.toObj(json, returnType);
System.out.println("redis缓存中的数据");
}else{
//表示数据不存在,需要在数据库中查询
result=joinPoint.proceed();//执行目标方法和通知
//将查询的数据存储到redis缓存中
String json = ObjectMapperUtil.toJson(result);
//判断是否需要超时时间
if(cacheFind.seconds()>0){
jedis.setex(key, cacheFind.seconds(), json);
}else{
jedis.set(key, json);
}
System.out.println("aop执行目标方法查询数据库");
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。