1. 反射概念

概念 : 将类的各个部分封装为其他对象,在程序的运行期可以动态的操作字节码文件的过程,叫做反射机制。
应用 : 框架设计的灵魂(无反射无框架)

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

反射/字节码对象 - 图1

反射/字节码对象 - 图2

2. 如何获取字节码对象

反射的灵魂 : 字节码对象
获取字节码对象的三种方式:

  • Class.forName(“类的全路径”); 【将磁盘class加载到内存中的方式】
  • 类名.class; 已经加载了类信息,想要获取时
  • 对象.getClass() ; 通过已经存在的对象来获取字节码信息
    • Object类中的本地方法: public final native Class<?> getClass();

反射/字节码对象 - 图3

  1. /**
  2. * 获取字节码对象的三种方式 :
  3. */
  4. public class ClassTest {
  5. public static void main(String[] args) {
  6. try {
  7. //方式1 :应用在通过类的字符串路径加载到内存中获取字节码对象的时候
  8. Class<?> clazz1 = Class.forName("com.company.entity.User");
  9. //方式2 : 应用在形参设计上【会自动封装框架时体现】
  10. //Class<?> clazz2 = User.class;
  11. Class<User> clazz2 = User.class;
  12. //方式3:
  13. User user = new User();
  14. Class<? extends User> clazz3 = user.getClass();
  15. System.out.println(clazz1 == clazz2); // true
  16. // System.out.println(clazz2 == clazz3); // true
  17. // 字节码对象在一次程序执行过程中,无论通过什么方式来获取,都获取的是唯一的这个字节码对象
  18. // 同一个字节码文件只会被类加载器加载一次(因此 : 类的初始化只有一次)
  19. } catch (ClassNotFoundException e) {
  20. e.printStackTrace();
  21. }
  22. //得到类的实例对象
  23. User user=(User)clazz1.getInstance();
  24. }
  25. }

反射/字节码对象 - 图4

