介绍
在软件业,AOP为Aspect Oriented Programming的缩写
意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
原理:动态代理
jdk动态代理,必须实现接口
- 目标对象实现接口
- 核心代理类实现InvocationHandler接口,覆写invoke
- 生成代理对象
IAdvertisement advertisement = (IAdvertisement) Proxy.newProxyInstance(
jiaLing.getClass().getClassLoader()
, jiaLing.getClass().getInterfaces()
,advertisementHandler
);
CGLIB动态代理
连接点( Joinpoint)
程序执行过程中的某一行为, 例如, MemberService .get 的调用或者MemberService .delete 抛出异常等行为。=> getUser
切入点(Pointcut)
匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
通知(Advice)
“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“Advice”。
前置通知(Before Advice)
在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。例如,TestAspect 中的 doBefore 方法。
后置通知(After Advice)
当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
例如,ServiceAspect 中的 returnAfter 方法,所以 Teser 中调用 UserService.delete 抛出异常时,returnAfter 方法仍然执行。
返回后通知(After Return Advice)
在某连接点正常完成后执行的通知,不包括抛出异常的情况。
环绕通知(Around Advice)
包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可 以在方法的调用前后完成自定义的行为, 也可以选择不执行。
例如,ServiceAspect 中的 around 方法。
异常通知(After Throwing Advice)
在方法抛出异常退出时执行的通知 。
目标对象(Target Object)
被一个或者多个切面所通知的对象。例如,AServcieImpl 和 BServiceImpl,当然在实际运行时,Spring AOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。
织入(Weaving)
织入就是将增强添加到对目标类具体连接点上的过程。
织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
代理类(Proxy)
一个类被AOP织入增强后,就产生了一个代理类。
切面(Aspect)
切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
示例
对所有进行登录操作的用户进行拦截,打印登录日志存入数据库
对所需要拦截的方法添加注解:@SelfRecords
@SelfRecords(id="login")
@GetMapping
public ResultJson login(User user){
User userDb = userService.getByUsernameAndPassword(user);
// if(userList != null && userList.size() >0){
if(userDb != null){
/**
* 获取用户所有权限
*
* List、Set
*/
Set<String> menuUrlPathList = userService.listMenuUrlPathByUserId(1L);
userDb.setMenuUrlPathList(menuUrlPathList);
// 生成token
String token = jwtUtil.createToken(userDb);
userDb.setToken(token);
session.setAttribute("token",token);
return ResultJson.ok(userDb);
}
return ResultJson.error("用户名或密码错误!");
}
在每次请求上述注解的方法之前都先被拦截进入下述操作中
import com.qcby.xmfs.boot.annonation.SelfRecords;
import com.qcby.xmfs.boot.common.web.ResultJson;
import com.qcby.xmfs.boot.entity.Rizhi;
import com.qcby.xmfs.boot.entity.User;
import com.qcby.xmfs.boot.service.RizhiService;
import com.qcby.xmfs.boot.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Objects;
@Aspect
@Slf4j
@Component
public class SelfRecordsAop {
@Autowired
private HttpSession session;
@Autowired
private RizhiService rizhiService;
@Autowired
private HttpServletRequest httpServletRequest;
//定义切点,注解作为切入点 拦截所有带有该注解的方法(也可以拦截整个controller)
@Pointcut("@annotation(com.qcby.xmfs.boot.annonation.SelfRecords)")
public void selfRecordsPoinCut() {
}
/**
* before 目标方法执行前执行,前置通知 ==>token验证
* after 目标方法执行后执行,后置通知 =》记录操纵日志
* after returning 目标方法返回时执行 ,后置返回通知 =》记录操纵日志
* after throwing 目标方法抛出异常时执行 异常通知 ==>记录异常日志
* around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知 ==>token验证
*/
@Before("selfRecordsPoinCut()")
public void before(){
int a=1;
log.info("before................通知");
}
@After("selfRecordsPoinCut()")
public void after(){
log.info("After................通知");
}
@AfterReturning("selfRecordsPoinCut()")
public void afterReturning(){
log.info("AfterReturning................通知");
}
@AfterThrowing("selfRecordsPoinCut()")
public void afterThrowing(){
log.info("AfterThrowing................通知");
}
/**
* 可控制目标函数是否执行
*/
@Around("selfRecordsPoinCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around................通知");
log.info("进入Around通知....");
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
String token = httpServletRequest.getHeader("token");
//获取注解信息
SelfRecords viewRecords = method.getAnnotation(SelfRecords.class);
String idValue = viewRecords.id();
log.info("idValue的值为:{}",idValue);
Long id = null;
//获取切入点方法参数
Object[] objects = joinPoint.getArgs();
String[] paramNames = signature.getParameterNames();
for (int i =0;i<paramNames.length;i++){
if (Objects.equals(idValue,paramNames[i]) && objects[i] != null){
id = Long.valueOf(objects[i].toString());
}
}
log.info("id的值为:{}",id);
Rizhi rizhi=new Rizhi();
rizhi.setOperateMethod(idValue);
log.info("===objects[0]=========="+String.valueOf(objects[0]));
rizhi.setInparameter(String.valueOf(objects[0]));
/**
* 进行业务操作
*/
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable e) {
return ResultJson.error("报错");
}
log.error("arround-----------------");
rizhi.setOuparameter(String.valueOf(proceed));
//获取当前用户id存入日志中
String token2 = (String)session.getAttribute("token");
User user=new User();
user=JwtUtil.getUser(token2);
rizhi.setUser_id(user.getId());
LocalDateTime now = LocalDateTime.now();
rizhi.setTime(now);
log.info("rizhi:======="+rizhi);
//存入数据库
rizhiService.insert(rizhi);
return proceed;
}
}
这样操作之后就无需在每个方法中再写打印日志操作,减少了代码量
只需要定义添加一个注解即可
暂无评论内容