反射和自定义注解学习笔记

1、概念

JAVA反射机制:是在运行状态中,

  • 对于任意一个实体类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意方法和属性;
  • 这种动态获取信息以及动态调用对象方法的功能称为 Java语言的反射机制

框架: 半成品项目。我们可以再框架的基础上进行软件开发,简化编码。

反射: 框架的基础,也是框架的灵魂。将类的各个组成部分封装成其他的对象。

反射的好处: 可以在程序运行过程中,操作这些对象,可以提高程序扩展性和复用性,可以解耦。
20200625230359712

2、Class

Class 代表类的实体,在运行的 Java 应用程序中表示类和接口。

测试类

public class User {
    private String name;
    protected Integer age;
    public String telnumber;

    public User() {
    }

    public User(String name, Integer age, String telnumber) {
        this.name = name;
        this.age = age;
        this.telnumber = telnumber;
    }

    public User(String name){
        this.name = name;
    }

    /**
     * 属性
     * @return name
     */
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    protected Integer getAge() {
        return age;
    }

    protected void setAge(Integer age) {
        this.age = age;
    }

    private String getTelnumber() {
        return telnumber;
    }

    private void setTelnumber(String telnumber) {
        this.telnumber = telnumber;
    }

    @Override
    public String toString() {
        return "User{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", telnumber='" + telnumber + '\'' +
            '}';
    }
}

获取 Class(类) 的方式 (3种):

  1. Class userClass1 = User.class;

  2. Class<?> userClass2 = Class.forName(“com.reflection.pojo.User”);

  3. User user = new User();

    Class<?> userClass3 = user.getClass();

public void testGetClass() throws Exception {

    // 一: 获取一个 Class
    Class<User> userClass1 = User.class;
    // getName: 获取类、字段、方法、构造器的名称
    // 获取全类名: com.reflection.pojo.User
    System.out.println(userClass1.getName());
    // 获取不带包名的全类名: User
    System.out.println(userClass1.getSimpleName());

    // 二: 获取一个 Class
    Class<?> userClass2 = Class.forName("com.reflection.pojo.User");
    System.out.println(userClass2.getSimpleName());

    // 三: 获取一个 Class
    User user = new User();
    Class<?> userClass3 = user.getClass();
    System.out.println(userClass3.getSimpleName());
}

1、获得类相关的方法

方法用途
asSubclass(Class clazz)把传递的类的对象转换成代表其子类的对象
getClassLoader()获得类的加载器
getClasses()返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className)根据类名返回类的对象
getName()获得类的完整路径名字
newInstance()创建类的实例
getPackage()获得类的包
getSimpleName()获得类的名字
getSuperclass()获得当前类继承的父类的名字
getInterfaces()获得当前类实现的类或是接口
.class获取当前对象的类
@Test
public void testClassMethod() throws Exception {
    // 初始化: 获取 Class(类) 对象
    Class<User> userClass = User.class;

    // 1.获取包名: package com.reflection.pojo,
    // 这里的包名有个 package 前缀, 有些场景需要注意这点
    System.out.println(userClass.getPackage());

    // 2.创建一个 Class(类) 对象的实例: com.reflection.pojo.User@ba8a1dc
    User user = userClass.newInstance();
    System.out.println(user);

    // 3.获取 Class(类) 的加载器, 为了读取配置文件使用
    // ClassLoader classLoader = userClass.getClassLoader();

    // 返回一个数组: ["Bike"],
    // 4.数组中包含该类中所有的 "公共的" "类和接口" 的对象(类中的类)
    Class<?>[] classes = userClass.getClasses();
    for (Class<?> clazz : classes) {
        System.out.println(clazz.getSimpleName());
    }

    // 返回一个数组: ["Clothes","Bike"],
    // 5.数组中包含该类中所有的 "类和接口" 的对象(包括 "非公共的")(类中的类)
    Class<?>[] declaredClasses = userClass.getDeclaredClasses();
    for (Class<?> clazz : declaredClasses) {
        System.out.println(clazz.getSimpleName());
    }

}