3. 字节码创建默认对象

  1. package com.company.test;
  2. /**
  3. * 通过字节码对象可以创建出一个默认的对象
  4. */
  5. public class InstanceTset {
  6. public static void main(String[] args) {
  7. try {
  8. //1. 获取字节码对象
  9. Class<?> clazz = Class.forName("com.company.entity.User");
  10. //2. 通过字节码来创建对象
  11. Object obj = clazz.newInstance(); // 默认通过无参构造创建对象
  12. System.out.println(obj);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

反射/字节码对象 - 图5

4. 字节码获取属性

常用API说明

操作属性的API 方法说明
clazz.getDeclaredFields() 获取所有的属性
clazz.getDeclaredField(fieldName); 根据属性名fieldName来查找class中具体的属性
field.setAccessible(true); 关闭程序对field属性访问权限的检查
field.set(obj, value); 给对象obj的属性field赋值value
field.get(obj); 获取对象obj的属性filed值
field.getModifiers(); 获取属性的修饰符权值(数字)
Modifier.toString(mod) 根据修饰符权值获取具体的修饰符字符串名称
field.getType() 获取属性的类型【类型的字节码对象】
field.getName() 获取属性名
public class User {

    public int id;
    protected Integer age;
    String gender;
    private String  username;
    private String  password;

    public User() {
        super();
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}
/*
 * 反射获取指定的属性并赋值获取值
 */
public class FieldTest2 {
    public static void main(String[] args) throws Exception {
        //1. 获取User类的字节码对象
        Class<?> clazz = Class.forName("com.company.entity.User");
        //2. 从字节码对象中根据属性名来获取指定的属性
//        Field field = clazz.getField("password"); // 默认获取被public修饰的属性
        Field username = clazz.getDeclaredField("username");
        Field password = clazz.getDeclaredField("password");
        //3. 通过字节码对象创建默认的对象
        Object obj = clazz.newInstance();  // 本质 : 堆中创建并初始化一个默认对象
        //【 重点 】: 除public修饰符之外,其他的修饰符在反射操作时都会进行程序的自检查
        username.setAccessible(true);  // 关闭程序对username属性的自检查功能
        password.setAccessible(true); // 关闭程序对password属性的自检查功能
        //4. 给属性赋值
        username.set(obj, "admin");
        password.set(obj,"123");
        //5. 获取值
        Object unameValue = username.get(obj);
        Object passwordValue = password.get(obj);

        System.out.println(unameValue);
        System.out.println(passwordValue);
    }

    public void demo() {
        // 传统对象的创建及赋值
        User user = new  User();
        user.setUsername("admin");
        String username = user.getUsername();
    }
}

5. 字节码获取构造方法

/**
 * 通过字节码对象来获取构造方法
 */
public class ConstructorsTest {
    public static void main(String[] args) {
        //1. 获取到字节码对象
        Class<User> clazz = User.class;
        //2. 获取字节码对象的构造方法
        Constructor<?>[] constructors = clazz.getConstructors();
        for Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        /**
         *  构造方法的目的 : 创建对象并初始化对象
         * public com.company.entity.User(int,java.lang.Integer,java.lang.String)
            public com.company.entity.User(java.lang.String)
            public com.company.entity.User()
         */
    }
}
/**
 * 通过字节码对象来获取构造方法创建对象
 *   public Constructor<T> getConstructor(Class<?>... parameterTypes){}
 *               Class<?>... parameterTypes 表示无参或多参的构造形参列表
 *  
 *   public T newInstance(Object ... initargs)
 *               Object ... initargs   :  构造方法实参
 */
public class ConstructorsTest2 {
    public static void main(String[] args) throws Exception {
        //1. 获取到字节码对象
        Class<User> clazz = User.class;
        //2. 获取字节码对象的构造方法
        //2.1  创建无参对象
//        Constructor<User> constructor = clazz.getConstructor();  // 获取类的无参构造
//        User u1 = constructor.newInstance();
        User u1 = clazz.newInstance();

        //2.2 一参构造创建对象
        Constructor<User> constructor2 = clazz.getConstructor(String.class); 
        User u2 = constructor2.newInstance("张三");

        //2.3 三参构造创建对象
        Constructor<User> constructor3 = clazz.getConstructor(int.class, Integer.class, String.class); 
        User u3 = constructor3.newInstance(1, 18,"男");

        System.out.println(u1);
        System.out.println(u2);
        System.out.println(u3);
    }
}

反射/字节码对象 - 图6

6. 字节码获取方法

/** 
 * 通过字节码对象获取所有的方法
 */
public class MethodTest {

    public static void main(String[] args) throws Exception {
        //1. 获取到字节码对象
        Class<?> clazz = Class.forName("com.company.entity.User");
        //2. 查看当前字节码对象中有哪些方法
//        Method[] methods = clazz.getMethods(); // 获取本类或父类中public修饰的方法
        Method[] methods = clazz.getDeclaredMethods();  //  获取本类中所有的方法
        for (Method method : methods) {
            //2.1 获取方法的访问修饰符
            int mod = method.getModifiers();
            String modifier = Modifier.toString(mod);
            System.out.print(modifier+" ");
            //2.2 获取方法的返回值类型
            Class<?> returnTypeClass = method.getReturnType();
            String returnType = returnTypeClass.getSimpleName();
            System.out.print(returnType+" ");
            //2.3 获取方法名
            String name = method.getName();
            System.out.print(name+"(");
            //2.4  获取方法的形参列表
            Parameter[] parameters = method.getParameters();
            for (Parameter param : parameters) {
                System.out.print(param+" ");
            }
            System.out.println("){\n}\n");
        }
    }
}
/**
 * 反射最重要的部分 :
 *         通过字节码获取指定的方法,执行该方法并接受方法的返回值
 * public Method getMethod(String name, Class<?>... parameterTypes){}
 *             String name  :  方法名
 *             Class<?>... parameterTypes : 形参类型的列表
 * 
 *   public Object invoke(Object obj, Object... args){}   动态调用执行方法
 *               Object obj  :  指定调用方法的对象
 *               Object... args : 方法形参对应的真实参数
 *                 Object  : 方法的返回值【当void时,则返回null】

 * 需求 : 执行Employee中私有的方法add(3,5),计算出结果
 */
public class MethodTest2 {
    public static void main(String[] args) throws Exception {
        //1. 获取字节码对象
        Class<?> clazz = Class.forName("com.company.entity.Employee");
        Object obj = clazz.newInstance();
        //2. 通过字节码根据【方法的名字】和【形参列表】共同获取此方法
        Method add = clazz.getDeclaredMethod("add", int.class, int.class);
        //3. 【重重点】通过反射动态的调用执行方法
        add.setAccessible(true);  // 关闭程序对权限的检查
        Object result = add.invoke(obj, 3,5);  // result是方法执行的返回值
        // 4. 对结果进行处理
        if(result instanceof Integer) {
            Integer  re = (Integer)result;
            System.out.println(re);
        }
    }
}

反射/字节码对象 - 图7

7. 反射案例

7.1 泛型擦除

package com.company.demo;

import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * 泛型擦除的案例 : 面试真题
 */
public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(98);
        list.add(18);
        // 面试 : 请问能否向list集合中添加字符串或布尔类型的值?
//        list.add("admin");
//        list.add(true);
        // 报错的原因 : 泛型在编译期生效,对集合的类型进行强制检查

        // 可以的? 通过反射机制在程序的运行期,动态的通过集合的add方法动态加入
        /*
         *  理由 : 泛型在运行期自动失效,所有被泛型标记的类型会全部使用Object类型来代替
         *   public boolean add(E e) {}  变为  public boolean add(Object e) {}  
         */
        // 通过对象来获取字节码对象
        Class<? extends ArrayList> clazz = list.getClass();
        // 通过clazz动态的获取add方法
        Method add = clazz.getMethod("add", Object.class);
        // 通过反射执行方法
        add.invoke(list, "admin");
        add.invoke(list, true);

        System.out.println(list);
    }
}

7.2 反射自定义框架

需求 : 编写一段通用的程序,可以执行任意类的任意方法(设计的程序要符合开闭原则)

反射的优点 : 解耦合

classpath=com.company.demo.CalcuteUtil
methodName=info
/**
 * 编写一段通用的程序,可以执行任意类的任意方法(设计的程序要符合开闭原则)
 * 开闭原则(OCP): 对修改关闭,对扩展开放
 */
public class Test2 {
    public static void main(String[] args) throws Exception {
        // 代码填入处 【反射知识点】
        Properties props = new Properties();
        props.load(new FileReader("bean.properties"));
        String className = props.getProperty("classpath");
        String methodName = props.getProperty("methodName");

        //1. 通过反射来动态执行方法
        Class<?> clazz = Class.forName(className);
        Object obj = clazz.newInstance();
        try {
            //2. 通过字节码对象来获取需要执行的方法
            Method method = clazz.getDeclaredMethod(methodName);
            method.setAccessible(true);
            Object result = method.invoke(obj);
            System.out.println("方法的返回值:"+result);
        } catch (Exception e) {
            e.printStackTrace();  // 方法没有找到会出问题
        }
    }
}

7.3 反射ORM框架

ORM : 对象关系映射

  • 类 → 数据库表
  • 对象 → 数据库的一条记录
  • 类中的属性 → 数据库表中的列名

反射/字节码对象 - 图8

反射/字节码对象 - 图9

反射/字节码对象 - 图10

/**
 * 数据库工具类
 *     1. 校验数据库驱动
 * 2. 获取数据库连接对象
 */
public class DBUtil {

    private static DruidDataSource dataSource; // 数据源

    // 静态代码快 :类加载时只会执行一次(加载驱动)
    static {
        try {
            // 加载属性文件获取数据库连接参数
            Properties props = new Properties();
            props.load(new FileReader("db.properties"));
            String driver = props.getProperty("pool.driver");  // 获取数据库驱动路径
            String url = props.getProperty("pool.url"); // 获取连接数据库的url
            String user = props.getProperty("pool.user"); // 获取数据库的用户名
            String password =props.getProperty("pool.password"); // 获取数据库的密码
            int intialSize = Integer.parseInt(props.getProperty("pool.initialSize"));
            int maxSize = Integer.parseInt(props.getProperty("pool.maxSize"));
            // 初始化数据库连接池
            dataSource = new DruidDataSource();
            dataSource.setDriverClassName(driver);
            dataSource.setUrl(url);
            dataSource.setUsername(user);
            dataSource.setPassword(password);
            dataSource.setInitialSize(intialSize);
            dataSource.setMaxActive(maxSize);
        } catch (IOException e) {
            System.out.println("数据属性文件初始化失败");
        }
    }

    /**
     * 获取数据库的连接对象
     * @return
     */
    public static Connection getConnnection() {
         try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            System.out.println("获取数据库连接失败!");
        }
         return null;
    }

    /**
     * 释放数据库资源(添加删除修改专用)
     * @param conn
     * @param ps
     */
    public static void release(Connection conn, PreparedStatement ps) {
        release(conn,ps,null);
    }

    /**
     * 释放数据库资源(查询专用)
     * @param conn
     * @param ps
     * @param rs
     */
    public static void release(Connection conn, PreparedStatement ps, ResultSet rs) {
        try {
            if(rs != null)
                rs.close();
            if(ps != null)
                ps.close();
            if(conn != null)
                conn.close();  // 此时的close()不是销毁连接,而是归还到数据库连接池中
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回数据源
     * @return
     */
    public static DruidDataSource getDataSource() {
        return dataSource;
    }

}
/**
   JDBC 通用模板(CURD)
      事务 +非事务
 */
public class JDBCTemplete {

    private DataSource ds;  // 数据源

    public JDBCTemplete() {
        super();
    }

    public JDBCTemplete(DataSource ds) {
        super();
        this.ds = ds;
    }

    /**
     * 添加删除修改的通用模板
     * @param sql
     * @param values
     * @return
     */
    public int update(String sql, Object...params) {
        if(ds == null)
            throw new NullPointerException("数据源没有设定");
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = ds.getConnection();  //从数据源中获取一个数据库连接对象
            ps = preparedSQL(conn, sql, params);  // 占位符替换
            return ps.executeUpdate();  // 执行添加,删除,修改;
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(ps != null) 
                    ps.close();
                if(conn != null)
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }

    /**
     * 通用的用于查询单个对象的方法
     * @param sql
     * @param clazz
     * @param params
     * @return
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     */
    public  <T> List<T> queryForList(String sql, Class<T> clazz, Object...params)  {
        if(ds == null)
            throw new NullPointerException("数据源没有设定");
        if(sql == null || clazz == null || "".equals(sql)) 
            return null;
        Connection conn = null;
        PreparedStatement ps  = null;
        ResultSet rs = null;
        ArrayList<T> list = new ArrayList<>();
        try {
            conn = ds.getConnection();  // 从数据库连接池中获取数据库连接
            ps = preparedSQL(conn, sql, params); // 替换占位符
            rs = ps.executeQuery();  // 发送并执行SQL,返回的是一个查询的结果集
            while(rs.next()) {  // 判断结果集中是否存在下一条记录
                // 动态获取查询到的列数
                ResultSetMetaData metaData = rs.getMetaData();
                int count = metaData.getColumnCount();  // 实际查询到的列的个数
                // ORM1 :  通过反射来创建T类型对象
                T obj = clazz.newInstance();
                for (int i = 1; i <= count; i++) {  // 一列一列的获取值
                    String columnLabel = metaData.getColumnLabel(i);  // 获取列名
                    String fieldName = lowerCase(columnLabel); // 将数据库的下划线对应java驼峰式命名
                    Object value = rs.getObject(columnLabel);// 获取列名对应的值
                    try {
                        // 通过反射动态的根据查询的列名来找出与列名同名的Java类属性对象
                        Field field = clazz.getDeclaredField(fieldName);
                        field.setAccessible(true);
                        field.set(obj, value); // ORM2
                    } catch (Exception e) {}
                }
                // 每次for结束就意味着 :数据库的一条记录已经封装成了一个Java对象
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(rs != null) 
                    rs.close();
                if(ps != null) 
                    ps.close();
                if(conn != null) 
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return list.isEmpty() ? null : list;
    }

    /**
     * 查询单个对象
     * @param sql
     * @param clazz
     * @param params
     * @return
     */
    public  <T> T queryForObject(String sql, Class<T> clazz, Object...params)  {
        List<T> list = queryForList(sql, clazz, params);
        if(list.size() > 1) {
            throw new IllegalArgumentException("To Many ResultSet...");
        }
        return list.get(0);
    }

    /**
     * 预编译SQL(防止SQL注入的替换)
     * @param sql  SQL语句
     * @param params  占位符的实参数据
     * @return  预编译对象
     * @throws SQLException 
     */
    public PreparedStatement preparedSQL(Connection conn, String sql, Object...params) throws SQLException {
        PreparedStatement ps = conn.prepareStatement(sql);
        // 动态的分析SQL中的占位符的情况
        ParameterMetaData metaData = ps.getParameterMetaData();  // Parameter 占位符的信息
        int count = metaData.getParameterCount();  // 获取占位符?的个数
        if(count != params.length)   //  判断占位符?个数于替换的实参个数是否一致
            throw new IllegalArgumentException("占位符参数个数不正确");
         //经典 : 循环替换占位符参数
        for (int i = 1; i <= count; i++) {
            ps.setObject(i, params[i-1]); 
        }
        return ps;
    }

    /**
     * 将数据库的下划线转换成java属性的小驼峰式命名  dept_id  →  deptId  salary
     * @param label
     * @return
     */
    public  String lowerCase(String label) {
        String[] split = label.split("_");
        if(split.length == 1) {
            return label;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(split[0]);
        for (int i = 1; i < split.length; i++) {
            sb.append(Character.toUpperCase(split[i].charAt(0)))
                .append(split[i].substring(1));
        }
        return sb.toString();
    }

}
public class Test {
    public static void main(String[] args) {
        JDBCTemplete util = new JDBCTemplete(DBUtil.getDataSource());
//        String sql  = "DELETE FROM t_emp WHERE id=?";
//        String sql = "UPDATE t_emp SET tno=?,title=? WHERE id=? ";
//        int row = util.update(sql, "WNSH1100","歌手",29);
//        System.out.println(row > 0?"操作成功":"操作失败");

//        String sql = "select manager_id AS managerId,dept_id AS deptId from t_emp";
//        List<Employee> list = util.queryForList(sql, Employee.class);
//        for (Employee employee : list) {
//            System.out.println(employee);
//        }

        String sql2 = "select * from t_emp where id=?";
        Employee emp = util.queryForObject(sql2, Employee.class, 2);
        System.out.println(emp);
    }
}