在运行时期动态获取对象的属性方法
通过Class静态方法forName()获取类的字节码文件,那么类中的属性(包括属性值和属性名)方法都可以获得
对于使用private修饰的成员,想要获得它的细节,需要设置它的访问权限:
field.setAccessible(true);
然后使用相应方法获,例如对于private修饰的成员变量,可以使用
Object value = field.get(admin);
获取变量值,注意,变量值是属于某个对象的,而不是类的,因此需要传入对象
Declared的意思是声明的,也就是所有的
复习:protected和默认(包访问权限)是很相似的,在同一个包内,它们是一样的,而在另一个包内,默认是不能访问的,而protected是只有子类能访问。
@Test
public void testname() throws Exception {
String className = "com.cityu.basedao.Admin";
Class> clazz = Class.forName(className);
Admin admin = (Admin) clazz.newInstance();
//获取方法对象public void setUsername(String username){}
Method method = clazz.getMethod("setUsername", String.class);
Object returnValue = method.invoke(admin,"BJT");
System.out.println(returnValue+"--"+admin.getUsername());
}
输出:null–BJT,返回值是void,invoke=null
从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解)。
什么是Annotation,以及注解的作用?三个基本的 Annotation:
Annotation 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
掌握注解技术的要点:
//@interface是和class同等级的关键字
public @interface Author {
//修饰符默认为public
//当只有一个value属性时,在使用注解时,可以省略属性名,其余情况必须按照配置文件的"key-value对"写法;
//可以为属性设定默认值,在使用注解时就不用指定
String name();
int age() default 20;
}
@Author(name = "BJT")
public void testname() throws Exception {}
注解可以在Class,Method,Field对象中使用getAnnotation()方法获得
因此第一步是通过反射拿到Class,Method或者Field对象
复习:怎样拿到类的Class字节码对象?
对于注解Author:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.CLASS)
public @interface Author {
String name() default "BJT";
int age()default 30;
String remark();
}
使用注解反射获取注解信息:
@Id
@Author(remark = "Save the Bean!")
@Test
public void save() throws Exception{
//如何获取Author的注解信息:name/age/remark
Class clazz = TestApp.class;
Method m = clazz.getMethod("save");
Author author = m.getAnnotation(Author.class);
System.out.println(author.name());
System.out.println(author.age());
System.out.println(author.remark());
System.out.println(author.annotationType());
}
报错!java.lang.NullPointerException,原因是注解的生命周期定义为@Retention(RetentionPolicy.CLASS),只在字节码时期有效,而反射是在运行时有效,此时的注解已经消失,因此,只有@Retention(RetentionPolicy.RUNTIME)生命周期是运行期的注解才能用反射获取信息。
将@interface Author的生命周期修改为RetensionPolicy.RUNTIME,则输出为:
BJT
30
Save the Bean!
interface com.cityu.anno.Author
BaseDao使用条件:
1.表名-类型 相同
2.字段-属性 相同
3.主键名为id
这样封装的SQL语句(sql = “SELECT * FROM “+tableName+” WHERE id = ?”;)才能正确执行
如果数据库表名,字段或者主键不一致,那么使用使用参数化类型(泛型)反射获得的类名属性名不能映射到数据库的表中,SQL语句无法执行,BeanUtils也无法封装实体对象。这时候就需要手动去指定实体类JavaBean和数据库表之间的映射关系,通常通过读取XML配置文件完成。
这里我们选择使用注解反射来完成,通过在JavaBean实体类名、属性和方法上添加类似配置文件的注解,并在BaseDao中通过反射动态获取,完成JavaBean到数据库表的映射关系。
首先可以修改Admin数据库表字段名:
#修改表名
ALTER TABLE admin RENAME a_admin
#修改字段类型
ALTER TABLE admin MODIFY COLUMN a_id INT;
#修改字段名
ALTER TABLE admin CHANGE COLUMN username a_username VARCHAR(20);
ALTER TABLE admin CHANGE COLUMN pwd a_pwd VARCHAR(20);
这样JavaBean类名,属性以及主键和数据库不再一一对应,通过在JavaBean类上添加注解并且运行时反射,映射到数据库中的具体表名和字段。
package com.cityu.basedao;
import com.cityu.anno.Column;
import com.cityu.anno.Id;
import com.cityu.anno.Table;
@Table(tableName = "a_admin")
public class Admin {
@Id
@Column(columnName = "a_id")
private int id = 1;
@Column(columnName = "a_username")
private String username = "BJT";
@Column(columnName = "a_pwd")
private String pwd = "0017";
public int getId() {
return id;
}
public Admin(){
System.out.println("Admin.Admin()");
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "Admin [id=" + id + ", username=" + username + ", pwd=" + pwd
+ "]";
}
}
注解的作用相当于配置文件,为了能够在运行时期通过反射获取这些注解,需要设定注解的生命周期,因为默认为RententionPolicy.CLASS代表只在字节码中存在,运行时消失。
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String tableName();
}
通过反射获取注解,首先获取数据库表名和字段名,完成SQL操作;再将ResultSet通过自定义BeanHandler封装到结果对象中。
package com.cityu.basedao;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import com.cityu.anno.Column;
import com.cityu.anno.Id;
import com.cityu.anno.Table;
import com.cityu.utils.JdbcUtils;
/**
* 在JavaBean和数据库表名不对应的情况下,完成映射关系
* 1. 表名和类名
* 2. 字段名和属性名
* 3. 主键名
* @author shangxu2-c
*
* @param
*/
public class BaseDao {
private Class clazz;
private String tableName;//表名
private String primaryKey; //主键名
public BaseDao(){
//获取参数类型
ParameterizedType type
= (ParameterizedType) this.getClass().getGenericSuperclass();
//输出com.cityu.basedao.BaseDao
Type[] types = type.getActualTypeArguments();
clazz = (Class) types[0];
//已经拿到Admin.class,如何拿到类名和字段上的注解?
//确定表名tableName
Table table = clazz.getAnnotation(Table.class);
tableName = table.tableName();
//确定主键primaryKey--a_id
Field[] fs = clazz.getDeclaredFields();
for (Field field : fs) {
field.setAccessible(true);
Id id = field.getAnnotation(Id.class);
if(id!=null){
Column col = field.getAnnotation(Column.class);
primaryKey = col.columnName();
break;
}
}
}
public T findById(int id){
String sql;
try {
sql = "SELECT * FROM "+tableName+" WHERE "+primaryKey+" = ?";
return JdbcUtils.getQueryRunner().query(sql, new MyBeanHandler(clazz), id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public List getAll(){
String sql;
return null;
}
}
/**
* 自定义结果集
*/
class MyBeanHandler implements ResultSetHandler{
//已知clazz,将查到的字段值封装到对应属性中
private Class clazz;
public MyBeanHandler(Class clazz){
this.clazz = clazz;
}
@Override
public T handle(ResultSet rs) throws SQLException {
try {
T t = clazz.newInstance();
if (rs.next()) {
//遍历属性
Field[] fields = clazz.getDeclaredFields();
//获取属性注解--列名
for (Field field : fields) {
//这里名和值(实体类的和数据库表的,很容易混淆,记清楚:名都是String类型的,值都是Object类型的)
//属性名
String fieldName = field.getName();
Column column = field.getAnnotation(Column.class);
//字段名
String columnName = column.columnName();
//字段值
Object columnValue = rs.getObject(columnName);
//使用BeanUtils组件,将字段值赋给属性名,相当于name = "BJT"
BeanUtils.setProperty(t, fieldName, columnValue);
System.out.println("OK!!!");
}
}
return t;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}