自定义注解实现方式解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 自定义注解实现方式解析


image.png

自定义注意在日常开发中经常使用,同时有很多实现自定义注解的方式,本文将带你一一了解。

1.源注解解析

@Retention

    //注解只会存在源代码中,将会被编译器丢弃
    SOURCE,
    //注解将会保留到class文件阶段,但是在加载如vm的时候会被抛弃
    CLASS,
    //注解不单会被保留到class文件阶段,而且也会被vm加载进虚拟机的时候保留
    RUNTIME

@Target

用于描述类、接口(包括注解类型) 或enum声明 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
    TYPE_PARAMETER,
     *能标注任何类型名称 Use of a type
    TYPE_USE

2.依赖于@Conditional

原理是是否满足@Conditional中绑定类的条件,如果满足,就将使用注解的类注入进factory,不满足就不注入,只能在类中使用或者配合@bean使用。

1.添加注解类

首先定义一个注解类,定义的变量为参数。

@Conditional(CustomOnPropertyCondition.class)意思为CustomOnPropertyCondition类中返回为true才会使用注解
package com.airboot.bootdemo.config;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(CustomPropertyCondition.class)
public @interface CustomProperty {
    //参数
    String name() default "";
        /**
         * havingValue数组,支持or匹配
         */
    //参数
    String[] havingValue() default {};
}

2.CustomOnPropertyCondition类

实现condition接口,以下是实现接口后的代码,然后在其中填补。

public class CustomPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        return false;
    }
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;
public class CustomPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //获取注解上的参数和配置值
        Map<String, Object> annotationAttributes = annotatedTypeMetadata.getAnnotationAttributes(RequestMapping.class.getName());
        //获取具体的参数值
        String propertyName = (String) annotationAttributes.get("name");
        String[] values = (String[]) annotationAttributes.get("havingValue");
        if (0 == values.length) {
            return false;
        }
        //从application.properties中获取配置
        String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);
        // 有一个匹配上就ok
        for (String havingValue : values) {
            if (propertyValue.equalsIgnoreCase(havingValue)) {
                return true;
            }
        }
        return false;
    }
}
Map<String, Object> annotationAttributes = annotatedTypeMetadata.getAnnotationAttributes(RequestMapping.class.getName()); 获取类中注解上的参数
String propertyValue = conditionContext.getEnvironment().getProperty(propertyName);获取application.properties上的配置。

3.使用注解

@RequestMapping(value ="/Demo")
@Controller
@ResponseBody
@Api("demo测试类")
@CustomProperty(name = "condition",havingValue = {"2"})
public class DemoController {
}

注解会在在spring boot启动时加载。

3.使用HandlerMethodArgumentResolver

注意该方法只能接受get请求。

1.添加注解类

package com.airboot.bootdemo.config;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserCheck {
    //当前用户在request中的名字
    String value() default "userid";
}

2.定义类HandlerMethodArgumentResolver

其中resolveArgument中返回的是controllor的参数,也就是这个方法里面可以组装入参 。UserCheckMethodArgumentResolver 为拦截注解后的逻辑

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class UserCheckMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return false;
    }
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        return null;
    }
}
import com.airboot.bootdemo.entity.DemoVO;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class UserCheckMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.getParameterType().isAssignableFrom(DemoVO.class) && parameter.hasParameterAnnotation(UserCheck.class)) {
            return true;
        }
        return false;
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        UserCheck currentUserAnnotation = parameter.getParameterAnnotation(UserCheck.class);
        //获取head中的
        String userId = webRequest.getHeader("userId");
        //组装入参 return后的参数 类型需要和controllor中的相同 
        DemoVO demoVO = new DemoVO();
        demoVO.setId(Long.valueOf(1));
        return demoVO;
    }
}

3.使用注解

    @GetMapping(value = "/selectDemoByVo")
    public List<DemoVO> selectDemoByVo(@RequestBody @UserCheck DemoVO demoVO) {
        return demoService.selectDemoVO(demoVO);
    }

4.基于aop

该方式在日常最常用。主要使用了AOP的功能。

1.AOP的名词介绍

1.JoinPoint  
* java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 
* Signature getSignature() :获取连接点的方法签名对象; 
* java.lang.Object getTarget() :获取连接点所在的目标对象; 
* java.lang.Object getThis() :获取代理对象本身; 
2.ProceedingJoinPoint  
* ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法: 
* java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法; 
* java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。 

