java - 反射,注解

反射的笔记:看注解之前必须掌握反射

https://www.cnblogs.com/clamp7724/p/11655237.html

https://www.cnblogs.com/clamp7724/p/11658557.html

注解:

注解的作用:

1.作为注释使用   只是提示,没有实际意义

2.校验                 提示代码错误,比如@override会校验下面的方法是不是正确重写了父类方法,如果有错会在编译前显示出来

3.携带一些信息   作为容器携带信息,类似变量?

 

注解的使用:

package AnnotationTest;


import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class AnnotationClass {
    //注解的使用格式:
    //@注解

    //注解的位置:
    //类,构造方法,方法,属性的上面

    //注解的作用:
    //1 作为注释使用
    //2 校验
    //比如下面Override就表示这个是重写的方法,如果方法格式不对,会编译错误(有些编译器里Override下面会有红线),让我们知道不符合重写规范。
    @Override
    public String toString() {
        return super.toString();
    }

    //3.携带一些信息
    @SuppressWarnings({"unused","serial"})
    String s = "aaa";
    //SuppressWarning里面加String[],可以用来消除一些警告,不建议使用,因为警告一般都是表示有编译问题
    // 比如加unused表示下面的变量没有被使用
    // serial 继承Serializable版本不添加序列号
    //deprecation  方法过时
    // uncheck  不要检查  泛型问题不检测 (偶尔会用)
    // all  不警告所有问题 (最好别用)


    //自己的注解的使用
//注解结构: 具体看注解类定义中的注释
//@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) //能在属性,方法,构造方法前使用,还有其他元素,用ElementType.获取 //@Retention(RetentionPolicy.RUNTIME) //运行时使用,还有源代码时,编译时,用RetentionPolicy.获取 //@Inherited //注解能被继承 //@Documented //能被变为文档, 不常用 //public @interface MyAnnotation { //结构类似interface(接口) // public static final String s = "aaa"; // public abstract String test(); //} //如果自定义的注解中有方法,使用时需要给方法传值。 方法名 = 返回值类型的值 //注解作为容器把传的值输出给别人,方法需要自己赋值,属性定义注解时已经赋值了 //如果只有一个方法,方法名叫value可以省略方法名直接写值:比如java自带的注解SuppressWarnings,里面只有一个方法 String[] value(); @MyAnnotation(testInt = 1, testString = "aaa", testStringArray = {"aaa","bbb","ccc"}) private String str; public static void main(String[] args){ try { //注解的应用: 利用反射 Class c1 = AnnotationClass.class; //获取类 Field f1 = c1.getDeclaredField("str"); //根据属性名字获取有注解的属性 //获取注解,解析内容 MyAnnotation ma1 = f1.getAnnotation(MyAnnotation.class); int value1_1 = ma1.testInt(); String value1_2 = ma1.testString(); String[] value1_3 = ma1.testStringArray(); System.out.println("value1 = " + value1_1 + "value2 = " + value1_2 + "value3[0] = " + value1_3[0] ); //value1 = 1value2 = aaavalue3[0] = aaa //感觉作用像全局变量。。。 //完全利用反射进行解析内容 Class c2 = AnnotationClass.class; //获取类 Field f2 = c2.getDeclaredField("str"); //根据属性名字获取有注解的属性 Annotation ma2 = f2.getAnnotation(MyAnnotation.class); Class clazz2 = ma2.getClass(); Method m2_1 = clazz2.getDeclaredMethod("testInt"); //一般为了方便使用,注解一般内部都是只有一个String[] value 方法,这样这里就可以统一处理了 int value2_1 = (int) m2_1.invoke(ma2); Method m2_2 = clazz2.getDeclaredMethod("testString"); String value2_2 = (String) m2_2.invoke(ma2); Method m2_3 = clazz2.getDeclaredMethod("testStringArray"); String[] value2_3 = (String[]) m2_3.invoke(ma2); System.out.println("value1 = " + value2_1 + "value2 = " + value2_2 + "value3[0] = " + value2_3[0] ); //value1 = 1value2 = aaavalue3[0] = aaa } catch (Exception e) { e.printStackTrace(); } } }

 

 

自定义的一个注解

package AnnotationTest;

import java.lang.annotation.*;

//自定义注解:
//注解中只能包含如下类型信息:
//基本类型
//String
//枚举 enum
//注解  @
//以上4种类型的数组 []

//自定义注解使用 @interface, 需要添加元注解(java自带注解,用来说明注解)
//元注解Target,说明当前注解可以使用的位置,里面是个enum
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})  //属性,方法,构造方法

//元注解Retention,描述当前注解存在于什么作用域中
// 源文件.java   --编译--     字节码文件.class    ---执行--    内存
// 源文件:注解只用来作为注释        SOURCE
// 字节码文件:编译时使用,可以检测  CLASS
// 内存:用来执行                    RUNTIME
@Retention(RetentionPolicy.RUNTIME)   //运行时使用

@Inherited //注解能被继承

@Documented  //能被变为文档, 不常用

//注解内部结构与interface基本一致
public @interface MyAnnotation {

    //可以描述public static final的属性,不写修饰符也可以,默认为public static final
    public static final String s = "aaa";

