利用反射为一个只含有setter和getter普通方法的Person类生成一个MyPerson.java文件。之前没做,现在自己动手打一遍,对反射更加熟练了,动态之美。

描述:我已经事先写好了一个Person类如下

包含各个属性的setter和getter方法,以及构造器,现在我要编写一个Generate类,传入Person类的全路径,通过反射获取Person的结构,并写一个MyPerson.java文件,除了类名改为MyPerson,其余属性,方法与Person类一模一样,也就是复制。

public class Generate {
    private String classFullPath;//存放Person类的全路径
    private Class clazz;//Person类的Class对象
    private StringBuilder sb = new StringBuilder();//整个MyPerson.java文件的内容

    public Generate(String classFullPath) {
        this.classFullPath = classFullPath;
        try {
            clazz = Class.forName(classFullPath);
        } catch (ClassNotFoundException e) {
            System.out.println("传入类的全路径错误");
        }
    }
}

class StringUtil {

    /**
     * 传入全路径:xxx.xxx.name  ,得到名字name
     */
    public static String getName(String fullName) {
        int index = fullName.lastIndexOf(".");
        return fullName.substring(index+1);
    }
}

准备好后,开始编写各个方法

1.packgee 包名以及class 类名{

每一个.java文件第一行都要写当前包名,

    /**
     * 写包名:package 包名
     */
    public void getPackageDesc() {
        String name = clazz.getPackage().toString();//得到:package 包名
//        String name = clazz.getPackage().getName();//得到:包名
        sb.append(name).append(";\n\n");
    }

    /**
     * 写第一行: class 类名 {
     */
    public void getClassDesc() {
        sb.append("class My").append(StringUtil.getName(classFullPath)).append("{\n\n");
    }

通过clazz.getPackage()可以得到一个Package对象,可以print直接打印,输出如下

Class<?> aClass = Class.forName("work01.Person");
        Package aPackage = aClass.getPackage();
        System.out.println("aPackage = "+aPackage);
        System.out.println("aPackage.toString() = "+aPackage.toString());
        System.out.println("aPackage.getName() = "+aPackage.getName());

/*
输出结果:
aPackage = package work01
aPackage.toString() = package work01
aPackage.getName() = work01

*/

package对象直接被打印,底层自动调用本身的toString()方法,得到package 包名

package.getName直接得到 包名

2.得到属性信息

通过因为我们不知道到底有什么属性,所以

通过反射得到属性,比较常用的方法就下面几个:

  1. clazz.getField(fieldName)         获得public的属性的Class对象,名字为fieldName,返回一个Field 对象
  2. clazz.getFields()        获得所有public的属性的Class对象,返回一个 Field[] 数组
  3. clazz.getDeclaredField(fieldName)
  4. clazz.getDeclaredFields()  

3,4与1,2的区别就在于3,4是获得所有属性,而1,2只能得到public修饰的属性,但是要注意 非public 修饰的field对象在使用前要打开访问权限,也就是加一句field.setAccessible(true);

我们通过field.set(obj,vale)以及field.get(obj)来给obj对象的属性赋值或者获得属性的值

修饰符:我们可以通过field.getModifiers()得到属性的修饰符,以int形式返回

修饰符返回的int值
缺省(默认)0
public1
private2
protected4
static8
final16

当存在两个及以上的修饰符时,采用加法,比如public static 就是 1+8=9

Modifier类也提供了toSrting(mod)方法将转化为String类型的修饰符,比如Modifier.toString(17)返回String类型的 "public final"

类型:field.getType()可以得到属性的类型,返回的是一个Type类的Class对象,再调用该对象的getName()方法即可得到属性的类型

Field getAge = aClass.getDeclaredField("age");
        getAge.setAccessible(true);
        Class<?> type = getAge.getType();
        System.out.println(type);
        System.out.println(type.getTypeName());
        System.out.println(type.getName());


/**

结果:
class java.lang.Integer
java.lang.Integer
java.lang.Integer

/*

属性名: 通过field.getName()即可得到属性的名字
 

    /**
     * 写属性
     */
    public void getFieldDesc() {
        Field[] fields = clazz.getDeclaredFields();//得到所有属性
        StringBuilder str;
        for (Field field : fields) {//遍历
            str = new StringBuilder();
            field.setAccessible(true);//开启访问权限
            int modifierMod = field.getModifiers();//得到修饰符
            String modifier = Modifier.toString(modifierMod);//通过特有方法转化为String表示的修饰符
            String typeName = field.getType().getName();
            typeName = StringUtil.getName(typeName);
            String fieldName = field.getName();//得到属性名字
            str.append("\t").append(modifier).append(" ").append(typeName).append(" ").append(fieldName).append(";\n");
            sb.append(str);
        }
    }

 3.得到构造器信息

其实构造器与属性,方法的信息获取方法是一样的,前面你属性都会了,那么构造器的获取修饰符,获取构造器名字,这个肯定也没有问题啦。

获取构造器:

  1. clazz.getConstructor() 
  2.  clazz.getConstructors()
  3. clazz.getDeclaredConstructor()
  4.  clazz.getDeclaredConstructors()     

用法同上面属性field一样。那怎么获取有参数的构造器?括号里面加入参数类型的Class对象即可,比如获取public Person(int age,String name)这个构造器,clazz.getConstructor(int.calss,String.class)  就这么简单。也可以传入一个数组,如下所示。

Constructor<?> constructor = aClass.getConstructor(int.class, String.class);
System.out.println(constructor);

Class[] classes={int.class,String.class};//这里也可以传入一个数组
Constructor<?> constructor1 = aClass.getConstructor(classes);
System.out.println(constructor1);

/*
结果:
public work01.Person(int,java.lang.String)
public work01.Person(int,java.lang.String)

*/

 怎么获取构造函数里的参数

构造器对象:constructer,constructer.getParameters()可以返回一个Parameter[]数组,存放该构造器的每一个参数对象parameter,

我们可以通过parameter.getType().getName()得到每个参数的类型,

通过parameter.getName()得到每一个参数的名字

让我们来尝试下:

Constructor<Person> constructor = aClass.getConstructor(Integer.class, String.class);
        Parameter[] parameters = constructor.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println("名字:"+parameter.getName());
            System.out.println("类型:"+parameter.getType().getName());
        }