2.添加注解类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    int p0() default 0;
    String p1() default "";
    Class<?> clazz();
}

3.切面配置

其中切点要切到注解类的路径。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Aspect
@Component
public class TestAspect {
    // 切入点签名
    @Pointcut("@annotation(com.airboot.bootdemo.config.TestAnnotation)")
    private void cut() {
    }
    // 前置通知
    @Before("cut()")
    public void BeforeCall() {
        log.info("====前置通知start");
        log.info("====前置通知end");
    }
    // 环绕通知
    @Around(value = "cut()")
    public Object AroundCall(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("====环绕通知start");
        // 注解所切的方法所在类的全类名
        String typeName = joinPoint.getTarget().getClass().getName();
        log.info("目标对象:[{}]", typeName);
        // 注解所切的方法名
        String methodName = joinPoint.getSignature().getName();
        log.info("所切方法名:[{}]", methodName);
        StringBuilder sb = new StringBuilder();
        // 获取参数
        Object[] arguments = joinPoint.getArgs();
        for (Object argument : arguments) {
            sb.append(argument.toString());
        }
        log.info("所切方法入参:[{}]", sb.toString());
        // 统计方法执行时间
        long start = System.currentTimeMillis();
        //执行目标方法,并获得对应方法的返回值
        Object result = joinPoint.proceed();
        log.info("返回结果:[{}]", result);
        long end = System.currentTimeMillis();
        log.info("====执行方法共用时:[{}]", (end - start));
        log.info("====环绕通知之结束");
        return result;
    }
    // 后置通知
    @After("cut()")
    public void AfterCall() {
        log.info("====后置通知start");
        log.info("====后置通知end");
    }
    // 最终通知
    @AfterReturning("cut()")
    public void AfterReturningCall() {
        log.info("====最终通知start");
        log.info("====最终通知end");
    }
    // 异常通知
    @AfterThrowing(value = "cut()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        throw new RuntimeException(ex);
    }
}

4.使用注解

    @RequestMapping(value = "/selectDemoByVo")
    @TestAnnotation(p0 = 123, p1 = "qaws",clazz = DemoVO.class)
    public List<DemoVO> selectDemoByVo(@RequestBody DemoVO demoVO) {
        return demoService.selectDemoVO(demoVO);
    }

5.拦截器

1.添加注解类

import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptorAnnotation {
    String[] value() default {};
    String[] authorities() default {};
    String[] roles() default {};
}

2.实现拦截器

主要原理就是拦截所有的请求,然后判断方法上是否有自定的注解,如果有注解,执行注解的操作。

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class TestAnnotationInterceptor extends HandlerInterceptorAdapter {
    // 在调用方法之前执行拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 将handler强转为HandlerMethod, 前面已经证实这个handler就是HandlerMethod
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 从方法处理器中获取出要调用的方法
        Method method = handlerMethod.getMethod();
        // 获取出方法上的自定义注解
        InterceptorAnnotation access = method.getAnnotation(InterceptorAnnotation.class);
        if (access == null) {
            // 如果注解为null,没有注解 不拦截
            return true;
        }
        //获取注解值
        if (access.authorities().length > 0) {
            // 如果权限配置不为空, 则取出配置值
            String[] authorities = access.authorities();
        }
        // 拦截之后应该返回公共结果, 这里没做处理
        return true;
    }
}

3.注册拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TestAnnotationInterceptor()).addPathPatterns("/**");
    }
}

4.使用注解

    @RequestMapping(value = "/selectDemoByVo")
    @InterceptorAnnotation(authorities = {"admin"})
    public List<DemoVO> selectDemoByVo(@RequestBody DemoVO demoVO) {
        return demoService.selectDemoVO(demoVO);
    }

6.ConstraintValidator注解实现验证

此方法大多是验证入参格式使用。

1.添加注解类