Class 对继承关系的操作

public class Animal {

    private String species;

    public String getSpecies() {
        return species;
    }

    public void setSpecies(String species) {
        this.species = species;
    }
}
public class ChineseUser extends User {

    public void kongFu() {
        System.out.println("中国功夫");
    }
}
public interface MyInterface {

    /**
     * 接口方法: 默认为 public
     */
    void method1();
}
public class User extends Animal implements MyInterface {

    /**
     * 字段
     */
    private String name;
    protected Integer age;
    public String telnumber;

    public User() {
    }

    public User(String name, Integer age, String telnumber) {
        this.name = name;
        this.age = age;
        this.telnumber = telnumber;
    }

    public User(String name){
        this.name = name;
    }

    /**
     * 属性
     * @return name
     */
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    protected Integer getAge() {
        return age;
    }

    protected void setAge(Integer age) {
        this.age = age;
    }

    private String getTelnumber() {
        return telnumber;
    }

    private void setTelnumber(String telnumber) {
        this.telnumber = telnumber;
    }

    public static class Bike {
        private String bikename;
    }

    private static class Clothes {
        private String clothesname;
    }

    @Override
    public void method1() {

    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", telnumber='" + telnumber + '\'' +
                '}';
    }
}

测试

/**
 * 测试 继承与实现 的类
 */
@Test
public void testExtends() {
    Class<User> userClass = User.class;

    // 把传递的 "类的对象(Animal.class)" 转换为代表其 "子类的对象(<? extends Animal> 中的 ?)"
    Class<? extends Animal> aClass = userClass.asSubclass(Animal.class);
    // 获取全类名: com.reflection.pojo.User
    System.out.println(aClass.getName());

    // 获取 "当前类(User.class)" 继承的 "父类的对象(<? super User> 中的 ?)"
    Class<? super User> superclass = userClass.getSuperclass();
    // 获取全类名: com.reflection.pojo.Animal
    System.out.println(superclass.getName());

    // 获取当前类 "实现的类或是接口" 的数组
    Class<?>[] interfaces = userClass.getInterfaces();
    for (Class<?> clazz : interfaces) {
        //获取全类名: com.reflection.pojo.MyInterface, 原因: 仅实现了一个接口.
        System.out.println(clazz.getName());
    }
}

2、获得类中字段相关的方法

方法用途
getField(String name)获得某个公有的字段对象
getFields()获得所有公有的字段对象
getDeclaredField(String name)获得某个字段对象
getDeclaredFields()获得所有字段对象
@Test
public void testClassField() throws Exception {
    Class<User> userClass = User.class;

    // 获取类中的所有 "公共的" 字段对象
    Field[] fields = userClass.getFields();
    for (Field field : fields) {
        // telnumber
        System.out.println(field.getName());
    }

    // 获取类中某个 "公共的" 字段对象
    Field telnumber = userClass.getField("telnumber");
    // telnumber
    System.out.println(telnumber.getName());

    // 获取类中的所有 "任意的" 字段对象(包括私有对象)
    Field[] declaredFields = userClass.getDeclaredFields();
    for (Field field : declaredFields) {
        // name
        // age
        // telnumber
        System.out.println(field.getName());
    }

    // 获取类中 "任意的" 的某个字段对象(包括私有对象)
    Field name = userClass.getDeclaredField("name");
    // name
    System.out.println(name.getName());

}

3、获得类中注解相关的方法

方法用途
getAnnotation(Class annotationClass)返回该类中与参数类型匹配的公有注解对象
@Logger
public class User extends Animal implements MyInterface {
	//...
}
@Test
public void testClassAnnotation() {
    Class<User> userClass = User.class;

    // 获取指定的注解, 可以据此来判断注解是否存在, 去执行对应的逻辑
    Logger annotation = userClass.getAnnotation(Logger.class);
    // @jdk.nashorn.internal.runtime.logging.Logger(name=)
    System.out.println(annotation);

}

4、获得类中构造器相关的方法