/*

打印结果:
名字:arg0
类型:java.lang.Integer
名字:arg1
类型:java.lang.String
*/

  并没有如期所愿,类型倒是正确,但名字却没有得到。在java8以后,我们可以在

1)要在设置-->构建.执行.部署-->编译器-->java编译器 里面的附加命名行形参加入:-parameters

2)删除下面黄色的bin文件,再重新启动就可以获取了

获得真实名字

名字:age
类型:java.lang.Integer
名字:name
类型:java.lang.String

现在最重要的参数信息已经获取到,基本上没问题了,直接上代码:

    /**
     * 写构造器
     */
    public void getConstructorDesc() {
        Constructor[] constructors = clazz.getDeclaredConstructors();
        StringBuilder str;
        String className = StringUtil.getName(classFullPath);//得到类名
        for (Constructor constructor : constructors) {
            str = new StringBuilder();
            constructor.setAccessible(true);
            int modifiers = constructor.getModifiers();
            String modifier = Modifier.toString(modifiers);//得到修饰符
            str.append("\t").append(modifier).append(" My").append(className).append(" ").append("(");
            StringBuilder strTemp = new StringBuilder();
            //得到方法参数
            Parameter[] parameters = constructor.getParameters();
            //用String[] 来保存参数名
            String[] parameterNames = new String[parameters.length];
            for (int i = 0; i < parameters.length; i++) {//遍历
                Parameter parameter = parameters[i];
                String typeName = parameter.getType().getName();//参数类型,得到的是:java.lang.String
                typeName = StringUtil.getName(typeName);//参数类型,得到:String
                String parameterName = parameter.getName();//得到参数名
                parameterNames[i] = parameterName;
                strTemp.append("\t").append(typeName).append(" ").append(parameterName).append(",");
            }
            //因为str最后,多了一个",",所以去掉
            if (parameterNames.length == 0)
                str.append("){\n");
            else
                str.append(strTemp.substring(0, strTemp.length() - 1)).append(" ){\n");
            //构造方法,方法体为this.属性=属性
            for (String parameterName : parameterNames) {
                str.append("\t\tthis.").append(parameterName).append(" = ").append(parameterName).append(";\n");
            }
            str.append("\t}\n");
            sb.append(str);
        }
    }

