基于反射机制的多种实现方式的自定义注解,来实现特定的功能

反射

反射的应用场景是非常多的,框架的底层大多都会涉及到反射,包括我们现在要谈的自定义注解,所以在开始之前,先对反射进行一个基础的介绍。

什么是反射?

官方解释:JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制

详细原理,大家也看下这篇文章,在原理上面说的还是较为详细的Java反射的原理解析及使用

反射常用方法操作解析

直接上代码,我在代码上面有注释,主要是反射操作构造器、方法、属性的常见方法,感兴趣的可以看下

@org.junit.Test
public void reflect() {
        Person person = new Person();
        person.setName("abc");
        person.setDesc("你真棒");
        person.setIdCard("321");

        // 获取类的class信息
        Class<? extends Person> personClass = person.getClass();

        // 获取该类的所有public的构造器
//        Constructor<?>[] constructors = personClass.getConstructors();
        // 获取该类的所有构造器
        Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
        System.err.println("----------操作Constructor-------------");
        Arrays.stream(declaredConstructors).forEach(constructor -> System.err.println("modifiers:" + Modifier.toString(constructor.getModifiers()) + ",name:" + constructor.getName()));

//        // 获取public 修饰的方法
//        Method[] methods = personClass.getMethods();
        // 获取class中public、protected,default、private修饰,但是除了继承的方法
        Method[] declaredMethods = personClass.getDeclaredMethods();

        System.err.println("----------操作Method-------------");
        Arrays.stream(declaredMethods).forEach(method -> {
            // 这里打印的结果可以看到,继承CustomCheckBase的check方法名,并未被打印
            System.err.println(method.getName());
            // 方法设置为可使用的,这样的话,我们就可以操作private这些方法,而不单单是public修饰的
            method.setAccessible(true);

            if (method.getName().startsWith("set")) {
                try {
                    // 通过invoke来执行set的method方法,设置新的值
                    method.invoke(person, "a");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } else if (method.getName().startsWith("get")) {
                Object invoke = null;
                try {
                    // 通过invoke来执行get的method方法,得到此刻的值
                    invoke = method.invoke(person);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                System.err.println(invoke.toString());
            }
        });
        System.err.println(person);


        System.err.println("----------操作Field-------------");
//        // public修饰的属性值
//        Field[] fields = personClass.getFields();
        // 所有的属性值,除继承的属性值
        Field[] declaredFields = personClass.getDeclaredFields();
        Arrays.stream(declaredFields).forEach(field -> {
            // 获取属性值的名称
            System.err.println(field.getName());
            field.setAccessible(true);
            try {
                // 获取属性值的值
                System.err.println(field.get(person));
                // 设置属性值的具体值
                field.set(person, "123");

            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });

    }

注解

注解简介

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。在聊自定义注解前,先对注解的进行基本的介绍。

java元注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

使用过springboot的朋友们对上面的代码很熟悉吧,下面三个注解主要涉及到springboot的运行原理、加载机制,这个不是我们本篇讨论的范围,主要是聊下上面四个JAVA的元注解。

@Target

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方),主要是通过 枚举类 ElementType类指定的,像ElementType.TYPE 用于描述类、接口(包括注解类型) 或enum声明 Class, interface (including annotation type), or enum declaration

@Retention

@Retention作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.

从注释上看:

source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略

class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期

runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

@Documented

在生成javadoc的时候就会把注解给显示出来。
总结:
该注解在javadoc中显示出来
该注解一直存在(生命周期)
该注解作用在注解上

@Inherited

@Inherited是一个标识,用来修饰注解
作用:如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解,即:A使用了XX注解,XX注解若被@Inherited修饰,则A的子类都将默认使用了XX注解

自定义注解

annotation是挺简单的东西.其实就是个声明。然后通过反射去取那些声明了值,我们写出来一个方法,来通过反射,执行自定义注解的功能,那么什么是否会调用呢,难道还要我们通过代码显示的调用一次吗?也太low了吧,spring提供的注解可不是这样,直接一个注解,即可使用该注解提供的功能。于是我们先聊下spring使用注解的方式,spring首先是通过动态代理来生成代理类bean,通过注解反射得到相应的方法,所以spring实现像@Transaction注解时,通过aop面向切面编程,在调用method.invoke()方法,前置、后置方法对应开启事务、提交事务,即可实现具体的功能。

照猫画虎,依附与spring的aop模式,第一种自定义注解的实现方式就来了

一、声明自定义注解

二、通过切面类来执行注解实现,pointCut切入点就是自定义注解;AOP对应的通知:
前置通知Before advice:
在目标方法执行之前执行
后置通知After returning advice:
在目标方法执行之后执行
异常通知After throwing advice:
目标方法执行后抛出异常才执行
最终通知After (finally) advice:
在目标方法执行之后 都会执行的通知

在这些通知上执行我们的注解逻辑代码,即可完成我们想要的自定义注解功能

该自定义注解CheckParamNotNot 的功能:作用于方法上,判断方法入参是否为空或者是空字符串

import java.lang.annotation.*;

/**
 * 作用于方法上,判断方法的入参都不为空
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CheckParamNotNot {
}
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class MethodCheckParamDefine {

    // 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
    @Pointcut("@annotation(com.fss.custom.annotation.CheckParamNotNot)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        Arrays.stream(args).forEach(o -> {
            if(null == o || StringUtils.isBlank(o.toString())){
                throw new RuntimeException("参数异常");
            }
        });

        return joinPoint.proceed();
    }

}
import com.fss.custom.annotation.CheckParamNotNot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RequestMapping("rest")
@RestController
public class AnnotationController {

    @CheckParamNotNot
    @GetMapping("hello")
    public String helloAnnaotation(String name, String json){
        System.err.println("name:"+name+",json:"+json);
        return "SUCCESS";
    }
}

 在方法上面添加@CheckParamNotNot注解,即可使用注解带来的功能增强

第二种自定义注解方式实现

虽然spring在我们日常开发中必不可少,然而注解时JAVA提供的功能,不应该被Spring束缚住,抛开Spring,有其他方式来实现自定义注解的执行吗?

其实核心问题就在于,使用注解时会自动调用注解的具体实现逻辑,帮助我们增强代码功能,Spring的AOP刚好符合,抛开Spring,好像只能我们添加一行代码,来执行注解的逻辑了,这样显然也不是很好,可以考虑使用继承、实现的方式来实现

下面实现的自定义注解,功能主要是判断某个类的某个属性是否是null、是字符串】是数值的功能。

/**
 * 自定义check注解:判断非空、非字符、非数字等,最好可根据参数判断
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CustomCheck {
    String field();

    /**
     * 1:非空;2:非字符;3:非数字
     *
     * @return
     */
    char type() default '1';
}
import lombok.Data;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@CustomCheck(field = "name", type = '2')
@Component
@Data
public class Person implements Serializable, CustomCheckBase {

    private static final long serialVersionUID = -2762743486784007569L;

    private Person(String idCard) {

    }

    public Person() {
        check(this);
    }

    // transient 修饰的参数,不会被序列化后,映射出具体值
    private transient String idCard;
    private String name;
    private String desc;
    
}

 

import java.lang.reflect.Field;

public interface CustomCheckBase {

    // 自定义注解的校验逻辑核心代码
    default void check(CustomCheckBase customCheckBase) {
        try {
            Class<? extends CustomCheckBase> customCheckClass = customCheckBase.getClass();
            if (customCheckClass.isAnnotationPresent(CustomCheck.class)) {
                CustomCheck customCheck = customCheckClass.getAnnotation(CustomCheck.class);
                String checkField = customCheck.field();
                char type = customCheck.type();

                Field classField = customCheckClass.getDeclaredField(checkField);
                classField.setAccessible(true);
                Object field = classField.get(customCheckBase);
                if (field == null) {
                    throwCheckException('0');
                }

                boolean flag = false;
                switch (type) {
                    case '1':
                        flag = field == null;
                        break;
                    case '2':
                        flag = checkIsString(field);
                        break;
                    case '3':
                        flag = checkIsNumber(field);
                        break;
                    default:
                        break;
                }
                if (flag) {
                    throwCheckException(type);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    default boolean checkIsNumber(Object classField) {
        try {
            Long.valueOf(classField.toString());
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    default boolean checkIsString(Object field) {
        return String.class == field.getClass();
    }

    default void throwCheckException(char type) {
        String msg = null;
        if ('1' == type) {
            msg = "为空";
        } else if ('2' == type) {
            msg = "是字符串";
        } else if ('3' == type) {
            msg = "是数字";
        } else if ('0' == type) {
            msg = "can not null";
        } else {
            msg = "未知异常";
        }
        throw new RuntimeException(msg);
    }
}

通过这种方式,当使用自定义注解@CustomCheck时,只需要创建的类实现CustomCheckBase,构造器调用下check(this);即可,或者,想要check时,显式person.check(person)调用,这种方式,虽然可以实现自定义注解,但是和通过Spring的方式来实现,还是有一定的不足。主要是在构造器调用check()还是很不方便的,有一定的代码侵入。大家有什么其他的、更好的实现自定义注解的方式,欢迎交流!!!

三、通过拦截器HandlerInterceptor的preHandle来判断

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旺仔丷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值