美文网首页程序员
Java核心技术之反射(详细API和MyBatis简单实现)

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

作者: 不姓马的小马哥 | 来源:发表于2017-04-03 21:53 被阅读0次

    我们平时调用方法或者创建一个类的实例都是在代码之中写死的,有没有什么办法例如就是传一个方法名进一个来,然后就能动态调用这个方法呢,诸如此类的在程序运行时能够获取某个类自身的所有信息在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
    

    相关文章

      网友评论

        本文标题:Java核心技术之反射(详细API和MyBatis简单实现)

        本文链接:https://www.haomeiwen.com/subject/dwqgottx.html