Java核心技术之反射(详细API和MyBatis简单实现)

我们平时调用方法或者创建一个类的实例都是在代码之中写死的,有没有什么办法例如就是传一个方法名进一个来,然后就能动态调用这个方法呢,诸如此类的在程序运行时能够获取某个类自身的所有信息在java当中被称之为反射,反射是java的核心技术,各种框架当中无一不用到反射,可以说程序当中自动化功能的实现都需要反射去实现.

Java.lang.reflect(反射包)
包中包括以下类:

  1. Class :代表一个类,可以获取一个类中的所有信息

  2. Field :代表类的成员变量

  3. Method :代表类的方法

  4. Constructor: 代表类的构造方法

  5. Array : 提供了动态创建数组,以及访问数组的元素的静态方法

  6. Proxy : 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类

1.Class

每个java类运行时都在JVM里表现为一个Class对象,可通过类名.class,类对象.getClass(),Class.forName("全类名")获取class对象

方法名中带有Declared的方法表示本类的信息,无论公私有属性或函数

  1. String getName():获取全类名(包名和类名)
  1. String getSimpleName() :获取类名
  2. Class<?> forName(String className) :根据全类名获取Class对象
  3. T newInstance():根据Class对象新建一个对象(类中必须要有一个无参的构造函数)
  4. ClassLoader getClassLoader():获得类的类加载器。
  5. Class getSuperclass():获取类的父类,继承了父类则返回父类,否则返回java.lang.Object.
  6. boolean isEnum() :判断是否为枚举类型
  7. boolean isArray() :判断是否为数组类型
  8. boolean isPrimitive() :判断是否为基本类型
  9. boolean isAnnotation() :判断是否为注解类型
  10. Package getPackage() :反射中获得package
  11. int getModifiers() : 反射中获得修饰符对应的数字(若要转换为public,可用Modifier.toString(Domain.class.getModifiers()))
  12. Field getField(String name):反射中获得域成员

14 .Field[] getFields() :获得域数组成员
15 . Method[] getMethods() :获得所有共有方法
16 . Method getDeclaredMethod(String name, Class<?>... parameterTypes):加个Declared代表本类,继承,父类均不包括。而且包括所有公私有方法.
17 .Constructor<?>[] getConstructors() :获得所有的构造函数
18 .Class<?> getComponentType :如果是数组Class对象,则可通过此方法得到数组类型,如果不是数组用此方法返回为null
19 .Annotation getAnnotation(Class<A> annotationClass)
如果存在这样的注解,则返回该元素的指定类型的注解,否则返回null。
20 .Annotation[] getAnnotation()
21 .InputStream getResourceAsStream(String path) : path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源
22 .Class<?> getSuperclass() :返回父类的Class对象
23 .boolean isassignablefrom(Class<?> cls)
用来校验一个类是否参数中的Class实现指定的父类
24 .boolean isInstance(Object obj)该方法和instanceof运算符作用等价,但是instanceof是对象instanceof 类,检查左边的被测试对象 是不是 右边类或接口的 实例化,而isInstance方法是 类.class.isInstance(对象),obj是被测试的对象,如果obj是调用这个方法的class或接口 的实例,则返回true
25 .boolean isMemberClass() :判断当前类是否成员类

2. Field

可以通过class的getDeclaredField(String name),getDeclaredFields(),getField(String name),getFields()获取,通过Field的方法可以获取、设置属性的值,并能获取属性的注解、字段的声明类型。

  1. Class<?> getType(): 获取属性声明时类型对象
  1. Type getGenericType() : 获取属性声明时类型的Type对象
  2. String getName() : 获取属性声明时名字
  3. getAnnotations() : 获得这个属性上所有的注释
  4. int getModifiers() : 获取属性的修饰符对应的值
  5. boolean isSynthetic() : 判断这个属性是否是 复合类
  6. get(Object obj) : 取得obj对象这个Field上的值(不通过get方法获取,需要使用setAccessible(true)禁用访问控制权限)
  7. set(Object obj, Object value) : 向obj对象的这个Field设置新值value(不通过set方法设置,需要使用setAccessible(true)禁用访问控制权限)
  8. setAccessible(boolean flag) 禁用/开启访问控制权限

3. Method

描述类的成员方法,Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。Method 允许在匹配要调用的实参与底层方法的形参时进行扩展转换可以通过class的getDeclaredMethod(String name, Class<?>... parameterTypes) ,getDeclaredMethods() ,getMethod(String name,Class<?>... parameterTypes),getMethods() 获取,通过Method的invoke方法去执行,获取返回值,也可以获取方法注解、 返回值类型等。