方法用途
getConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的公有构造方法
getConstructors()获得该类的所有公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的构造方法
getDeclaredConstructors()获得该类所有构造方法
public class User extends Animal implements MyInterface {

    private String name;
    protected Integer age;
    public String telnumber;

    public User() {
    }

    protected User(String name, Integer age, String telnumber) {
        this.name = name;
        this.age = age;
        this.telnumber = telnumber;
    }

    private User(String name){
        this.name = name;
    }
    // ...
}
@Test
public void testClassConstructor() throws Exception {
    Class<User> userClass = User.class;

    // 获取类中的所有 "公共的" 构造方法
    Constructor<?>[] constructors = userClass.getConstructors();
    for (Constructor<?> constructor : constructors) {
        int parameterCount = constructor.getParameterCount();
        // 0个参数的构造方法: com.reflection.pojo.User
        System.out.println(parameterCount + "个参数的构造方法: " + constructor.getName());
    }
    
    // 获取类中的所有 "任意的" 构造方法(包括私有的)
    Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors();
    for (Constructor<?> constructor : declaredConstructors) {
        int parameterCount = constructor.getParameterCount();
        // 1个参数的构造方法: com.reflection.pojo.User
        // 3个参数的构造方法: com.reflection.pojo.User
        // 0个参数的构造方法: com.reflection.pojo.User
        System.out.println(parameterCount + "个参数的构造方法: " + constructor.getName());
    }

    /*
          根据 "参数类型列表" 获取指定的构造方法
          该方法使用 ... 运算符, 表示不定长参数, 可以是 0 个, 也可以是多个。本质是个数组。
        */
    Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class, Integer.class, String.class);
    // com.reflection.pojo.User : 3
    System.out.println(declaredConstructor.getName() + " : " + declaredConstructor.getParameterCount());
}

5、获得类中方法相关的方法

方法用途
getMethod(String name, Class…<?> parameterTypes)获得该类某个公有的方法
getMethods()获得该类所有公有的方法
getDeclaredMethod(String name, Class…<?> parameterTypes)获得该类某个方法
getDeclaredMethods()获得该类所有方法
@Test
public void testClassGetMethod() throws Exception {
    Class<User> userClass = User.class;

    // 获取类中的所有 "公共的" 方法
    Method[] methods = userClass.getMethods();
    for (Method method : methods) {
        //toString
        //getName
        //setName
        //method1
        //getSpecies
        //setSpecies
        //wait
        //wait
        //wait
        //equals
        //hashCode
        //getClass
        //notify
        //notifyAll
        System.out.println(method.getName());
    }

    // 根据 "名称" 和 "参数类型列表" 获取类中指定的 "公共的" 方法
    Method method = userClass.getMethod("toString");
    // toString
    System.out.println(method.getName());

    // 获取类中 "任意的"  的方法(包括私有方法)
    Method[] declaredMethods = userClass.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        //toString
        //getName
        //setName
        //setAge
        //getAge
        //method1
        //setTelnumber
        //getTelnumber
        System.out.println(declaredMethod.getName());
    }

    // 根据 "名称" 和 "参数类型列表" 获取类中指定的 "任意"  方法(包括私有方法)
    Method declaredMethodsetAge = userClass.getDeclaredMethod("setAge", Integer.class);
    // setAge
    System.out.println(declaredMethodsetAge.getName());
    System.out.println();
    Method declaredMethodgetAge = userClass.getDeclaredMethod("getAge");
    // getAge
    System.out.println(declaredMethodgetAge.getName());
}

6、类中其他重要的方法

