前言

  反射在框架中的应用还是很多的,比如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.什么是反射

先说一下反射都有些什么功能:
    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类,还可以造一个对象,还可以执行这个对象的方法。这个过程就是一个反射。有的时候我们可能就知道一个对象,甚至就知道一个全类名。想要获取这个类的其他信息,就需要用到反射。

2.反射需要掌握哪些内容?

  1. Class类
  2.获取方法Method
  3.获取属性

3.Class类

  注意这里的Class中的"C"是大写

3.1Class是什么

  Class类的实例表示正在java程序中运行的类。Class封装了这个运行的类的属性、方法等信息。在Object类中可以发现有一个方法是:getClass();在文档中它的解释是返回运行时类。这个类里面有很多方法,比如获取这个运行类的名称,获取它的方法,还可以执行一些方法等等。

  

java 书写格式 java dao怎么写_sql

3.2获取Class的方式

方式一:通过类的class属性。已知类名
Class clazz = String.class;

方式二:通过getClass()方法来获取。已经有对象的情况
Class clazz = "这是一个字符串".getClass();

方式三:通过全类名的方式获取。需要知道全类名
Class clazz = Class.forName("java.lang.String");

方式三可能用的比较多,比如我们在使用spring 的时候用到 IOC 容器的时候,配置一个bean就是用的这种方式。

另外还有一个很重要的方法就是 newInstance(); 在后面我们会用到,可以使用这个方法创建一个实例,前提是这个类要有默认的构造方法。 所以我在编写一个实体类的时候,写了带参数的构造方法就一定要写一个无参数的构造方法。(根据java基础我们知道,如果不写构造方法,系统会默认创建无参数的构造方法。一旦写了构造方法就不会默认创建了)

4.获取方法

方法也有相关的封装类------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等方法。

java 书写格式 java dao怎么写_Customer_02


这是clazz.getDeclaredMethods()执行的结果

java 书写格式 java dao怎么写_java反射_03


我们还可以通过方法名和参数类型来获取一个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);
    }
}

java 书写格式 java dao怎么写_DAO_04


通过看api文档会发现还有很多相关的方法,这里不一一说了

5.获取属性

关于属性/字段也有一个相应的封装类Field。Field的使用基本上和Method差不多

5.1获取字段

方法

描述

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;
}

java 书写格式 java dao怎么写_Customer_05

5.2设置和获取字段的值

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;
    }
}

java 书写格式 java dao怎么写_java 书写格式_06


这里值得我们一说的是,字段值的设置和修改是通过实体类的getXxx,setXxx方法的。这就是我们为什么在写实体类的时候需要写get,set方法,其实他留给了反射来使用。 注意的是get、set的方法名的写法是有讲究的,如:getName。get后面是字段名,但第一个字母大写。

6.回到最初的问题

对反射有了一个大致的了解了,现在在回到开始的问题。这个时候我们就可以引入一个新的技巧。先上代码。

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具体是什么。

7.编写通用DAO的构造器代码

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

java 书写格式 java dao怎么写_sql_07


这段代码用到了与泛型相关东西

  1.Tyep:指的是一个类型

  2.ParameterizedType:指的是泛型类型,就是描述<>尖括号里面的东西的一个类

  3.getGenericSuperclass():获取父类的泛型类型。

  4.getActualTypeArguments() :获取实际泛型类型的数组

为了方便解释,这段代码写的不是很严谨。

8.反射工具类

通常我们会把反射写一个工具类,这个可以在网上找一下,包括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<Employee, String>
	 * @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<Employee, String>
	 * @param <T>
	 * @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;
	}
}

9.DAO< T>类

这里写了一个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 <E>
     * @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);
        }
    }
}

10.写在最后

学到了后面的框架,反射的用处还是很大,对理解框架的原理也有帮助。在这里整理了一下反射的知识,希望对大家有帮助。