4.获得getter和setter方法

获得方法的Class对象Method的方式与构造器一抹一样,只是把Constructor换为了Method。获取修饰符,方法名,参数列表信息如构造器和属性所示,再次就不过多赘述。

方法相比于构造器多了一个返回值,我们可以通过方法对象method.getReturnType().getName()获取返回类型。

话不多说,下面我们直接上代码:


    /**
     * 写setter和getter方法
     * 和构造方法写法几乎一样,就是多了个返回类型
     * 已经知道只有一个参数
     */
    public void getMethodDesc() {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            StringBuilder str = new StringBuilder();
            String modifier = Modifier.toString(method.getModifiers());//修饰符
            String returnName = method.getReturnType().getName();//得到返回类型名字
            if (!returnName.equals("void"))
                returnName = StringUtil.getName(returnName);
            String methodName = method.getName();
            str.append("\n\t").append(modifier).append(" ").append(returnName).append(" ").append(methodName).append(" ( ");
            if (returnName.equals("void")) {
                Parameter parameter = method.getParameters()[0];//只有一个参数
                String parameterTypeName = parameter.getType().getTypeName();//参数类型
                parameterTypeName = StringUtil.getName(parameterTypeName);
                String parameterName = parameter.getName();//参数名
                str.append(" ").append(parameterTypeName).append(" ").append(parameterName).append("){\n").
                        append("\t\tthis.").append(parameterName).append(" = ").append(parameterName).append(";\n");
            } else {
                str.append("){\n");
                //得到更改属性名,方法名:set属性名
                String propertyName = methodName.replaceFirst("get", "");
                propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);
                str.append("\t\treturn ").append(propertyName).append(";");
            }
            str.append("\t}\n");
            sb.append(str);
        }
    }

5.将sb写入到文件中

我们使用字节流FileOuputStream来输出,写入到文件。路径就写为:

包名+类名+".class";

public void generateOutput() {
        FileOutputStream fs = null;
        try {
            String packageName = this.getClass().getPackage().getName();//当前包名
            String path = "src/" + packageName + "/My" + StringUtil.getName(classFullPath) + ".java";
            fs = new FileOutputStream(path);//覆盖式创建
            fs.write(sb.toString().getBytes(StandardCharsets.UTF_8));
            fs.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fs != null)
                    fs.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

最后写一个方法,组装一下

    public void generate() {
        this.getPackageDesc();
        this.getClassDesc();
        this.getFieldDesc();
        this.getConstructorDesc();
        this.getMethodDesc();
        sb.append("\n}");
        this.generateOutput();
    }

到此大功告成,可能对于初学者来说有点难度,但自己动手写了之后才发现原来也就是那几个方法的调用。

我们大致总结一下:

1). 属性/构造器/方法  的获得方法就是那4个,常用的还是clazz.getDeclared???()以及clazz.getDeclared???s(),        这里???可以换为Field/Constructor/Method

后面加了" s "代表获取所有 属性/构造器/方法 ,不加" Declared "代表获取全部public 属性/构造器/方法 

对于非public的要用setAccessible(true)开启访问权限,不然抛出异常。

2)通过getModifires()获取修饰符,返回int型,Modifier.toString(int moddifire)得到对应的String类型的修饰符

3)通过getParameters()获得参数列表,parameter.getType().getName获取类型,parameter.getName()获取参数名字

4)通过getReturnType().getName()获取方法的返回类型

5)通过getName()获取属性/构造器/方法  的名字

等等,反射也可以动态获取泛型的信息,这里还没提及到。

上面这些,就在前几天学jdbc以及Servlet的时候也用到了,当时那个Servlet进行分层处理,什么业务层啊,管理层啊这些,然后不断降低耦合度,读取xml文件,大量使用到反射把我看蒙了,现在练习之后又去看了一下,也看得懂了,逻辑也理清楚了。所以啊,基础真的很重要,有一话怎么说来着,基础不牢,地动山摇。发现写这些东西对自己帮助也挺大的,理解得更加清楚了。

也希望各位能看到这里的大佬指出文中模糊的,说错的地方。我知道自己写的差,以后也会不断改进,欢迎大家提意见!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

luncker(摆烂版)

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

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

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

打赏作者

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

抵扣说明:

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

余额充值