Java注解的反射--在Dao层操作数据库和封装JavaBean中的应用

Java反射

在运行时期动态获取对象的属性方法

通过Class静态方法forName()获取类的字节码文件,那么类中的属性(包括属性值和属性名)方法都可以获得
对于使用private修饰的成员,想要获得它的细节,需要设置它的访问权限:

field.setAccessible(true);

然后使用相应方法获,例如对于private修饰的成员变量,可以使用

Object value = field.get(admin);

获取变量值,注意,变量值是属于某个对象的,而不是类的,因此需要传入对象

  • Declared的意思是声明的,也就是所有的

    • public Method[] getMethods()返回某个类的所有public方法包括其继承类的public方法,当然也包括它所实现接口的public方法。
    • public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。

    复习: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:

  • @Override: 限定重写父类方法, 该注解只能用于方法
  • @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
  • @SuppressWarnings: 抑制编译器警告.

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



public @interface Author {
//修饰符默认为public
//当只有一个value属性时,在使用注解时,可以省略属性名,其余情况必须按照配置文件的”key-value对”写法;
//属性值为数组时的写法:@Author(name = {“BJT”})
String[] name();
}

元注解–注解的注解

  • 指定注解的可用范围
    • @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  • 指定注解的生命周期
    • @Retention(RetentionPolicy.SOURCE) // 只在源码有效,即只在编译时有用
    • @Retention(RetentionPolicy.CLASS)// 只在字节码有效,即运行时不存在–默认值
    • @Retention(RetentionPolicy.RUNTIME)// 在运行时有效,即范围最大,运行时有效

注解反射(在方法中获得注解的内容)

注解可以在Class,Method,Field对象中使用getAnnotation()方法获得

因此第一步是通过反射拿到Class,Method或者Field对象

复习:怎样拿到类的Class字节码对象?

  1. 已知完整类名:ClassName.class
  2. Class.forName(“”);
  3. 已知对象:new Instance().getClass()

对于注解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(参考上一篇博客)

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

你可能感兴趣的:(JavaEE)