    //方法类型为 public abstract, 没有方法体,public abstract可以省略
    //必须有返回值(interface中的方法可以用void,注解不行)
    //注解的方法主要为了动态传递信息
    public abstract int testInt();

    public abstract String testString();

    public abstract String[] testStringArray();
}

 

//注解的应用和优点,看一个例子:看不懂看我之前写的反射的笔记。。。

模拟一个场景,两个公司合作开发项目,A写底层,B用A的底层生成对象来使用

客户突然有一天说要加属性,A把底层Class改了 (javaBean),属性个数不同了,构造方法也要变了。

按传统情况,B就不得不把所有上层中用到这种class的地方全改一遍!万一这个工程非常大,上百个class,工作量将会很可怕,而且容易遗漏。

这时候就要用到Spring的思想:IOC控制反转(A的创建交给别人,不用new 构造方法的方式创建), Di依赖注入(不是用new 对象,然后调用方法的方式赋值),这样A的class的结构改变了也不会影响到B

大幅降低耦合度,让A修改class对B的影响降到最低。

 

写一个注解用来存值传值

package AnnotationTest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnAnnotation {
    String[] value();
}

 

A的底层类:  这里用注解@的方法存值,这样可以方便开发的时候修改,测试一类的。

package AnnotationTest;



public class ObjectTest {

    private String name;
    private Integer age;
    private String sex;

    @AnAnnotation({"一个名字","24", "男"})
    public ObjectTest(){
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

 

B用来生成A所写class的对象的方法 

package AnnotationTest;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.text.Format;

public class AnnotationTest {
    public static void main(String[] args){
        AnnotationTest at = new AnnotationTest();
        Object obj = at.createObject("AnnotationTest.ObjectTest");
        ObjectTest objectTest = (ObjectTest)obj;//泛型
        System.out.println("名字为:" + objectTest.getName());
        System.out.println("年龄为:" + objectTest.getAge());
        System.out.println("性别为:" + objectTest.getSex());


        //那么问题来了。。。这么干的好处是什么呢!
        //可以试着把ObjectTest的Class类修改一下,比如把性别sex和相关的getSex,setSex方法去掉,修改注解@,下面这个生成对象的方法依旧可以使用,不用修改任何代码。
        //如果在项目中,上百个class互相之间相互调用,一旦一个class发生改变(比如客户要求添加数据库新字段),传统方法就不得不把所有用到这个类的对象的地方全重新改一遍,
        // 而用反射就可以只改这个class而不用改其他地方了。
        //这样可以减少类之间的耦合度,ObjectTest修改对AnnotationTest的影响大幅降低。
    }

    //ioc
    //写一个方法,根据class名字利用构造方法上面的注解传值,直接生成对象
    public Object createObject(String objectClassName){
        Object obj = null; //用于返回生成的对象
        try {
            Class c = Class.forName(objectClassName);//获得生成对象的class
            Constructor constructor = c.getConstructor();//获得构造函数

            AnAnnotation annotation = (AnAnnotation)constructor.getAnnotation(AnAnnotation.class);//获取注解内的值 -> object的属性值
            String[] values =  annotation.value();

            obj = constructor.newInstance(); //利用无参数构造方法生成object对象
            Field[] fields = c.getDeclaredFields(); //获得class的所有属性
            //给属性赋值
            for(int i = 0; i < fields.length; i++){
                System.out.println("--------------开始处理第" + i +"个属性---------------");
                Class fieldType = fields[i].getType();//获取属性类型

                //获取set方法名 set + 属性名首字母大写
                String firstFieldName = fields[i].getName().substring(0,1).toUpperCase();
                String lastFieldName = fields[i].getName().substring(1);
                StringBuilder setMethodName = new StringBuilder("set");
                setMethodName.append(firstFieldName);
                setMethodName.append(lastFieldName);

                System.out.println("获得set方法:" + setMethodName);

                Method setMethod = c.getMethod(setMethodName.toString(), fieldType); //用set方法名和属性类型获得set方法

                Constructor constructorField = fieldType.getConstructor(String.class); //获取set方法参数的类型的String为参数的构造函数,因为values[]中值都为String

                setMethod.invoke(obj,constructorField.newInstance(values[i])); //运行set方法,把values[]中对应的值传给obj中对应的方法,比如Integer类型的,就用 Integer("24")来把String变成Integer
                System.out.println("第一个属性赋值:" + fields[i].getName() + " = " + values[i] );
            }

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

        System.out.println("---------------Object赋值结束------------------");
        return obj; //把创建赋值完毕的object返回
    }
}

 

输出结果:

--------------开始处理第0个属性---------------
获得set方法:setName
第一个属性赋值:name = 一个名字
--------------开始处理第1个属性---------------
获得set方法:setAge
第一个属性赋值:age = 24
--------------开始处理第2个属性---------------
获得set方法:setSex
第一个属性赋值:sex = 男
---------------Object赋值结束------------------
名字为:一个名字
年龄为:24
性别为:男

 

 

 

 

 

想体会一下反射的好处,可以修改一下ObjectTest类

posted @ 2019-10-13 19:03  不咬人的兔子  阅读(229)  评论(0编辑  收藏  举报