方法用途
isAnnotation()如果是注解类型则返回 true
isAnnotationPresent(Class<? extends Annotation> annotationClass)如果是指定类型注解类型则返回 true
isArray()如果是一个数组类则返回 true
isEnum()如果是枚举类则返回 true
isInstance(Object obj)如果 obj 是该类的实例则返回 true
isInterface()如果是接口类则返回 true
@Test
public void testClassOtherMethod() throws Exception {
    Class<User> userClass = User.class;

    // 如果是注解类型返回 true
    System.out.println(userClass.isAnnotation());
    System.out.println(Logger.class.isAnnotation());

    // 如果是接口类型返回 true
    System.out.println(userClass.isInterface());
    System.out.println(MyInterface.class.isInterface());

    // 如果是数组类型返回 true
    System.out.println(userClass.isArray());
    System.out.println(int[].class.isArray());

    // 如果是枚举类型返回 true
    System.out.println(userClass.isEnum());

    // isInstance(Object obj) 判断 obj 是该类的实例
    User user = new User();
    // 如果 userClass 是 User 类的实例返回 true
    System.out.println(userClass.isInstance(user));

    // isAnnotationPresent(Class<? extends Annotation> annotationClass)
    // 判断这个 Class 是否拥有指定注解, 如果是指定类型注解返回 true
    System.out.println(userClass.isAnnotationPresent(Logger.class));
}

3、Field

Field 代表类的成员变量。 成员变量(字段)和 成员属性 是两个概念。

  • 当一个 User 类中有一个 name 变量,那么这个时候就说它有 name 这个字段。
  • 如果没有 getNamesetName 这两个方法,那么这个类就没有 name 属性。
  • 反之,如果这个类拥有 getAgesetAge 这两个方法,不管有没有 age 字段,都认为它有 age 这个属性
方法用途
get(Object obj)获得 obj 中对应的属性值
set(Object obj, Object value)设置 obj 中对应属性值
SetAccessible暴力反射,忽略访问权限修饰符
@Test
public void testFieldGet() throws Exception {
    User user = new User("张三", 23, "233");
    // 反射前: User{name='张三', age=23, telnumber='233'}
    System.out.println("反射前: " + user);

    // 获取 User 的字节码文件
    Class<User> userClass = User.class;
    // 获取 name 字段
    Field field = userClass.getDeclaredField("name");

    // 暴力反射: 忽略访问权限修饰符
    field.setAccessible(true);
    // 获取指定对象当前字段的值: 张三
    System.out.println(field.get(user));
}
@Test
public void testFieldSet() throws Exception {
    User user = new User("张三", 23, "233");
    // 反射前: User{name='张三', age=23, telnumber='233'}
    System.out.println("反射前: " + user);

    // 获取 User 的字节码文件
    Class<User> userClass = User.class;
    // 获取 name 字段
    Field field = userClass.getDeclaredField("name");
    // 暴力反射: 忽略访问权限修饰符
    field.setAccessible(true);
    // 设置指定对象当前字段的值
    field.set(user, "李四");
    // 反射前: User{name='张三', age=23, telnumber='233'}
    System.out.println("反射后: " + user);
}

4、Method

Method 代表类中的方法(不包括构造方法)。

方法用途
invoke(Object obj, Object… args)传递 object 对象及参数调用该对象对应的方法
getName获取方法名
SetAccessible暴力反射,忽略访问权限修饰符

Invoke方法的用处: SpringAOP 在切面方法执行的前后进行某些操作,就是使用的 invoke 方法。动态代理设计模式中使用的也是 invoke。

@Test
public void testMethod() throws Exception {
    User user = new User("张三", 23, "233");
    // 反射前: User{name='张三', age=23, telnumber='233'}
    System.out.println("反射前: " + user);

    // 获取 User 的字节码文件
    Class<User> userClass = User.class;

    // 根据 "名称" 和 "参数类型列表" 获取类中指定的方法(包括私有方法)
    Method method = userClass.getDeclaredMethod("getTelnumber");
    // private java.lang.String com.reflection.pojo.User.getTelnumber()
    System.out.println(method);
    // 暴力反射: 忽略访问权限修饰符
    method.setAccessible(true);
    // 传入 object 对象及参数调用该对象对应的方法, 参数从第二个开始, 是该方法的参数值
    Object value = method.invoke(user);
    // 反射后: 233
    System.out.println("反射后: " + value);

    // 根据 "名称" 和 "参数类型列表" 获取类中指定的方法(包括私有方法)
    Method setTelnumberMethod = userClass.getDeclaredMethod("setTelnumber", String.class);
    setTelnumberMethod.setAccessible(true);
    setTelnumberMethod.invoke(user, "666");
    // 反射后: User{name='张三', age=23, telnumber='666'}
    System.out.println("反射后: " + user);
}