方法中存在Generic的,就表示返回值返回Type类型

  1. getAnnotation(Class<T> annotationClass) :如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
  1. Annotation[] getDeclaredAnnotations() :返回直接存在于此元素上的所有注释
  2. Class<?> getDeclaringClass() :返回当前方法的类Class对象
  3. Class<?>[] getExceptionTypes() :返回 Class 对象的数组,这些对象描述了声明将此 Method 对象表示的底层方法抛出的异常类型
  4. Type[] getGenericExceptionTypes() :返回 Type 对象数组,这些对象描述了声明由此 Method 对象抛出的异常
  5. Type[] getGenericParameterTypes() :按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的
  6. Class<?>[] getParameterTypes() :按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型
  7. Type getGenericReturnType() :返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象。
    Class<?> getReturnType() :返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型
  8. int getModifiers() 以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符
  9. String getName() :以 String 形式返回此 Method 对象表示的方法名称。
  10. Object invoke(Object obj, Object... args) :对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
  11. boolean isVarArgs(): 如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false

4. Constructor

Constructor是对构造方法的声明描述,Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。Constructor
允许在将实参与带有底层构造方法的形参的 newInstance() 匹配时进行扩展转换。可以通过class的getConstructor(Class<?>... parameterTypes) ,getConstructors(),getDeclaredConstructor(Class<?>... parameterTypes) ,getDeclaredConstructors() 获取。通过Constructor可以获取注解,参数类型等,并通过newInstance(Object... initargs) 创建类实例。

具体的方法跟上面的大同小异,通过名字都可以猜出来什么意思了

5. Array

Array 类提供了动态创建和访问 Java 数组的方法。允许在执行 get 或 set 操作期间进行扩展转换。可以通过class的isArray方法判定此 Class 对象是否表示一个数组类,getComponentType返回表示数组类型的 Class。通过newInstance初始化数组。

  1. static Object newInstance(Class cls,int array_length) :创建一个数组
  1. 访问动态数组元素的方法和通常有所不同,它的格式如下所示,注意该方法返回的是一个Object对象
    Array.get(arrayObject, index)

  2. 为动态数组元素赋值的方法也和通常的不同,它的格式如下所示, 注意最后的一个参数必须是Object类型
    Array.set(arrayObject, index, object)

  3. int getLength(Object obj) 返回数组的长度

6. Proxy(代理类)

Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的父类

  1. static Object newProxyInstance(ClassLoader classLoader,Class<?>[] interfaceArr,InvocationHandler h) :方法上有三个参数,第一个指定一个类加载器,第二个参数为,被代理类实现的接口数组,第三个是代理类

创建一个动态代理的步骤(例如要代理的类名为Car,代理类为MyProxy):

1.为被代理类创建一个接口,名字为BaseCar

public interface BaseCar {
    void run();
}

**2.被代理类实现BaseCar接口,并重写BaseCar中的方法 **

public class Car implements BaseCar {

    @Override
    public void run() {
        
        System.out.println("汽车启动");
    }

}

3.MyProxy代理类实现InvocationHandler接口,并重写invoke方法,记得要以BaseCar作为成员变量,还有给BseCar初始化,因为method.invoke要用到BaseCar的实例

public class MyProxy implements InvocationHandler {

    private BaseCar baseCar;

    public MyProxy(BaseCar baseCar) {
        super();
        this.baseCar = baseCar;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("汽车启动前");
        Object invoke = method.invoke(baseCar, args);
        System.out.println("汽车启动后");
        return invoke;
    }

}

可以看到invoke方法有三个参数:

  1. 动态代理类的引用,通常情况下不需要它。但可以使用getClass()方法,得到proxy的Class类从而取得实例的类信息,如方法列表,annotation等。
  1. 方法对象的引用,代表被动态代理类调用的方法。从中可得到方法名,参数类型,返回类型等等
  1. args对象数组,代表被调用方法的参数。注意基本类型(int,long)会被装箱成对象类型(Interger, Long)

4. 执行

        MyProxy myProxy = new MyProxy(new Car());
        
        BaseCar base = (BaseCar) Proxy.newProxyInstance
            (Object.class.getClassLoader(), new Class[]{BaseCar.class}, myProxy);
        
        base.run();

打印

汽车启动前
汽车启动
汽车启动后

动态代理是Spring框架AOP的执行原理,就是在需要执行的方法执行前后加入一些自定义的方法.

MyBatis的简单实现

先说说思路把,我们都知道MyBatis有一种使用方式,就是接口和xml配合使用,我最喜欢用这种方式因为sql语句和java代码可以完全解耦,另外dao层的实现类都不用自己写了,只需要在接口上面定义好方法,然后在对应的xml文件中写好与sql相关的配置就可以用了,MyBatis是我最喜欢用的一个框架(哎呀,跑题了),现在我们也要实现这样的功能,不过相比MyBatis来说会简陋非常多,只为演示,所以各位不要吐槽.

1 . 首先我们定义好一个Dao接口,叫做TestDao :

public interface TestDao {
    Test selectTest();

然后再写好对应的xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cppteam.dao.TestDao">
  
