反射在框架中的应用还是很多的,比如spring框架里面AOP用到的动态代理,而动态代理就是基于反射的。本文主要通过学习java的反射,最后编写通用的DAO类。最近在复习java web,在做数据库访问的时候做了一个事情。
写一个CustomerDAO类(获取数据库连接,执行sql,获取resultSet,获取对象,关闭数据库等操作),用于根据用户名来获取一个用户对象。这个时候有一个新的需求,需要根据根据学号来获取一个学生对象,又需要编写一个StudentDAO类。当有更多的需求是时,我们就需要写大量的重复性代码。解决这个问题最好的方法是把他们公共的部分都提取出来做一个类。也就是说把获取一个类对象的方法抽取出来。
public class DAO<T>{
//sql表示需要执行的sql语句,args表示查询用到的参数,比如可以根据学号,姓名等进行查询
T getObject(String sql, Object ... args){
//获取数据库连接
//执行sql
//......
//关闭连接
}
}
这里用到了泛型,这个方法可以理解为返回的是一个随便什么类对象。那么问题来了,我们通过sql执行完查询之后,怎么知道他到底要返回什么样的对象呢。这里就需要用到反射了。
先说一下反射都有些什么功能:
1. 在运行时可以构造一个类对象
2. 可以获取这个类的属性
3. 可以获取他的方法
反射就是在运行时可以获取一个类的内部信息的一个机制。用前面提到的来解释,在编写继承DAO< T >的类的时候,T可能写成Customer,也可能是Student;
public class CustomerDAO extends DAO<Customer>{
public Costomer getCustomer(long CustomerId){
String sql = "SELECT * FROM student_tb WHERE ID = ?";
return getObject(sql,CustomerId);
}
}
而在DAO< T >中我们需要从DAO< Customer >获取Customer类,并且在执行完sql过后还要把获取到的属性封装成一个Customer。这个过程中我们获取Customer类,还可以造一个对象,还可以执行这个对象的方法。这个过程就是一个反射。有的时候我们可能就知道一个对象,甚至就知道一个全类名。想要获取这个类的其他信息,就需要用到反射。
1. Class类
2.获取方法Method
3.获取属性
注意这里的Class中的"C"是大写
Class类的实例表示正在java程序中运行的类。Class封装了这个运行的类的属性、方法等信息。在Object类中可以发现有一个方法是:getClass();在文档中它的解释是返回运行时类。这个类里面有很多方法,比如获取这个运行类的名称,获取它的方法,还可以执行一些方法等等。
方式一:通过类的class属性。已知类名
Class clazz = String.class;
方式二:通过getClass()方法来获取。已经有对象的情况
Class clazz = "这是一个字符串".getClass();
方式三:通过全类名的方式获取。需要知道全类名
Class clazz = Class.forName("java.lang.String");
方式三可能用的比较多,比如我们在使用spring 的时候用到 IOC 容器的时候,配置一个bean就是用的这种方式。
另外还有一个很重要的方法就是 newInstance(); 在后面我们会用到,可以使用这个方法创建一个实例,前提是这个类要有默认的构造方法。 所以我在编写一个实体类的时候,写了带参数的构造方法就一定要写一个无参数的构造方法。(根据java基础我们知道,如果不写构造方法,系统会默认创建无参数的构造方法。一旦写了构造方法就不会默认创建了)
方法也有相关的封装类------Method,在Class的对象clazz里面可以获取,
获取Method的方法有:
方法 | 描述 |
---|---|
clazz.getMethods() | 获取类中的所有方法包括父类,但不能获取私有方法。返回Method数组 |
clazz.getDeclaredMethods() | 获取本类的方法包括私有方法。返回Method数组 |
public class TestMain {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = Customer.class;
Method[] methods = clazz.getMethods();
//Method[] methods = clazz.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i].getName());
}
}
}
class Customer{
String name;
String age;
public void publicSayHello(){
System.out.println("Hello!!");
}
private void privateSayHello(String name){
System.out.println("Hello "+name);
}
}
下面是clazz.getMethods();执行的结果。它能够获取父类的Method。所有类的父类都是Object,所以会有equals、wait等方法。
这是clazz.getDeclaredMethods()执行的结果
我们还可以通过方法名和参数类型来获取一个Method对象
public class TestMain {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class clazz = Customer.class;
Method method = clazz.getDeclaredMethod("publicSayHello",String.class);
System.out.println(method.getName());
Object obj = clazz.newInstance();//使用newInsstance()可以创建一个对象
methods.invoke(obj,"阿璐4r");//可以通过这个invoke执行获取到的方法,obj代表对象后面表示参数
}
}
class Customer{
String name;
String age;
public void publicSayHello(String name){
System.out.println("Hello!!" + name);
}
}
关于属性/字段也有一个相应的封装类Field。Field的使用基本上和Method差不多
方法 | 描述 |
---|---|
clazz.getDeclaredFields() | 获取类中的字段,包括私有的属性 |
clazz.getDeclaredField(String name) | 通过字段名来获取属性 |
获取字段相关代码:
public class TestMain {
public static void main(String[] args) throws NoSuchFieldException {
Class clazz = Student.class;
System.out.println("获取Student中的全部字段");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
System.out.println("根据字段名来获取字段");
Field field = clazz.getDeclaredField("name");
System.out.println(field.getName());
}
}
class Student{
private Integer id;
String name;
}
public class TestMain {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Student student = new Student(110,"阿璐4r");
Class clazz = student.getClass();
Field field = clazz.getDeclaredField("name");
//设置字段的值
field.set(student,"修改后的值");
//获取字段的值
Object val = field.get(student);
System.out.println(val);
}
}
class Student{
private Integer id;
String name;
public Student() {
}
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
这里值得我们一说的是,字段值的设置和修改是通过实体类的getXxx,setXxx方法的。这就是我们为什么在写实体类的时候需要写get,set方法,其实他留给了反射来使用。 注意的是get、set的方法名的写法是有讲究的,如:getName。get后面是字段名,但第一个字母大写。
对反射有了一个大致的了解了,现在在回到开始的问题。这个时候我们就可以引入一个新的技巧。先上代码。
public class DAO<T>{
Class<T> clazz;
public DAO(){
clazz = 实际的Class;如果数CustomerDAO继承DAO<Customer>,
那么这clazz就是Customer.class;
}
//sql表示需要执行的sql语句,args表示查询用到的参数,比如可以根据学号,姓名等进行查询
T getObject(String sql, Object ... args){
//获取数据库连接
//执行sql
//......
//关闭连接
}
}
我们在写CustomerDAO的时候可以继承DAO< Customer >,我们在写StudentDAO可以继承DAO< Student >。在DAO构造器中就可以获得相应的Class了。有了Class。我们的DAO代码就通用了,这样我们就是在获取getObject()方法中获取到相应的对象了。那么现在的问题是如何获取这个DAO< T >,T具体是什么。
public class DAO<T>{
Class<T> clazz;
public DAO(){
Class cla = this.getClass();//根据我们学的基础知识,这里的this指的是CustomerDAO
Type type = cla.getGenericSuperclass();//获取父类带<>里面的类型
if(type instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] args = parameterizedType.getActualTypeArguments();
if(args != null && args.length > 0){
clazz = (Class<T>) args[0];
}
}
System.out.println("DAO的构造方法 clazz:"+clazz.getName());
}
//sql表示需要执行的sql语句,args表示查询用到的参数,比如可以根据学号,姓名等进行查询
T getObject(String sql, Object ... args){
//获取数据库连接
//执行sql
//......
//关闭连接
}
}
这里获取到了实际的Class
这段代码用到了与泛型相关东西
1.Tyep:指的是一个类型
2.ParameterizedType:指的是泛型类型,就是描述<>尖括号里面的东西的一个类
3.getGenericSuperclass():获取父类的泛型类型。
4.getActualTypeArguments() :获取实际泛型类型的数组
为了方便解释,这段代码写的不是很严谨。
通常我们会把反射写一个工具类,这个可以在网上找一下,包括spring里面都有相应反射工具类,大家可以研究研究。
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 反射的 Utils 函数集合
* 提供访问私有变量, 获取泛型类型 Class, 提取集合中元素属性等 Utils 函数
* @author Administrator
*/
public class ReflectionUtils {
/**
* 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
* 如: public EmployeeDao extends BaseDao
* @param clazz
* @param index
* @return
*/
public static Class getSuperClassGenricType(Class clazz, int index){
Type genType = clazz.getGenericSuperclass();
if(!(genType instanceof ParameterizedType)){
return Object.class;
}
Type [] params = ((ParameterizedType)genType).getActualTypeArguments();
if(index >= params.length || index < 0){
return Object.class;
}
if(!(params[index] instanceof Class)){
return Object.class;
}
return (Class) params[index];
}
/**
* 通过反射, 获得 Class 定义中声明的父类的泛型参数类型
* 如: public EmployeeDao extends BaseDao
* @param
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public static<T> Class<T> getSuperGenericType(Class clazz){
return getSuperClassGenricType(clazz, 0);
}
/**
* 循环向上转型, 获取对象的 DeclaredMethod
* @param object
* @param methodName
* @param parameterTypes
* @return
*/
public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes){
for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
try {
//superClass.getMethod(methodName, parameterTypes);
return superClass.getDeclaredMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
//Method 不在当前类定义, 继续向上转型
}
//..
}
return null;
}
/**
* 使 filed 变为可访问
* @param field
*/
public static void makeAccessible(Field field){
if(!Modifier.isPublic(field.getModifiers())){
field.setAccessible(true);
}
}
/**
* 循环向上转型, 获取对象的 DeclaredField
* @param object
* @param filedName
* @return
*/
public static Field getDeclaredField(Object object, String filedName){
for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
try {
return superClass.getDeclaredField(filedName);
} catch (NoSuchFieldException e) {
//Field 不在当前类定义, 继续向上转型
}
}
return null;
}
/**
* 直接调用对象方法, 而忽略修饰符(private, protected)
* @param object
* @param methodName
* @param parameterTypes
* @param parameters
* @return
* @throws InvocationTargetException
* @throws IllegalArgumentException
*/
public static Object invokeMethod(Object object, String methodName, Class<?> [] parameterTypes,
Object [] parameters) throws InvocationTargetException{
Method method = getDeclaredMethod(object, methodName, parameterTypes);
if(method == null){
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + object + "]");
}
method.setAccessible(true);//让私有方法变为可访问类型
try {
return method.invoke(object, parameters);
} catch(IllegalAccessException e) {
System.out.println("不可能抛出的异常");
}
return null;
}
/**
* 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
* @param object
* @param fieldName
* @param value
*/
public static void setFieldValue(Object object, String fieldName, Object value){
Field field = getDeclaredField(object, fieldName);
if (field == null)
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
makeAccessible(field);
try {
field.set(object, value);
} catch (IllegalAccessException e) {
System.out.println("不可能抛出的异常");
}
}
/**
* 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
* @param object
* @param fieldName
* @return
*/
public static Object getFieldValue(Object object, String fieldName){
Field field = getDeclaredField(object, fieldName);
if (field == null)
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
makeAccessible(field);
Object result = null;
try {
result = field.get(object);
} catch (IllegalAccessException e) {
System.out.println("不可能抛出的异常");
}
return result;
}
}
这里写了一个DAO< T >类,这里主要使用了两个工具类,JDBCutils,与数据库相关的工具类,方便获取连接,关闭连接等等,其次使用了DButils,可以预编译执行sql语句,返回对象,返回对象的集合,查询、单数、填充占位符等等。
import com.javawebmvc.db.JdbcUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DAO<T> {
//DBUtils
public QueryRunner queryRunner = new QueryRunner();
private Class<T> clazz;
public DAO() {
//clazz = ReflectionUtils.getSuperGenericType(getClass());
Class cla = this.getClass();
Type type = cla.getGenericSuperclass();//获取父类带<>里面的类型
if(type instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] args = parameterizedType.getActualTypeArguments();
if(args != null && args.length > 0){
clazz = (Class<T>) args[0];
}
}
System.out.println("DAO的构造方法 clazz:"+clazz.getName());
}
//返回一个对象
public T getObject(String sql, Object... args){
Connection connection = null;
T result = null;
try {
connection = JdbcUtils.getConnection();
result = queryRunner.query(connection, sql, new BeanHandler<>(clazz), args);
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.releaseConnection(connection);
}
return result;
}
/**
* 返回一个值
*
* @param sql
* @param args
* @param
* @return
*/
public <E> E getforVal(String sql, Object... args) {
Connection connection = null;
try {
connection = JdbcUtils.getConnection();
return (E) queryRunner.query(connection,sql,new ScalarHandler(),args);
}catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.releaseConnection(connection);
}
return null;
}
/**
* 返回一个集合list
*
* @param sql
* @return
*/
public List<T> listObject(String sql,Object ... args) {
Connection connection = null;
List<T> list = new ArrayList<>();
try {
connection = JdbcUtils.getConnection();
list = queryRunner.query(connection, sql, new BeanListHandler<>(clazz), args);
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.releaseConnection(connection);
}
return list;
}
/**
* 封装增加,删除,更新操作。
*
* @param sql
* @param args
*/
public void update(String sql, Object... args) throws SQLException {
Connection connection = null;
try {
connection = JdbcUtils.getConnection();
queryRunner.update(connection, sql, args);
} finally {
JdbcUtils.releaseConnection(connection);
}
}
}
学到了后面的框架,反射的用处还是很大,对理解框架的原理也有帮助。在这里整理了一下反射的知识,希望对大家有帮助。本知识的学习来源于尚硅谷佟刚老师。如果需要大家可以下载
不对之处,请多指教!!