java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > 自定义注解基本概念和使用

自定义注解基本概念和使用方式

作者:?abc!

这篇文章主要介绍了自定义注解基本概念和使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

1. 概念

1.1 元注解

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明

Java5.0定义的元注解:java.lang.annotation包

1.1.1 Target

描述了注解修饰的对象范围

取值在java.lang.annotation.ElementType定义,常用的包括:

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
}

1.1.1.1 示例

注解Table 可以用于注解类、接口(包括注解类型) 或enum声明

注解NoDBColumn仅可用于注解类的成员变量。

@Target(ElementType.TYPE)
public @interface Table {
    /**
     * 数据表名称注解,默认值为类名称
     * @return
     */
    public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {

}

1.1.2 Retention

表示注解保留时间长短

取值在java.lang.annotation.RetentionPolicy中,取值为:

只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。

1.1.2.1 示例

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField"; 
    public boolean defaultDBValue() default false;
}

1.1.3 Documented

描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。

Documented是一个标记注解,没有成员

表示是否将此注解的相关信息添加到javadoc文档中

1.1.4 Inherited

定义该注解和子类的关系,使用此注解声明出来的自定义注解,在使用在类上面时,子类会自动继承此注解,否则,子类不会继承此注解。

注意:

1.1.4.1 示例

@Inherited
public @interface Greeting {
    public enum FontColor{ BULE,RED,GREEN};
    String name();
    FontColor fontColor() default FontColor.GREEN;
}

1.2 自定义注解

@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数

1.2.1 使用格式

public @interface 注解名 {定义体}

1.2.2 支持数据类型

注解参数可支持数据类型:

Annotation类型里面的参数该怎么设定:

例如,String value();这里的参数成员就为String;

如果只有一个参数成员,最好把参数名称设为"value",后加小括号.

例:下面的例子FruitName注解就只有一个参数成员。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}

1.2.3 注解元素的默认值

注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null

1.3 为什么要使用自定义注解

语义清晰:自定义注解可以使代码的意图更加明确和可读。

例如,使用 @Transactional 注解可以清晰地表明某个方法需要事务支持,而不需要查看AOP配置或切面代码。

2. 使用注意

2.1 不生效情况

保留策略不正确:注解可能在运行时不可见。

目标元素不正确:目标元素(Target Element)设置不正确,注解可能无法应用到期望的程序元素上。

未启用AOP:如果使用AOP来处理注解,但未启用AOP支持,注解处理逻辑将不会生效

解决方法:确保在Spring Boot应用的主类上添加 @EnableAspectJAutoProxy 注解。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Spring Boot的自动配置机制会根据类路径中的依赖和配置文件中的属性自动配置许多常见的功能。

例如,spring-boot-starter-aop 依赖会自动启用AOP支持

切面未被Spring管理:如果切面类未被Spring管理,AOP切面将不会生效。

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {
    // 切面逻辑
}

注解处理逻辑有误:如果注解处理逻辑有误,注解可能不会按预期生效。

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Method method = MyClass.class.getMethod("myMethod");
        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
        if (annotation != null) {
            // 处理注解
        }
    }
}

注解未正确应用:如果注解未正确应用到目标元素上,注解将不会生效。

public class MyClass {
    @MyAnnotation
    public void myMethod() {
        // 方法实现
    }
}

2.2 其他

自定义注解可以在Java和Spring项目中使用。具体来说:

因此,自定义注解既可以用于纯Java项目,也可以用于Spring项目。具体取决于你的需求和项目类型。

3. 实例

3.1 自定义注解 实现赋值和校验

