有时候,自定义注解需要和AOP配合使用。但AOP那些术语总是晦涩难懂,不好理解,先记录一下。
- 连接点(Joinpoint)
- 切点(Pointcut)
- 增强(Advice)
- 目标对象(Target)
- 引介(Introduction)
- 织入(Weaving)
- 代理(Proxy)
- 切面(Aspect)
晦涩的东西,如果能使用生活中的一些小故事来作类比、作隐喻,就比较好理解了。
场景一
试想:父母为了保护自己的孩子,对孩子的来往书信进行拦截。如果发现书信内容不健康,直接就给对方回信。如果书信内容正常,就转交给孩子,由孩子自行处理。
自定义注解如下:
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "检查书信是否健康";
}
自定义切面(Aspect)如下,凡是使用了自定义注解的地方都是切点(Pointcut), @Around、@Before、 @After都是增强(Advice)的一种。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class TestAspect {
@Pointcut("@annotation(com.test.my.annotation.MyAnnotation)")
public void addAdvice(){
}
@Around("addAdvice()")
public Object interceptor(ProceedingJoinPoint joinPoint){
System.out.println("######进入@Around()方法,华容道横刀立马:如果满足某些条件,越俎代庖直接返回某些信息;否则如同寻常一般,进入控制器方法。");
Object result = null;
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
String letter = (String) args[0];
if ("love".equals(letter)) {
return "不想再看到你的信了";
}
}
// 如同寻常一般,该怎么执行就怎么执行
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
@Before("addAdvice()")
public void before(JoinPoint joinPoint){
System.out.println("######进入@Before()方法");
MethodSignature sign = (MethodSignature)joinPoint.getSignature();
Method method = sign.getMethod();
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
}
@After("addAdvice()")
public void after() {
System.out.println("######进入@After()方法");
}
}
使用注解:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@MyAnnotation()
@RequestMapping("/add1")
public String addData1(String letter) {
System.out.println("======进入控制器方法:addData1(),参数letter=" + letter);
return "success";
}
}
模拟信的内容正常,交给孩子自行处理:http://localhost:8080/add1?letter=hello
######进入@Around()方法,华容道横刀立马:如果满足某些条件,越俎代庖直接返回某些信息;否则如同寻常一般,进入控制器方法。
######进入@Before()方法
======进入控制器方法:addData1(),参数letter=hello
######进入@After()方法
模拟信的内容不正常,根本不交给孩子处理,直接返回:http://localhost:8080/add1?letter=love
######进入@Around()方法,华容道横刀立马:如果满足某些条件,越俎代庖直接返回某些信息;否则如同寻常一般,进入控制器方法。
######进入@After()方法
注:代码参考了这篇博客
场景二
通过注解,自动为某Service方法添加锁。暂且不论锁的功能是否正常。
自定义注解如下:
import java.lang.annotation.*;
@Target(value = {ElementType.PARAMETER, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface ServiceLock {
String description() default "";
}
自定义切面(Aspect)如下:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Component
@Aspect
public class LockAspect {
private static Lock lock = new ReentrantLock(true);
@Pointcut("@annotation(com.test.my.annotation.ServiceLock)")
public void lockAspect() {
}
@Before("lockAspect()")
public void exec(){
System.out.println("######进入LockAspect>@Before()方法");
}
@Around("lockAspect()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("######进入LockAspect>@Around()方法,神不知、鬼不觉地添加锁");
lock.lock();
Object obj = null;
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println("######进入LockAspect>@Around()方法,神不知、鬼不觉地释放锁");
lock.unlock();
}
return obj;
}
@After(value = "lockAspect()")
public void exec1(){
System.out.println("######进入LockAspect>@After()方法");
}
}
使用注解:
public interface IAService {
void m1();
}
@Service
public class AServiceImpl implements IAService {
@ServiceLock
@Override
public void m1() {
System.out.println("######进入业务方法m1()");
}
}
@RestController
public class TestController {
@Autowired
private IAService aService;
@MyAnnotation()
@RequestMapping("/add2")
public String addData2(String deviceId) {
aService.m1();
return "success";
}
}
访问http://localhost:8080/add2的日志如下:
######进入LockAspect>@Around()方法,神不知、鬼不觉地添加锁
######进入LockAspect>@Before()方法
######进入业务方法m1()
######进入LockAspect>@Around()方法,神不知、鬼不觉地释放锁
######进入LockAspect>@After()方法
注:代码参考了这篇博客