1、自定义一个注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DBChange {
}
2、创建切面类
@Aspect
@Component
@Log4j
public class DBChangeAop {
//切面链接处
@Pointcut("@annotation(com.ggdo.manager.util.DBChange)")
public void cutMethod(){}
@Before(value = "cutMethod() && @annotation(dbChange)")
public void doBeforeCut(JoinPoint joinPoint,DBChange dbChange) throws Exception{
log.info("Before增强");
//自定义工具类解析注接类传入参数
String projectId = (String)DBChangeResolver.newInstance().resolver(joinPoint,dbChange.projectId());
String environmentType = (String)DBChangeResolver.newInstance().resolver(joinPoint,dbChange.environmentType());
log.info("projectId:"+projectId);
log.info("environmentType:"+environmentType);
if (projectId == null || "".equals(projectId) ){
throw new CommonException("projectId不能为空!");
}
}
@After(value = "cutMethod()")
public void doAfterCut(JoinPoint joinPoint){
log.info("After增强");
}
}
注:
@Aspect:表示该类是切面类。
@Pointcut("@annotation(com.ggdo.manager.util.DBChange)") :切面连接处,添加此连接以后,有DBChange注解的地方,进行切面切入。
2.1 AOP注解详解
2.1.1 @Aspect
名称:@Aspect 类型:
注解 位置:类定义上方
作用:设置当前类为切面类
格式:
@Aspect
public class AopAdvice {
}
说明:一个beans标签中可以配置多个aop:config标签
2.2.2 @Pointcut
名称:@Pointcut
类型:注解
位置:方法定义上方
作用:使用当前方法名作为切入点引用名称
格式:
@Pointcut(“execution(* *(…))”)
public void pt() {
}
说明:被修饰的方法忽略其业务功能,格式设定为无参无返回值的方法,方法体内空实现(非抽象)
2.2.3 @Before
名称:@Before
类型:注解
位置:方法定义上方
作用:标注当前方法作为前置通知
格式:
@Before(“pt()”) public void before(){
}
特殊参数: 无
2.2.4 @After
名称:@After
类型:注解
位置:方法定义上方
作用:标注当前方法作为后置通知
格式:
@After(“pt()”)
public void after(){
}
特殊参数: 无
2.2.5 @AfterReturning
名称:@AfterReturning
类型:注解
位置:方法定义上方
作用:标注当前方法作为返回后通知
格式:
@AfterReturning(value=“pt()”,returning = “ret”)
public void afterReturning(Object ret) {
}
特殊参数: returning :设定使用通知方法参数接收返回值的变量名
2.2.6 @AfterThrowing
名称:@AfterThrowing
类型:注解
位置:方法定义上方
作用:标注当前方法作为异常后通知
格式:
@AfterThrowing(value=“pt()”,throwing = “t”)
public void afterThrowing(Throwable t){
}
特殊参数: throwing :设定使用通知方法参数接收原始方法中抛出的异常对象名
2.2.7 @Around
名称:@Around
类型:注解
位置:方法定义上方
作用:标注当前方法作为环绕通知
格式:
@Around(“pt()”)
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed(); return ret;
}
特殊参数: 无
AOP使用XML配置情况下,通知的执行顺序由配置顺序决定,在注解情况下由于不存在配置顺序的概念的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序
同一个通知类中,相同通知类型以方法名排序为准 不同通知类中,以类名排序为准 使用@Order注解通过变更bean的加载顺序改变通知的加载顺序
3、在注解类中添加需要传入的参数。
如果切面类中需要传入参数,可在注解类中定义。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DBChange {
String projectId() default "";
String environmentType() default "";
/**
*传递参数的对象类型
*/
Class<?> parameter() default Object.class;
/**
* 默认不校验参数
* @return
*/
boolean require() default false;
}
4、添加自定义注解在需要的方法
@PostMapping("/change")
@DBChange(projectId = "#{resultInfo.projectId}",environmentType = "#{resultInfo.environment}" ,require = true)
public ResponseObj changeDb(@RequestBody EdcReleaseLogDto resultInfo){
log.info("数据源切换测试");
return ResponseObj.fail("数据源切换失败");
}
注:自定义注解类设置了需要传递的参数,在添加自定义注解@DBChange时,需要设置对应参数projectId和environmentType。
考虑到传入的参数不固定,设置成"#{}"形式。
5、自定义工具类,对注解传入的参数进行解析
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
public class DBChangeResolver {
private static DBChangeResolver resolver ;
public static DBChangeResolver newInstance(){
if (resolver == null) {
return resolver = new DBChangeResolver();
}else{
return resolver;
}
}
/**
* 解析注解上的值
* @param joinPoint
* @param str 需要解析的字符串
* @return
*/
public Object resolver(JoinPoint joinPoint, String str) {
if (str == null) return null ;
Object value = null;
if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
if (newStr.contains(".")) { // 复杂类型
try {
value = complexResolver(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = simpleResolver(joinPoint, newStr);
}
} else { //非变量
value = str;
}
return value;
}
private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strs = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strs[0].equals(names[i])) {
Object obj = args[i];
Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
Object value = dmethod.invoke(args[i]);
return getValue(value, 1, strs);
}
}
return null;
}
private Object getValue(Object obj, int index, String[] strs) {
try {
if (obj != null && index < strs.length - 1) {
Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
obj = method.invoke(obj);
getValue(obj, index + 1, strs);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getMethodName(String name) {
return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
}
private Object simpleResolver(JoinPoint joinPoint, String str) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
if (str.equals(names[i])) {
return args[i];
}
}
return null;
}
}