简述自定义注解

标注在注解上的常用注解

最重要的两个注解:@Retention与@Target, 一般自定义注解上都需要加上这两个注解

  • @Retention:用来指定注解的生命周期,可以指定的生命周期有:(RetentionPolicy枚举类的)SOURCE、CLASS、RUNTIME,如果不加此注解,默认生命周期为CLASS
    • SOURCE:只在源代码上显示,编译后不保存。
    • CLASS:编译后会留在class文件中,但运行程序时JVM不会保留该注解。
    • RUNTIME:编译后留在class文件中,且运行程序时JVM会保留该注解。(当需要使用反射来获得类上的注解时,该注解的生命周期必须是RUNTIME
    • @Retention注解示例使用:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}
  • @Target:用来指定该注解可以标注在哪些地方:

    • 用例:
   @Retention(RetentionPolicy.RUNTIME)
   @Target({ElementType.TYPE, ElementType.METHOD})
   public @interface MyAnnotation {
       String value();
   }
  • ElementType枚举类的含义解析:
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,				// 可以标注在类、接口、注解接口、枚举类上

    /** Field declaration (includes enum constants) */
    FIELD,				// 可以标注在属性上

    /** Method declaration */
    METHOD,				// 可以标注在方法上

    /** Formal parameter declaration */
    PARAMETER,			// 可以标注在方法参数上

    /** Constructor declaration */
    CONSTRUCTOR,		// 可以标注在构造器上

    /** Local variable declaration */
    LOCAL_VARIABLE,		// 可以标注在局部变量上

    /** Annotation type declaration */
    ANNOTATION_TYPE,	// 可以标注在注解接口上

    /** Package declaration */
    PACKAGE,			// 可以标注在包上

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,		// 可以标注在类型变量的声明语句中(如泛型)

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE			// 可以标注在使用类型的任何语句中
}
  • @Documented:表示所修饰的注解在被javadoc解析时,会被保留下来
  • @Inherited:表示修饰的注解将具有继承性(也就是子类可以继承父类上被标记了@Inherited注解的注解)

自定义注解的用途

创建了自定义注解后,标注在对应的地方,可以通过反射的方式查看注解,在程序中判断是否有指定注解,根据结果,对方法进行不同的操作(如通过拦截器Interceptor,对有注解/没注解的方法等进行前置处理)

用途一 作为AOP的切入点

比如想要在一些相似的方法的切面上插入一个固定方法(如想要在固定位置插入日志记录等),可以通过给那些方法上加一个辅助的自定义注解,然后通过Spring的AOP功能,完成切面编程。

例子

1、首先创建一个自定义注解
(这里的注解,如果只是用于作为AOP切入点,Retention生命周期为CLASS或RUNTIME都可以,但是不可以是SOURCE的)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AOPAnnotation {
}

2、编写基于@Aspect注解的切面类(切面类是我自己的说法)

@Aspect		// 开启切面功能
@Component	// 切面类需要加入IOC容器
public class MyAspect {
	// 先给出切点 也可以不事先给出 在后面直接用表达式指定切点
	// 切点就是标注了@AOPAnnotation注解的方法
    @Pointcut("@annotation(org.fall.springboot.annotation.AOPAnnotation)")
    public void pointcut() {
    }

	// 后置通知
    @AfterReturning("pointcut()")
    public void doAfterReturn() {
        System.out.println("在标注了@AOPAnnotation的方法执行完成后执行... ...");
    }

	// 前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("在标注了@AOPAnnotation的方法执行之前执行... ...");
    }
}

3、编写标注@AOPAnnotation的方法,用来测试切面是否生效

@Service
public class MyService {
    @AOPAnnotation	// 标注
    public void doService() {
        System.out.println("service 方法执行... ...");
    }
}

4、在测试类中测试

@SpringBootTest
class SpringBootAnnotationApplicationTests {
    @Resource
    private MyService myService;

    @Test
    void testAOP() {
        myService.doService();
    }
}

执行testAOP()的结果:
在这里插入图片描述
可以发现标注了@AOPAnnotation注解后,可以基于该注解给对应方法加切面通知。

用途二 通过反射利用注解作为拦截器的标识

除了将自定义注解作为AOP的切点之外,还可以利用注解与拦截器结合,完成一些特定的功能(如最常用的利用注解进行权限认证

例子

1、先创建一个自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AuthAnnotation {
}

2、编写拦截器类

public class AuthInterceptor implements HandlerInterceptor {

    /**
     * preHandle 在请求到达拦截器前触发
     * 这里模拟:
     * 		在请求到达时,只放行未标注@AuthAnnotation注解的方法(返回true)
     *      而不放行标注了@AuthAnnotation注解的方法(返回false)
     * 如果需要更多的功能可以自行添加
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    	// 先判断当前传入的是不是HandlerMethod的实例 防止类型转换的错误
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 这里先判断类上有没有@AuthAnnotation注解
        AuthAnnotation authAnnotation = handlerMethod.getBean().getClass().getAnnotation(AuthAnnotation.class);
        Method method = null;
        // 如果类上没有@AuthAnnotation注解 再判断对应的方法上有没有该注解
        if (authAnnotation == null) {
            method = handlerMethod.getMethod();
            authAnnotation = method.getAnnotation(AuthAnnotation.class);
        }

        if (authAnnotation == null) {
            // 方法上没有注解时的操作
            System.out.println("方法 " + method.getName() + " 上未标注@AuthAnnotation 直接放行");
            return true;
        } else {
            // 方法上有注解时的操作
            System.out.println("方法 "+ method.getName() +" 上标注了@AuthAnnotation 不允许放行");
            return false;
        }
    }

    // postHandle 在响应到达拦截器前触发
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		// 可以自行编写操作
    }

    // afterCompletion 在页面渲染完毕后触发
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		// 可以自行编写操作
    }
}

3、将拦截器注册入容器

@Configuration
public class MyWebMVCConfig implements WebMvcConfigurer {
    // 重写WebMvcCOnfigurer的addInterceptors方法
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册AuthInterceptor拦截器
        registry.addInterceptor(new AuthInterceptor());
    }
}

4、编写Controller层的方法测试注解与拦截器是否生效了

@RestController
public class MyController {

	// 该方法标注了@AuthAnnotation注解
    @AuthAnnotation
    @GetMapping("/with")
    public String doMethodWithAuthAnnotation() {
        System.out.println("标注了@AuthAnnotation注解的方法执行");
        return "doMethodWithAuthAnnotation";
    }

	// 该方法未标注@AuthAnnotation注解
    @GetMapping("/without")
    public String doMethodWithoutAuthAnnotation() {
        System.out.println("未标注@AuthAnnotation注解的方法执行");
        return "doMethodWithoutAuthAnnotation";
    }
}

5、此时启动项目,分别测试两个controller方法

  • without:
    • 浏览器访问:
      在这里插入图片描述
    • 后台输出:
      在这里插入图片描述
  • with:
    • 浏览器访问:
      在这里插入图片描述
    • 后台输出:
      在这里插入图片描述
      可以发现未标注@AuthAnnotation的handler方法在被浏览器访问时,拦截器检测到并放行,执行了内部的代码;而标注了@AuthAnnotation的handler方法被拦截器检测到后,发现有指定注解,被拦截,内部代码没有执行,浏览器也没有输出任何文字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值