自定义工具类

public final class MethodUtils {

    /**
     * 不可被实例化
     */
    private MethodUtils() {
    }

    /**
     * 执行对象的指定方法
     *
     * @param obj        要执行方法的对象
     * @param methodName 要执行的方法名称
     * @param params     要执行的方法参数
     * @return 返回
     */
    public static Object invokeMethod(Object obj, String methodName, Object... params) {
        Object result = null;

        try {
            long startTime = System.currentTimeMillis();

            // 获取要执行方法对应类的字节码
            Class<?> clazz = obj.getClass();

            System.out.println("类: " + clazz.getName());
            System.out.println("方法: " + methodName);

            if (params == null || params.length == 0) {
                // 获取对应的方法
                Method method = clazz.getDeclaredMethod(methodName);
                // 暴力反射: 忽略访问权限修饰符
                method.setAccessible(true);
                // 传入 object 对象及参数调用该对象对应的方法, 参数从第二个开始, 是该方法的参数值
                result = method.invoke(obj);
            } else {
                // 获取传入的 "方法参数" 大小
                int size = params.length;
                // 存放传入的 "方法参数" 对应的类型字节码,
                // 如: name 为 String 类型, 则存放 String.class
                Class<?>[] classes = new Class[size];
                // 存放传入的 "方法参数"
                Object[] paramValues = new Object[size];

                for (int i = 0; i < params.length; i++) {
                    // 获取传入的 "方法参数" 对应的类型字节码,
                    // 如: name 为 String 类型, 则获取 String.class
                    classes[i] = params[i].getClass();
                    // 获取传入的 "方法参数"
                    paramValues[i] = params[i];
                }

                System.out.println("参数: " + Arrays.toString(paramValues));

                // 获取对应的方法
                Method method = clazz.getDeclaredMethod(methodName, classes);
                // 暴力反射: 忽略访问权限修饰符
                method.setAccessible(true);
                // 传入 object 对象及参数调用该对象对应的方法, 参数从第二个开始, 是该方法的参数值
                result = method.invoke(obj, paramValues);
            }

            System.out.println("返回值: " + result);
            long endTime = System.currentTimeMillis();
            System.out.println("耗时: " + (endTime - startTime));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

测试

@Test
public void testMethodUtils() {
    User user = new User("张三", 23, "233");
    Object result = MethodUtils.invokeMethod(user, "getName");
    // 张三
    System.out.println(result);
    MethodUtils.invokeMethod(user, "setName", "李四");
    System.out.println(user);
}

20200626003222017

5、Constructor(用的比较少)

Constructor 代表类的构造方法。

方法用途
newInstance(Object… initargs)根据传递的参数创建类的对象

Constructor类在实际开发中使用极少,几乎不会使用Constructor。

因为: Constructor 违背了 Java 的一些思想,比如:私有构造不让用户去new对象;单例模式保证全局只有一个该类的实例,而 Constructor 则可以破坏这个规则。

案例:写一个小工具,可以创建任意类的对象,并且执行其中任意方法

案例:通过反射,验证运行中泛型会被擦除

@Test
public void testClassNewInstance() throws Exception {
    Class<User> userClass = User.class;
    // 通过调用空参构造成功创建了对象
    // Class 中的 newInstance 功能简单, 局限性较大
    // 只能通过公共的无参构造去创建对象
    User user = userClass.newInstance();
    // User{name='null', age=null, telnumber='null'}
    System.out.println(user);
}
@Test
public void testConstructor() throws Exception {
    Class<User> userClass = User.class;
    Constructor<User> declaredConstructor = userClass.getDeclaredConstructor(String.class, Integer.class, String.class);

    // Constructor 的 newInstance 可以通过有参构造创建对象
    User user = declaredConstructor.newInstance("张三", 23, "233");
    // User{name='张三', age=23, telnumber='233'}
    System.out.println(user);

    // Constuctor 的 newInstance 还可以通过私有的构造方法去创建对象, 但是极不安全
    Constructor<User> declaredConstructor1 = userClass.getDeclaredConstructor(String.class);
    declaredConstructor1.setAccessible(true);
    User user1 = declaredConstructor1.newInstance("张三");
    // User{name='张三', age=null, telnumber='null'}
    System.out.println(user1);
}

Constructor 对单例模式的破坏

public class Session {

    private Session(){}

    private static Session session = null;

    public static Session getInstance(){
        synchronized (Session.class){
            if (session == null){
                session = new Session();
            }
        }
        return session;
    }
}
public class ObjectTest {

    @Test
    public void testObject() throws Exception {
        Session session1 = Session.getInstance();
        Session session2 = Session.getInstance();
        // true
        System.out.println(session1 == session2);
    }

    @Test
    public void testConstructorNewInstance() throws Exception {
        Class<Session> sessionClass = Session.class;
        Constructor<Session> declaredConstructor = sessionClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);

        Session session1 = declaredConstructor.newInstance();
        Session session2 = Session.getInstance();
        // false
        System.out.println(session1 == session2);
    }
}

通过反射,验证运行中泛型会被擦除

@Test
public void testList() throws Exception {
    List<User> userList = new ArrayList<>();
    userList.add(new User("张三", 23, "233"));
    userList.add(new User("李四", 24, "244"));

    Class<? extends List> listClass = userList.getClass();
    Method addMethod = listClass.getDeclaredMethod("add", Object.class);
    addMethod.invoke(userList, "哈哈哈");
    addMethod.invoke(userList, 123);

    // [User{name='张三', age=23, telnumber='233'}, User{name='李四', age=24, telnumber='244'}, 哈哈哈, 123]
    System.out.println(userList);

    // 证实: 泛型会在运行阶段被擦除, 这个时候像集合一类的对象就可以强制往里面放入非指定泛型的数据
    // 如果一定要这样去使用的话, 建议后面不要进行任何操作, 直接返回给前端.
    // java.lang.ClassCastException: java.lang.String cannot be cast to com.reflection.pojo.User
    for (User user : userList) {
        System.out.println(user);
    }

}

6、注解

概念: 注解就是说明程序的一个标识,给计算机看的。

注释: 用文字描述程序,给程序员看的。

定义: 也叫作元数据,是一种代码级别的说明。它是 JDK1.5 引入的一个特性,是一种特殊的接口。它可以声明在类、字段、方法、变量、参数、包等前面,作为一个描述去使用。

作用分类:

  • 编写文档:通过代码中标识的注解生成文档(Swagger)
  • 代码分析:通过代码里的注解对代码进行分析(逻辑判断)
  • 编译检查:通过代码里对应的注解让编译器实现基本的编译检查(Override,Deprecated,FunctionalInterface)

JDK中预定义的一些注解:

  • Override:检测该注解标识的方法是否继承自父类
  • Deprecated:标识方法、类、字段等已经过时,后续的版本可能会将其移除
  • SuppressWarnings:压制警告

1、自定义注解

在实际开发中,可能存在一些极其复杂或者复用性很低的业务逻辑,比如:导出 Excel、缓存、将返回值转 JSON、事务 等,这个时候就可以使用自定义注解

格式

元注解
Public @interface 注解名称 {
	属性列表
}

本质: 注解本质上是一个接口,该接口事实上默认继承自 Annotation 接口

2、属性

事实上是接口中的抽象方法:

  1. 如果定义了属性,在使用属性的时候需要给属性赋值;如果有默认值,则可以不赋值。

  2. 如果只有一个属性需要赋值,并且这个属性名称是 value,则可以省略 value。

  3. 数组赋值时,需要使用{ }包起来,如果数组中只有一个元素,则大括号可以省略

属性中的返回值类型有下列取值:

  • 基本数据类型

  • String

  • 枚举

  • 注解

  • 以上类型的数组

3、元注解

用于描述注解的注解:

  • @Target: 描述该注解作用范围

    • ElementType取值:
      • Type: 作用于
      • METHOD: 作用于 方法
      • FIELD: 作用于 字段
  • @Retention: 描述注解被保留的阶段

    • RetentionPolicy.RUNTIME: 当前描述的注解,会保留到 class 字节码文件中,并被 JVM 读取到
  • @Documented: 描述注解是否被抽取到 API 文档中

  • @Inherited: 描述注解是否可以被继承

4、测试

自定义注解

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value();

    int num() default 0;

}

使用自定义注解到类上

@MyAnnotation(value = "我是内容")
public class User extends Animal implements MyInterface {

