自定义注解的申明
自定义注解annotation
一些元注解的说明:
@Retention: 什么时候使用该注解,我们定义为运行时;
- SOURCE, 编译器处理完该注解后不存储在class中
- CLASS, 编译器把该注解存储在class字节码文件中
- RUNTIME, 编译器把该注解存储在class字节码文件中,并且可以由JVM读取,在运行时可以通过反射获取到
@Target: 注解用于什么地方,我们定义为作用于方法和类上;
- @Target(ElementType.TYPE) //接口、类、枚举、注解
- @Target(ElementType.METHOD) //方法上
- @Target(ElementType.FIELD) //字段、枚举的常量
- @Target(ElementType.PARAMETER) //方法参数
- @Target(ElementType.CONSTRUCTOR) //构造函数
- @Target(ElementType.LOCAL_VARIABLE) //局部变量
- @Target(ElementType.ANNOTATION_TYPE) //注解
- @Target(ElementType.PACKAGE) //包
@Documented: 这个Annotation可以被写入javadoc;
@Inherited: 这个Annotation 可以被继承,如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
// 参数,可以在aop里获取到
String value() default "";
}
声明完成,该注解就可以使用了
配合aop使用(常用)
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.2</version>
</dependency>
aspectj相关注解的作用:
@Aspect :定义一个切面,标注在类上
@Pointcut :定义一个切点(在切点前后增强),定义需要拦截的内容,切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
execution表达式: 以 execution(* com.mutest.controller….(…))表达式为例:
- 第一个 * 号的位置:表示返回值类型,* 表示所有类型。
包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。 - 第二个 * 号的位置:表示类名,* 表示所有类。
(…):这个星号表示方法名, 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
annotation() 表达式:
annotation() 方式是针对某个注解来定义切点。
定义一个切面
@Component
@Aspect
@Slf4j
public class MyAnnotationAspect {
// 注解类
// 如果是切入注解使用annotation,如果是正常类使用execution
@Pointcut("@annotation(com.feng.study.annotation.MyAnnotation)")
public void pointcut(){}
/**前置增强方法*/
@Before("pointcut()")
//@Before("execution(public * com.bc.aop..*.*(..))")可以给方法单独指定切入点
public void beforeLogger(JoinPoint jp) {
//比如说拦截的是controller里的方法,我想记录每个调用的ip地址,也是可以获取到请求信息的
//ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//HttpServletRequest request = attributes.getRequest();
// String ip = request.getRemoteAddr();
logger.info("这是MyLogger类的before方法!");
System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));
System.out.println("切入点方法签名对象:" + jp.getSignature());
System.out.println("切入点所在目标对象:" + jp.getTarget());
System.out.println("代理对象本身:" + jp.getThis());
}
/**后置增强方法,此方法对void 返回值的无效*/
@AfterReturning(pointcut = "pointcut()", returning = "result")
public void afterReturning(JoinPoint jp, Integer result) {
logger.info("这是MyLogger类的after-returning方法!");
System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));
System.out.println("切入点方法签名对象:" + jp.getSignature());
System.out.println("切入点所在目标对象:" + jp.getTarget());
System.out.println("代理对象本身:" + jp.getThis());
System.out.println("切入点方法返回对象:" + result);
}
/**后置异常增强方法*/
@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e) {
logger.info("这是MyLogger类的after-Throwing方法!");
System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));
System.out.println("切入点方法签名对象:" + jp.getSignature());
System.out.println("切入点所在目标对象:" + jp.getTarget());
System.out.println("代理对象本身:" + jp.getThis());
System.out.println("异常:" + e);
}
/**最终增强方法*/
@After("pointcut()")
public void after(JoinPoint jp) {
logger.info("这是MyLogger类的after方法!");
System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));
System.out.println("切入点方法签名对象:" + jp.getSignature());
System.out.println("切入点所在目标对象:" + jp.getTarget());
System.out.println("代理对象本身:" + jp.getThis());
}
/**环绕增强方法*/
@Around("pointcut()")
public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable {
logger.info("这是MyLogger类的around方法!");
System.out.println("切入点方法入参列表:" + Arrays.toString(jp.getArgs()));
System.out.println("切入点方法签名对象:" + jp.getSignature());
System.out.println("切入点所在目标对象:" + jp.getTarget());
System.out.println("代理对象本身:" + jp.getThis());
System.out.println("-------------------------------");
// System.out.println("执行切入点方法");
// 调用proceed方法,会执行切点,返回方法结果,可以在这里进行干扰操作
// Object result = jp.proceed(); //执行切点,会依次调用@Before -> 接口逻辑代码 -> @After -> @AfterReturning
// 一个示例 比如说我想统计方法执行耗时
// long start = System.currentTimeMillis(); //记录调用接口的开始时间
// Object result = joinPoint.proceed(); // 就需要调用该方法执行,获取结果在返回
// long end = System.currentTimeMillis(); //记录结束时间
// System.out.println("Time-Consuming: "+ (end - start) / 1000+" s"); //记录耗时
// return result; //返回接口参数结果
System.out.println("-------------------------------");
System.out.println("执行切入点方法并改变参数");
return jp.proceed(new Object[]{7,9});
}
// 示例非注解拦截的指定方法,并且获取到方法上的参数
// @Around("execution(* com.jeeplus.modules.mail.service.MailBoxService.save(com.jeeplus.modules.mail.entity.MailBox)) && args(mailBox)")
// 或者 @Around("execution(* com.jeeplus.modules.mail.service.MailBoxService.save(..)) && args(mailBox)")
// public void interceptSave(ProceedingJoinPoint jp, MailBox mailBox) throws Throwable {
// 在方法调用前执行的逻辑
// System.out.println("Before save method");
// 调用原始方法
// jp.proceed();
// System.out.println("切入点方法入参:" + mailBox);
// 在方法调用后执行的逻辑
// System.out.println("After save method");
// }
// 被拦截的请求方法
// @Service
// @Transactional(readOnly = true)
// public class MailBoxService{
// @Transactional(readOnly = false)
// public void save(MailBox mailBox) {
// System.out.println("被拦截的请求方法");
// }
// }
}
如果说我们想在增强时获取到注解上的参数就需要用下面的写法
MyAnnotation 就是定义注解的类
@Before("@annotation(myAnnotation)") // 注意的是 两个 myAnnotation的名称要一致
public void beforeLogger(JoinPoint jp,MyAnnotation myAnnotation) {
System.out.println(myAnnotation.value());
}
通过反射获取注解信息(不常用)
public class TestMyAnnotation {
public static void main(String[] args) throws Exception {
Person person = new Person();
//通过反射获取person对象的方法
Method method = person.getClass().getMethod("print", Integer.class, String.class);
//执行对应的print方法
method.invoke(person,new Object[]{15,"pag"});
//从方法中获取注解信息
getAnnotationByMethod(method);
}
private static void getAnnotationByMethod(Method method) {
//首先判断该方法是否包含MyAnnotation注解
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
//再获取注解的属性值,进行输出打印
String value1 = annotation.value1();
String value2 = annotation.value2();
System.out.println("value1: " + value1 + ",value2: " + value2);
}
//同时可以获取该方法的所有注解信息
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation: annotations) {
System.out.println(annotation);
}
}
}
配合拦截器使用(常用)
/**
* @Author: chenl
* @Date: 2023/5/11
*
* 自定义注解 配合拦截器使用
*
*/
@Component
public class AnnotationAndInterceptor {
// 自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle方法在业务处理器处理请求之前被调用");
// 获取拦截到的方法,判断该方法上是否存在指定注解
HandlerMethod method = (HandlerMethod) handler;
MyAnnotation methodAnnotation = method.getMethodAnnotation(MyAnnotation.class);
if(methodAnnotation==null){
return true;
}
//方法上有自定义注解
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("你访问的资源需要先进行登录");
response.flushBuffer();
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle方法在业务处理器处理请求执行完成后,生成视图之前执行");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion方法在DispatcherServlet完全处理完请求后被调用,可用于清理资源等");
}
}
// 注册拦截器
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
}