1. 反射概念
概念 : 将类的各个部分封装为其他对象,在程序的运行期可以动态的操作字节码文件的过程,叫做反射机制。
应用 : 框架设计的灵魂(无反射无框架)
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
2. 如何获取字节码对象
反射的灵魂 : 字节码对象
获取字节码对象的三种方式:
- Class.forName(“类的全路径”); 【将磁盘class加载到内存中的方式】
- 类名.class; 已经加载了类信息,想要获取时
- 对象.getClass() ; 通过已经存在的对象来获取字节码信息
- Object类中的本地方法: public final native Class<?> getClass();
/**
* 获取字节码对象的三种方式 :
*/
public class ClassTest {
public static void main(String[] args) {
try {
//方式1 :应用在通过类的字符串路径加载到内存中获取字节码对象的时候
Class<?> clazz1 = Class.forName("com.company.entity.User");
//方式2 : 应用在形参设计上【会自动封装框架时体现】
//Class<?> clazz2 = User.class;
Class<User> clazz2 = User.class;
//方式3:
User user = new User();
Class<? extends User> clazz3 = user.getClass();
System.out.println(clazz1 == clazz2); // true
// System.out.println(clazz2 == clazz3); // true
// 字节码对象在一次程序执行过程中,无论通过什么方式来获取,都获取的是唯一的这个字节码对象
// 同一个字节码文件只会被类加载器加载一次(因此 : 类的初始化只有一次)
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//得到类的实例对象
User user=(User)clazz1.getInstance();
}
}
3. 字节码创建默认对象
package com.company.test;
/**
* 通过字节码对象可以创建出一个默认的对象
*/
public class InstanceTset {
public static void main(String[] args) {
try {
//1. 获取字节码对象
Class<?> clazz = Class.forName("com.company.entity.User");
//2. 通过字节码来创建对象
Object obj = clazz.newInstance(); // 默认通过无参构造创建对象
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
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. 字节码获取方法
/**
* 通过字节码对象获取所有的方法
*/
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.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 : 对象关系映射
- 类 → 数据库表
- 对象 → 数据库的一条记录
- 类中的属性 → 数据库表中的列名
/**
* 数据库工具类
* 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);
}
}