    /**
     * 字段
     */
    @MyAnnotation("我是李四")
    private String name;
    protected Integer age;
    public String telnumber;
	
    // ...
}

测试

@Test
public void testAnnotation() throws Exception {
    Class<User> userClass = User.class;
    MyAnnotation myAnnotation = userClass.getAnnotation(MyAnnotation.class);
    if (myAnnotation != null) {
        // 我是内容 : 0
        System.out.println(myAnnotation.value() + " : " + myAnnotation.num());
    }
}
/**
 * 根据字段上的注解改变字段的值
 *
 * @throws Exception
 */
@Test
public void testAnnotation2() throws Exception {
    // 新建 user 对象
    User user = new User();
    // User{name='null', age=null, telnumber='null'}
    System.out.println(user);

    // 获取 user 对象的字节码文件
    Class<User> userClass = User.class;
    // 获取所有字段
    Field[] declaredFields = userClass.getDeclaredFields();
    
    // 遍历字段数组
    for (Field declaredField : declaredFields) {
        // 获取字段上的注解
        MyAnnotation myAnnotation = declaredField.getDeclaredAnnotation(MyAnnotation.class);
        if (myAnnotation != null){
            // 获取注解的 value 值
            String value = myAnnotation.value();
            declaredField.setAccessible(true);
            // 设置字段值
            declaredField.set(user, value);
        }
    }
    // User{name='我是李四', age=null, telnumber='null'}
    System.out.println(user);
}

5、案例

编写一个缓存注解,该注解用于缓存指定方法的返回值。

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

