springAop
前言:这里介绍AOP的基本使用,aop知识点比较多后续会不断补充~
1.概念介绍(通俗易懂)
(1).Join point(连接点)对哪个方法进行处理增强,这个方法就是点
程序执行过程中的一点,例如方法执行或异常处理。在Spring AOP中,连接点始终代表方法的执行
(2).Pointcut(切入点)作用范围,如在哪个包下,哪个类中等等!
切入点是与连接点匹配的表达式
(3).Advice(增强/通知)拦截后做的事情增强,如:我在执行前了写日志,做了用户访问权限校验等!
所谓的通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为目的地通知,后置通知,异常通知,最终通知和环绕通知(切面要完成的功能)
(4).Aspect(切面)你写的aop的类,就是切面,再简单一点就是你加了@Aspect的类,专业名称叫做"切面"
Aspect切面表示Pointcut(切入点)和Advice(增强/通知)的结合
如图:
2.常用表达式
3.表达式符号解释
1.方法的修饰符 public
2.返回值 *
3.包名 com.xxxx
4.方法,方法参数 service.*(…)
包路径: 当前包和子包就是用…
匹配无参的就是 () 括号内是空的
(Long) 那么就是匹配一个long类型的入参
(…) 那么就是任意参数
5.匹配方法抛出了哪些异常 exception
(一般加载在括号后面,这里放一个图片案例如: 后面跟的throws…)
4.aop通知
5.JoinPoint & ProceedingJoinPoint API介绍
JoinPoint
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature(); //返回当前连接点方法签名
(方法签名的含义: 就是方法名加括号里面的所有参数列表,注意没有返回值!举例: print(String name) print方法加它的入参String类型的name 这就是方法的签名)
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
ProceedingJoinPoint
环绕通知的入参,包含了joinPoint的所有api并且多了两个方法
Object proceed() throws Throwable (这里就连接点执行本类的方法)
通过反射执行目标对象连接点处的方法。
Object proceed(Object[] var1) throws Throwable
使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。
6.代码演示!
1.依赖
框架用的springboot
springboot父~
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
aop测试依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
下面是测试的几个类!
package com.aop.aspect.testaop;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
**目标执行类**
@Component
public class MyTest {
public String add(String userId) throws Exception {
if (StringUtils.isBlank(userId)){
throw new Exception("我没有userId...我是个没有理想的咸鱼!");
}
System.out.println("我是userId..." + userId);
return userId;
}
}
切面类
package com.aop.aspect.testaop;
import com.aop.aspect.exception.DAOException;
import com.aop.aspect.exception.ErrorCodeConstants;
import com.aop.aspect.exception.PandoraException;
import com.aop.aspect.exception.PandoraResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.MDC;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.*;
@Aspect
@Component
@Slf4j
public class MyAop {
/**
*
* 这里要写一个切点表达式,表示你要作用于哪个类,哪个方法
* 写在方法上是因为@Pointcut()表达式要在类上才生效啊! 方法名可以自己定义但是要见名知意!方法内可以不写任何东西
*
* public * com.aop.aspect..*.*(..)
* 表达式解释: public修饰的/任意返回值的/com.aop.aspect包下和子包下的/任意类/任意方法/任意参数
*
* 其他解释: 包后面的..表示当前包, *表示任意 (..)括号里的..表示任意参数你可以把他看成方法的括号就好理解了,传几个参数都可以什么类型都可以
*/
@Pointcut("execution(public * com.aop.aspect..*.*(..))")
public void cut(){
System.out.println("我是一个切入点方法... ");
}
/**
* 切点通知,你要在这里面做的方法增强逻辑就写在这里面,
* cut()表示你要把Before这个通知作用到上面的切入点的范围内.
* @param joinPoint
*/
@Before("cut()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println(joinPoint);
log.info("我是前置通知...我在方法执行前做处理");
printPointAgs(joinPoint);
}
//后置通知
@After(value = "cut()",argNames = "joinPoint")
public void afterAdvice(JoinPoint joinPoint){
log.info("我是后置通知,在方法执行之后做处理");
}
//正常返回通知
@AfterReturning(value = "cut()",returning = "result")
public void afterReturningAdvice(Object result){
log.info("我是返回通知,可以用来做正常返回值有关的逻辑!");
}
//异常通知
@AfterThrowing(value = "cut()",throwing="e")
public void afterThrowingAdvice(Exception e){
log.info("我是一个异常处理通知,在抛出异常的时候执行");
}
//环绕通知
@Around(value = "cut()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
log.info("我是环绕通知,我最强大! 我可以做任何事情!");
//这里给一个案例!日志打印,并抛出自定义的异常!产生traceId
long before = System.currentTimeMillis();
String traceId = UUID.randomUUID().toString();
try{
//TraceIdUtil.set(traceId);
//this.logInfo(pjp,traceId);
MDC.put("traceId", traceId);
return pjp.proceed();//执行该方法
}catch (PandoraException pandoraException) {
this.logError(pjp, pandoraException);
if(isNotBlank(pandoraException.getErrorMSG())){
return PandoraResult.fail(pandoraException.getErrorCode(), pandoraException.getErrorMSG());
}
return PandoraResult.fail(pandoraException);
} catch (DAOException daoException) {
this.logError(pjp, daoException);
return PandoraResult.fail(ErrorCodeConstants.DA0_ERROR);
} catch (Exception e) {
this.logError(pjp, e);
return PandoraResult.fail(ErrorCodeConstants.SERVICE_ERROR);
} catch (Throwable e){
this.logError(pjp, e);
return PandoraResult.fail(ErrorCodeConstants.SERVICE_ERROR);
}finally{
this.logInfoRT(pjp, traceId, before);
MDC.remove("traceId");
}
}
//参数打印!
private void printPointAgs(JoinPoint joinPoint) {
System.out.println("================joinPoint参数列表!===================");
String string = joinPoint.toString(); // execution(String com.aop.aspect.testaop.MyTest.add(String))
log.info("string {}",string);
String shortString = joinPoint.toShortString();//execution(MyTest.add(..))
log.info("shortString {}",shortString);
String longString = joinPoint.toLongString();//execution(public java.lang.String com.aop.aspect.testaop.MyTest.add(java.lang.String))
log.info("longString {}",longString);
Object aThis = joinPoint.getThis();//com.aop.aspect.testaop.MyTest@3711c71c
log.info("aThis {}",aThis);
Object target = joinPoint.getTarget();//com.aop.aspect.testaop.MyTest@3711c71c
log.info("target {}",target);
Object[] args = joinPoint.getArgs();//闲鱼~
log.info("args {}",args);
Signature signature = joinPoint.getSignature();//String com.aop.aspect.testaop.MyTest.add(String)
log.info("signature {}",signature);
SourceLocation sourceLocation = joinPoint.getSourceLocation();//org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@30508066
log.info("sourceLocation {}",sourceLocation);
String kind = joinPoint.getKind();//method-execution
log.info("kind {}",kind);
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();//execution(String com.aop.aspect.testaop.MyTest.add(String))
log.info("staticPart {}",staticPart);
}
private Method getMethod(JoinPoint pjp) throws ClassNotFoundException, NoSuchMethodException {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Class<?> targetClass = Class.forName(pjp.getTarget().getClass().getName());
Class[] classes = methodSignature.getMethod().getParameterTypes();
return targetClass.getDeclaredMethod(pjp.getSignature().getName(), classes);
}
private void logInfoRT(JoinPoint joinPoint, String traceId, long before) {
try {
Method method = getMethod(joinPoint);
log.info("Info@{}#{}-traceId={}:RT={}", method.getDeclaringClass().getName(), method.getName(), traceId, (System.currentTimeMillis() - before));
} catch (Exception e) {
}
}
private void logError(JoinPoint joinPoint, Throwable e){
try{
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
Method method = getMethod(joinPoint);
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
sb.append(parameters[i].getName()+"={},");
}
sb.append("errorMessage={}");
List<Object> list = new ArrayList<Object>();
for (Object object : arguments) {
list.add(object);
}
if(e instanceof PandoraException){
PandoraException exception = (PandoraException) e;
list.add(exception.getErrorCode()+"-"+exception.getErrorMSG());
}else{
list.add(e.getMessage());
}
list.add(e);
log.error("error@"+joinPoint.getTarget().getClass().getSimpleName()+"#"+methodName+":"+sb.toString(),list.toArray());
}catch(Exception e1){}
}
}
测试用例!
package com.aop.aspect.testaop;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@SpringBootTest
@RunWith(SpringRunner.class)
public class JUnitMyTest {
@Resource
private MyTest myTest;
@Test
public void aopTest() throws Exception {
myTest.add("闲鱼~");
}
}
运行的日志结果!
注意: 如果在切面类中写了@Around 环绕通知! 那么@Before前置通知是不会执行的! 环绕通知中是可以处理前置后置等一系列的操作!