  <select id="selectTest" resultType="com.cppteam.domain.Test">
    select * from test.test
  </select>
  
</mapper>

2 . 有一个名字为test的数据库,名字为test的表

mysql> select * from test;

+----+------+
| id | name |
+----+------+
|  1 | 1    |
+----+------+
1 row in set (0.00 sec)

创建一个对应的Test类

public class Test {

    private Integer id;

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Test [id=" + id + ", name=" + name + "]";
    }

}

3 .创建一个代理类,代理TestDao类,对应TestDao的每一个方法的执行,都只获取方法名,方法返回值等等,再利用获取到的方法名去xml文件里面找,然后获取对应的sql语句,执行sql语句之后,再通过反射获取到返回值类型,再注入那个类对应的实例,所以整个过程是不需要实现类的,我们现在开始.

sqlSession主要是配置连接数据库,有一个select方法返回代理类

public class SqlSession {

    private static Connection connection = null;

    static {

        try {
            Class.forName("com.mysql.jdbc.Driver");

            connection = DriverManager.
                    getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8", "root","123456");
        } catch (Exception e) {
            // TODO: handle exception
        }

    }

    public static Connection getConnection() {
        return connection;
    }

    public static <T> T select(Class<T> cls) {
        return (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls },
                new MyProxy());
    }

}

4 .这里看看MyProxy代理类(重点部分)

public class MyProxy implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        
        //先读取xml文件
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        
        Document xml = builder.parse(Object.class.getResourceAsStream("/com/cppteam/mapper/TestMapper.xml"));
        
        //找到第一个select结点
        Node node = xml.getElementsByTagName("select").item(0);
        
        NamedNodeMap attributes = node.getAttributes();
        
        //查找属性id的值,对应的应该是接口中selectTest方法的名字
        Node id = attributes.getNamedItem("id");
        
        String methodName = method.getName();
        
        if(!id.getTextContent().trim().equals(methodName)){
            throw new Exception("找不到方法");
        }
        
        //找到对应的sql语句
        String sql = node.getTextContent().trim();
        
        //开始执行sql语句
        Connection connection = SqlSession.getConnection();
        
        connection.setAutoCommit(true);
        
        PreparedStatement prepareStatement = connection.prepareStatement(sql);
        
        ResultSet resultSet = prepareStatement.executeQuery();
        
        resultSet.next();
        
        //得到方法的返回值类型
        Class<?> returnType = method.getReturnType();
        
        System.out.println("方法名为:"+methodName+",方法返回值类型为:"+method.getReturnType().getName());
        
        System.out.println("执行的sql为"+sql);
        
        Object newInstance = returnType.newInstance();
        
        Field[] declaredFields = newInstance.getClass().getDeclaredFields();
        
        //对返回值类型的实例进行赋值
        for (Field field : declaredFields) {
            field.setAccessible(true);
            field.set(newInstance, resultSet.getObject(field.getName()));
        }
        return newInstance;
    }

}

5 .执行

        TestDao testDao = SqlSession.select(TestDao.class);
        
        Test test = testDao.selectTest();
        
        System.out.println(test);

打印

方法名为:selectTest,方法返回值类型为:com.cppteam.domain.Test

执行的sql为select * from test.test

Test [id=1, name=1]

我们应该更加注重框架的原理,而不是框架的使用,从这个例子当中,我们可以看到另一种应用,就是不用实现类,直接通过动态代理执行接口方法,利用这个思路我们可以在别的方面做出更好的设计.


另外说一下反射调用函数,,假如现在有一个函数:

public class Test {
    public void show(int num){
        System.out.println("函数参数为基本类型int");
    }
}

我们现在用反射对它进行调用,

public class Test1 {
    public static void show(Object object){
        Test.class.getMethod("show", object.getClass());
    }
    
    public static void main(String[] args) {
        int i = 1;
        show(1);
    }
}

运行后,报错了

 java.lang.NoSuchMethodException: com.cppteam.util.Test.show(java.lang.Integer)

Test1 的main函数中对本类中的show函数调用中,参数为1,而show函数的参数用的是Object对象接收,所以int类型的数值会被包装为Integer类,但是实际上存在的函数参数是int类型的,所以我们不得不加以判断,然后转型.

public class Test1 {
    public static void show(Object object) {
        
        Class<?> class1 = object.getClass();
        if (Integer.class.isInstance(object)) {
            class1 = int.class;
        }
        Method method = Test.class.getMethod("show", class1);
        
        method.invoke(new Test(), object);
    }

    public static void main(String[] args){
        int i = 1;
        show(i);
    }
}

运行结果是成功的,看打印:

函数参数为基本类型int
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,690评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 第一章:Java程序设计概述 Java和C++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能...
    loneyzhou阅读 1,243评论 1 7
  • 从荒野行动,终结者,光荣使命,CF手游的荒岛特训模式说一说吃鸡游戏市场
    KEEP_PACE阅读 293评论 0 0