自定义注解(AOP、拦截器)

本文详细介绍了Java中的注解原理,包括其作用、JDK内置注解如@Override、@Deprecated等,以及AOP方式实现自定义注解和拦截器的示例。元注解和注解的生命周期也得到了讨论。

一、注解

1、注解原理

Annotation (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

注解本质是一个继承了Annotation的特殊接口。

编译器将在编译期扫描每个类或者方法上的注解信息,会将这些信息存在class文件中。

java运行时,采用 JDK 动态代理机制生成一个实现注解(接口)的代理类。使用反射(也就是我们这里的 getAnnotation 方法)获取注解类实例时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。

注解只有被解析之后才会生效,常见的解析方法有两种:

1、编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。

2、运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的。

2、JDK内置注解

JDK中内置了三个基本的注解:

@Override: 限定重写父类中方法, 该注解只能用于方法;

@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时,通常是因为所修饰的结构危险或存在更好的选择;

@SuppressWarnings: 抑制编译器警告。

3、元注解

元注解就是修饰注解的注解。JDK中有四个元注解:

@Retention:表示注解的生命周期,即注解被保留的阶段。

(1) RetentionPolicy.SOURCE :在源文件中有效(即源文件保留),编译时编译器会直接丢弃这种策略的注解;

(2) RetentionPolicy.CLASS : 在class文件中有效(即class保留),当运行Java程序时, JVM不会保留注解。这是默认值

(3) RetentionPolicy.RUNTIME : 在运行时有效(即运行时保留),当运行 Java 程序时, JVM会保留注解。程序可以通过反射获取该注释。

@Target:表示注解能够作用的位置。

(1) ElementType.TYPE :可以作用在类、接口和枚举类上;

(2) ElementType.METHOD :可以作用在方法上;

(3) ElementType.FIELD :可以作用在成员变量上;

(4) ElementType.CONSTRUCTOR :可以作用在构造器上;

(5) ElementType.LOCAL_VARIABLE :可以作用在局部变量上。

(6) ElementType.ANNOTATION_TYPE:可以作用在注解上;
(7) ElementType.PACKAGE:可以作用在包上;

注:可以指定多个位置,例如:@Target({ElementType.METHOD, ElementType.TYPE}),也就是此注解可以在方法和类上面使用

@Documented:表示该注解修饰的注解,可以被抽取到API文档中。

注意:定义为Documented的注解必须设置Retention值为RetentionPolicy.RUNTIME 。

@Inherited:指定被修饰的Annotation将具有继承性,表示该注解可以被子类继承。

二、AOP方式实现自定义注解

1、定义注解

2、定义AOP切面

@Slf4j
@Component
@Aspect
public class DemoAspect {

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.wyl.fund.demo.annotation.DemoAnno)")
    public void logPointCut() {}


    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object saveLog(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTimeMillis = System.currentTimeMillis();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        DemoAnno demoAnno = method.getAnnotation(DemoAnno.class);


        log.info("请求链接:{}", httpServletRequest.getRequestURL().toString());
        log.info("请求类型:{}", httpServletRequest.getMethod());
        log.info("请求方法:{}", signature.getDeclaringTypeName(), signature.getName());
        log.info("请求ip:{}", httpServletRequest.getRemoteAddr());
        log.info("请求入参:{}", joinPoint.getArgs());

        // 处理注解的字段值,并根据字段值 do something!
        log.info("demoAnno value: " + demoAnno.value());
        log.info("demoAnno des: " + demoAnno.des());


        Object proceedResult = null;
        try {
            // 执行目标方法
            proceedResult = joinPoint.proceed();
        } catch (Exception e) {
            log.error("目标方法执行异常:", e);
            throw e;
        }

        //
        long endTimeMillis = System.currentTimeMillis();
        log.info("请求耗时:{}ms", endTimeMillis - startTimeMillis);
        //
        log.info("目标方法执行结果:{}", proceedResult);


        return proceedResult;
    }
}

3、方法上加注解

三、拦截器方式实现自定义注解

1、定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoAnno {
    String value() default "weng";
    String des() default "";
}

2、定义拦截器

@Component
@Slf4j
public class DemoInterceptor extends HandlerInterceptorAdapter {
    //before the actual handler will be executed
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();//获得被拦截的方法对象
        DemoAnno demoAnno = method.getAnnotation(DemoAnno.class);//获得方法上的注解
        if(demoAnno != null){
            //方法上加了自定义注解,需要进行日志记录
            long startTime = System.currentTimeMillis();
            request.setAttribute("startTime", startTime);
            log.info("[" + handler + "] executeTime开始");
            return true;
        }
        return false;

    }

    //after the handler is executed
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();//获得被拦截的方法对象
        DemoAnno demoAnno = method.getAnnotation(DemoAnno.class);//获得方法上的注解


        String requestUri = request.getRequestURI();
        String methodName = method.getDeclaringClass().getName() + "." +
                method.getName();
        String methodDesc = demoAnno.des();

        log.info("请求uri:" + requestUri);
        log.info("请求方法名:" + methodName);
        log.info("方法描述:" + methodDesc);

        long startTime = (Long)request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        //统计耗时
        long executeTime = endTime - startTime;
        log.info("[" + handler + "] executeTime结束 : " + executeTime + "ms");

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("[" + handler + "] afterCompletion结束 ");

    }
}

3、配置拦截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
        // .excludePathPatterns("/user/get")/.addPathPatterns(). 可以排除和指定设置
    }
}

4、方法上加注解

### 创建和使用Hibernate Validator中的自定义注解 #### 定义自定义约束注解 为了创建自定义验证注解,在Java中需先定义一个新的注解类。此注解应指定`@Constraint`来表明这是一个用于字段或方法参数上的验证规则[^1]。 ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MyCustomValidator.class) public @interface MyCustom { String message() default "Invalid value"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 上述代码展示了如何声明名为 `MyCustom` 的新注解,它将在运行时被保留并应用于字段上。通过设置默认消息以及分组和支持负载的属性,可以增强其灵活性。 #### 实现约束校验器 接着实现具体的逻辑处理程序——即实现了`ConstraintValidator<A,T>`接口的一个类,其中A代表所定义的注解类型而T则是待检验的目标数据类型。 ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class MyCustomValidator implements ConstraintValidator<MyCustom, String> { @Override public void initialize(MyCustom constraintAnnotation) {} @Override public boolean isValid(String value, ConstraintValidatorContext context) { // Add your validation logic here. return true; // Replace this line with actual validation code. } } ``` 这段示例说明了怎样编写一个简单的字符串有效性检查函数;实际应用中应当替换掉返回true的那一行以加入特定业务需求下的判断条件。 #### 应用自定义注解到实体模型 最后一步就是把新建好的注解标记给相应的域或者getter/setter方法之上: ```java public class UserEntity { private Long id; @MyCustom(message="This field must follow specific rules.") private String specialField; // Getters and setters... } ``` 这样每当保存含有此类标注的对象实例之前都会触发对应的验证过程,并依据配置的消息模板给出反馈提示。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值