    /**
     * 缓存的key
     * @return
     */
    String Key();

    /**
     * 默认的过期时间
     * @return
     */
    int timeOut() default 10;

    /**
     * 默认过期时间单位
     * @return
     */
    TimeUnit timeUnit() default TimeUnit.MINUTES;

}
public final class CacheUtils {

    private static Map<String, Object> cacheMap = new ConcurrentHashMap<>();

    private static final String CACHE_SEPARATE = "::";

    /**
     * 不可被实例化
     */
    private CacheUtils() {
    }

    /**
     * 执行对象的指定方法
     *
     * @param obj        要执行方法的对象
     * @param methodName 要执行的方法名称
     * @param params     要执行的方法参数
     * @return 返回
     */
    public static Object invokeMethod(Object obj, String methodName, Object... params) {
        Object result = null;

        try {
            long startTime = System.currentTimeMillis();

            // 获取要执行方法对应类的字节码
            Class<?> clazz = obj.getClass();

            if (params == null || params.length == 0) {
                // 无参数, 以大 Key 为缓存的 key
                // 获取对应的方法
                Method method = clazz.getDeclaredMethod(methodName);
                // 暴力反射: 忽略访问权限修饰符
                method.setAccessible(true);

                Cache cache = method.getAnnotation(Cache.class);
                // 方法上有 Cache 注解才进行缓存
                if (cache != null) {
                    // 获取 Key
                    String key = getCacheKey(cache);
                    Object value = cacheMap.get(key);
                    if (value != null) {
                        return value;
                    }
                }
                // 传入 object 对象及参数调用该对象对应的方法, 参数从第二个开始, 是该方法的参数值
                result = method.invoke(obj);
                // 将数据写入缓存 cacheMap
                if (cache != null) {
                    // 获取 Key
                    String key = getCacheKey(cache);
                    // 将缓存结果放入缓存中
                    cacheMap.put(key, result);
                }
            } else {
                // 获取传入的 "方法参数" 大小
                int size = params.length;
                // 存放传入的 "方法参数" 对应的类型字节码,
                // 如: name 为 String 类型, 则存放 String.class
                Class<?>[] classes = new Class[size];
                // 存放传入的 "方法参数"
                Object[] paramValues = new Object[size];

                for (int i = 0; i < params.length; i++) {
                    // 获取传入的 "方法参数" 对应的类型字节码,
                    // 如: name 为 String 类型, 则获取 String.class
                    classes[i] = params[i].getClass();
                    // 获取传入的 "方法参数"
                    paramValues[i] = params[i];
                }

                // 获取对应的方法
                Method method = clazz.getDeclaredMethod(methodName, classes);
                // 暴力反射: 忽略访问权限修饰符
                method.setAccessible(true);

                Cache cache = method.getAnnotation(Cache.class);
                // 判断是否有 cache 注解
                if (cache != null) {
                    // 获取拼接的 cacheKey
                    String cacheKey = getCacheKey(cache, paramValues[0]);
                    // 从缓存查找数据
                    Object value = cacheMap.get(cacheKey);
                    if (value != null) {
                        return value;
                    }
                }
                // 传入 object 对象及参数调用该对象对应的方法, 参数从第二个开始, 是该方法的参数值
                result = method.invoke(obj, paramValues);

                if (cache != null) {
                    // 获取拼接的 cacheKey
                    String cacheKey = getCacheKey(cache, paramValues[0]);
                    // 将缓存结果放入缓存中
                    cacheMap.put(cacheKey, result);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 拼接 cacheKey
     *
     * @param cache cache.Key() 大 Key
     * @param paramValue 小 key 方法参数列表
     * @return
     */
    private static String getCacheKey(Cache cache, Object... paramValue) {

        StringBuffer key = new StringBuffer();
        // 添加大 Key
        key.append(cache.Key());

        // 拼接缓存 key
        if (paramValue != null) {
            for (Object object : paramValue) {
                key.append(CACHE_SEPARATE);
                key.append(object);
            }
        }
        
        return key.toString();
    }
    
}
public class UserController {

    private User[] users = {
      new User("张三"),
      new User("李四"),
      new User("王五"),
      new User("赵六"),
      new User("田七"),
      new User("王八"),
      new User("老九")
    };

    @Cache(Key = "user")
    private User getUserById(Integer id) {
        System.out.println("查询用户方法执行...");
        return users[id];
    }

    @Cache(Key = "toChache")
    private String getName() {
        System.out.println("toCache ...");
        return "Cache....";
    }
}
@Test
public void cacheTest2() throws Exception {
    UserController userController = new UserController();
    Object toCache1 = CacheUtils.invokeMethod(userController, "getName");
    System.out.println("第一次执行: toCache1 == " + toCache1);

    Object toCache2 = CacheUtils.invokeMethod(userController, "getName");
    System.out.println("第二次执行: toCache2 == " + toCache2);
}

在这里插入图片描述

@Test
public void cacheTest1() throws Exception {
    UserController userController = new UserController();
    Object user1 = CacheUtils.invokeMethod(userController, "getUserById", 1);
    System.out.println("user1 == " + user1);

    Object user2 = CacheUtils.invokeMethod(userController, "getUserById", 1);
    System.out.println("user2 == " + user2);
}

在这里插入图片描述

7、总结

反射: 就是在程序运行中对 Class,对象进行一系列的操作

注解: 注解就是一个标识,相当于是给程序看的注释,注解本身不存在功能,需要通过反射去进行某些判断,根据判断结果去执行对应的逻辑,这个过程就是给注解赋予功能的过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值