定义两个注解,一个用来赋值,一个用来校验。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface InitSex {

    enum SEX_TYPE {MAN, WOMAN}

    SEX_TYPE sex() default SEX_TYPE.MAN;
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
@Inherited
public @interface ValidateAge {

    /**
     * 最小值
     */
    int min() default 18;

    /**
     * 最大值
     */
    int max() default 99;

    /**
     * 默认值
     */
    int value() default 20;
}

定义User类

@Data
public class User {

    private String username;

    @ValidateAge(min = 20, max = 35, value = 22)
    private int age;

    @InitSex(sex = InitSex.SEX_TYPE.MAN)
    private String sex;
}

测试调用

public static void main(String[] args) throws IllegalAccessException {
    User user = new User();
    initUser(user);
    boolean checkResult = checkUser(user);
    printResult(checkResult);
}

static boolean checkUser(User user) throws IllegalAccessException {

        // 获取User类中所有的属性(getFields无法获得private属性)
        Field[] fields = User.class.getDeclaredFields();

        boolean result = true;
        // 遍历所有属性
        for (Field field : fields) {
                // 如果属性上有此注解,则进行赋值操作
                if (field.isAnnotationPresent(ValidateAge.class)) {
                        ValidateAge validateAge = field.getAnnotation(ValidateAge.class);
                        field.setAccessible(true);
                        int age = (int) field.get(user);
                        if (age < validateAge.min() || age > validateAge.max()) {
                                result = false;
                                System.out.println("年龄值不符合条件");
                        }
                }
        }

        return result;
}
static void initUser(User user) throws IllegalAccessException {

        // 获取User类中所有的属性(getFields无法获得private属性)
        Field[] fields = User.class.getDeclaredFields();

        // 遍历所有属性
        for (Field field : fields) {
                // 如果属性上有此注解,则进行赋值操作
                if (field.isAnnotationPresent(InitSex.class)) {
                        InitSex init = field.getAnnotation(InitSex.class);
                        field.setAccessible(true);
                        // 设置属性的性别值
                        field.set(user, init.sex().toString());
                        System.out.println("完成属性值的修改,修改值为:" + init.sex().toString());
                }
        }
}

3.2 自定义注解+拦截器 实现登录校验

如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。

定义自定义注解:LoginRequired

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

定义两个简单的接口

@RestController
public class testController {    
    @GetMapping("/sourceA")    
    public String sourceA(){
            return "你正在访问sourceA资源";    
    }
    
    @LoginRequired    
    @GetMapping("/sourceB")    
    public String sourceB(){
            return "你正在访问sourceB资源";    
    }
}

实现spring的HandlerInterceptor 类,重写preHandle实现拦截器,登录拦截逻辑

@Slf4j
public class SourceAccessInterceptor implements HandlerInterceptor {
    @Override    
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("进入拦截器了"); 
        // 反射获取方法上的LoginRequred注解        
        HandlerMethod handlerMethod = (HandlerMethod)handler;        
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);        
        if(loginRequired == null){
            return true;        
        }        
        // 有LoginRequired注解说明需要登录,提示用户登录        
        response.setContentType("application/json; charset=utf-8");        
        response.getWriter().print("你访问的资源需要登录");        
        return false; 
    }    
    @Override    
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {    }    
    @Override    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {    }}

实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
    @Override    
    public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");    
    }
}

3.3 自定义注解+AOP 实现日志打印

切面需要的依赖包

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

自定义注解@MyLog

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

定义切面类

@Aspect // 1.表明这是一个切面类
@Component
public class MyLogAspect {

    // 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
    // 切面最主要的就是切点,所有的故事都围绕切点发生
    // logPointCut()代表切点名称
    @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
    public void logPointCut(){};

    // 3. 环绕通知
    @Around("logPointCut()")
    public void logAround(ProceedingJoinPoint joinPoint){
        // 获取方法名称
        String methodName = joinPoint.getSignature().getName();
        // 获取入参
        Object[] param = joinPoint.getArgs();

        StringBuilder sb = new StringBuilder();
        for(Object o : param){
            sb.append(o + "; ");
        }
        System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());

        // 继续执行方法
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(methodName + "方法执行结束");

    }
}

使用

@MyLog
@GetMapping("/sourceC/{source_name}")
public String sourceC(@PathVariable("source_name") String sourceName){
    return "你正在访问sourceC资源";
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文