其中message是返回值,groups()和payload() 是必须有的。@Constraint(validatedBy = TestConstraintValidator.class) 是处理注解逻辑的类。

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = TestConstraintValidator.class)
public @interface TestConstraintAnnotation {
    String message() default "入参大小不合适";
    long min();
    long max();
    boolean required() default true;
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2.逻辑类

需要实现ConstraintValidator<TestConstraintAnnotation,  Object>,第一个参数是注解类,第二个参数是入参类型。只有第一次调用时才会调用initialize  ,如果满足isValid逻辑,那么就正常执行,不满足会有message提示。

public class TestConstraintValidator implements ConstraintValidator<TestConstraintAnnotation, Object> {
    private long max = 1;
    private long min = 1;
    @Override
    public void initialize(TestConstraintAnnotation constraintAnnotation) {
        max = constraintAnnotation.max();
        min = constraintAnnotation.min();
    }
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        if(o == null){
            return true;
        }
        if(Long.valueOf(o.toString())>=min && Long.valueOf(o.toString())<=max){
            return true;
        }
        return false;
    }
}

3.使用注解

vo中在需要验证的参数上加上自定义的注解,在方法接收参数前加入@Valid 说明本方法需要验证。

    @RequestMapping(value = "/selectDemoByVo")
    public List<DemoVO> selectDemoByVo(@Valid @RequestBody DemoVO demoVO) {
        return demoService.selectDemoVO(demoVO);
    }
    @TestConstraintAnnotation(min = 1, max = 10)
    private Long id;


相关文章
|
28天前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
131 0
|
28天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
147 2
|
28天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
43 0
|
30天前
|
程序员 开发者 Python
深度解析Python中的元编程:从装饰器到自定义类创建工具
【10月更文挑战第5天】在现代软件开发中,元编程是一种高级技术,它允许程序员编写能够生成或修改其他程序的代码。这使得开发者可以更灵活地控制和扩展他们的应用逻辑。Python作为一种动态类型语言,提供了丰富的元编程特性,如装饰器、元类以及动态函数和类的创建等。本文将深入探讨这些特性,并通过具体的代码示例来展示如何有效地利用它们。
32 0
|
3月前
|
域名解析 网络协议 API
【API管理 APIM】APIM集成内部VNet时,常遇见的关于自定义DNS服务问题。
【API管理 APIM】APIM集成内部VNet时,常遇见的关于自定义DNS服务问题。
|
4月前
|
负载均衡 Java Spring
@EnableFeignClients注解源码解析
@EnableFeignClients注解源码解析
79 14
|
3月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
59 0
|
3月前
|
开发者 容器 Java
Azure云之旅:JSF应用的神秘部署指南,揭开云原生的新篇章!
【8月更文挑战第31天】本文探讨了如何在Azure上部署JavaServer Faces (JSF) 应用,充分发挥其界面构建能力和云平台优势,实现高效安全的Web应用。Azure提供的多种服务如App Service、Kubernetes Service (AKS) 和DevOps简化了部署流程,并支持应用全生命周期管理。文章详细介绍了使用Azure Spring Cloud和App Service部署JSF应用的具体步骤,帮助开发者更好地利用Azure的强大功能。无论是在微服务架构下还是传统环境中,Azure都能为JSF应用提供全面支持,助力开发者拓展技术视野与实践机会。
18 0
|
3月前
|
前端开发 开发者
Vaadin Grid的秘密武器:打造超凡脱俗的数据展示体验!
【8月更文挑战第31天】赵萌是一位热爱UI设计的前端开发工程师。在公司内部项目中,她面临大量用户数据展示的挑战,并选择了功能强大的Vaadin Grid来解决。她在技术博客上分享了这一过程,介绍了Vaadin Grid的基本概念及其丰富的内置功能。通过自定义列和模板,赵萌展示了如何实现复杂的数据展示。
41 0
|
3月前
|
SQL 开发框架 .NET
深入解析Entity Framework Core中的自定义SQL查询与Raw SQL技巧:从基础到高级应用的全面指南,附带示例代码与最佳实践建议
【8月更文挑战第31天】本文详细介绍了如何在 Entity Framework Core (EF Core) 中使用自定义 SQL 查询与 Raw SQL。首先,通过创建基于 EF Core 的项目并配置数据库上下文,定义领域模型。然后,使用 `FromSqlRaw` 和 `FromSqlInterpolated` 方法执行自定义 SQL 查询。此外,还展示了如何使用 Raw SQL 进行数据更新和删除操作。最后,通过结合 LINQ 和 Raw SQL 构建动态 SQL 语句,处理复杂查询场景。本文提供了具体代码示例,帮助读者理解和应用这些技术,提升数据访问层的效率和灵活性。
177 0

推荐镜像

更多