反射泛型和反射注解概括起來就三步:
- 自定義注解
- 通過反射獲取注解值
- 使用自定義注解
最終案例
通過自定義注解,將數(shù)據(jù)庫表與Java對象映射,在不需要配置文件的情況下,查詢出數(shù)據(jù)庫的記錄
分析:數(shù)據(jù)庫表有數(shù)據(jù)庫名,表名和字段几迄,所以在定義注解時這些都是必須的,但是在查詢時有通過主鍵查詢的方式冰评,那么如何知道哪個字段是主鍵呢映胁,這就需要來聲明一下,所以甲雅,還需要定義一個id注解用于標識某對象的某屬性對應著數(shù)據(jù)庫的主鍵解孙。此時就需要來說明一下什么是注解。
注解
什么是注解
- 語法:
@注解名稱
- 注解的作用:替代
xml
配置文件 - Servlet3.0中就可以不在使用
web.xml
文件抛人,而是所有配置文件都使用注解弛姜。 - 注解是由框架來讀取的!
注解的使用妖枚!
- 定義注解類(框架的工作)
- 使用注解(我們的工作)
- 讀取注解(反射讀取注解)(框架的工作)
注解其實也是一個類
如何定義注解類
class A{}定義類
interface A{}定義接口
@interface A{} 定義注解
- 天下所有的注解都是Annotation的子類
不用特意標記默認就是
如何使用注解
首先自定義一個注解:
//自定義注解類
package annotation;
@interface MyAnnotation {
//暫時不寫內(nèi)容廷臼,只是演示注解的使用
}
使用自定義MyAnnotation
注解
package annotation;
@MyAnnotation//注解能放在類上
public class Demo1 {
@MyAnnotation//注解能放在成員變量上
private String name;
@MyAnnotation//注解能放在構造器上
public void Demo1(){
}
@MyAnnotation//注解能放在方法上
public void test1(){
}
public void test2(@MyAnnotation String name){//注解能放在參數(shù)上
//注解能放在局部變量上
@MyAnnotation
String username = "hello";
}
//但是注解不能放在方法的調用上
}
注解的作用目標
由上述使用注解的例子可以看出,注解的作用目標有一下幾種:
- 類
- 方法
- 構造器
- 參數(shù)
- 局部變量
- 包
注解的屬性(依賴文檔了解)
- 定義屬性
格式:類型 屬性名()
例如:
//注解類
@interface MyAnno1{
int age();//這是屬性不是方法
String name();//括號后面不能跟{},也不能加參數(shù)荠商,因為這不是方法寂恬,而是注解的屬性
}
- 使用注解時給屬性賦值
//使用屬性時,屬性必須要求有值
@MyAnno1(age=100,name="zhangsan")
- 注解屬性的默認值
//注解類
@interface MyAnno2{
int age() default 100;//這是屬性不是方法
String name();
}
使用時:@MyAnno2(name="zhangsan")
也可以在給一個age值如:
@MyAnno2(age="999",name="zhangsan")
此時值會覆蓋默認值
- 名為value的屬性的特權
在使用注解時莱没,如果只給名為value的屬性賦值初肉,那么可以不給出屬性的名稱直接給出值
例如:
//注解的特權
@interface MyAnno3{
int value();
String name() default "hello world";
}
使用注解時:
@MyAnno3(100)相當于給value的屬性賦值不用指定value
- 注解屬性的類型
注解屬性有8中基本類型:
- String
- Enum
- Class
- 注解類型(和循環(huán)體的內(nèi)容是循環(huán)體是一個道理)
- 以上類型的一維數(shù)組類型 int[],(二維數(shù)組不行)
注意:Integer包裝類型不能使用
如何使用:
@interface MyAnno4{
int a();
String b();
MyEnum c();
Class d();
MyAnno2 e();
String[] f();
}
enum MyEnum{
A,B,C
}
@MyAnno4(
a=100,
b="hello",
c=MyEnum.A,
d=String.class,
e=@MyAnno2(name = "zhangsan"),
f={"hello","world"}
)
public class Demo3 {
}
當給數(shù)組類型的屬性賦值時,可以省略大括號
如f="hello"
注解的作用目標限定以及保存策略限定
讓一個注解他的作用目標只能在類上不能在方法上饰躲,這就叫做目標的限定
- 在定義注解時給注解添加注解叫做@Target
@Target(value={ElementType.TYPE,ElementType.ANNOTATION_TYPE,ElementType.METHOD,ElementType.FIELD})
@interface MyAnno1{
}
保留策略
- 源代碼文件(SOURCE):注解只在源代碼中存在牙咏,在編譯時就別忽略了(無法反射)
- 字節(jié)碼文件(CLASS):注解在源代碼中存在,編譯時會把注解信息放到class中存在嘹裂,但JVM在類時會被忽略加載注解(無法反射)
- JVM中(RUNTIME):注解在源代碼妄壶,字節(jié)碼文件中存在,并且在JVM加載類時會把注解加載到JVM內(nèi)存中(它是唯一可以反射的注解)
限定注解的而保留策略
@Retention(RetentionPolicy.RUNTIME)//保留策略
@interface MyAnno1{
}
讀取注解
反射泛型信息:
Class --> Type getGenericSuperclass()
Type --> ParameterizedType,把Type強轉為ParameterizedType類型
ParameterizedType --> 參數(shù)化類型 = A<String>
ParameterizedType :Type[] getActualTypeArguments(),A<String>中的String
Type[]就是Class[],我們就得到了類型參數(shù)了!
通過上述描述的步驟既可以獲的類型參數(shù)
public class Demo1 {
@Test
public void fun1(){
new B();//執(zhí)行得到java.lang.String
}
}
class A<T> {
public A() {
/*
* 在這里獲取子類傳遞的泛型信息寄狼,要得到一個Class
*/
// Class clazz = this.getClass();//得到子類的類型
// Type type = clazz.getGenericSuperclass();//獲取傳遞給父類參數(shù)化類型
// ParameterizedType pType=(ParameterizedType)type;//它就是A<String>
// Type[] types = pType.getActualTypeArguments();//它就是一個Class數(shù)組
// Class c = (Class)types[0];
// System.out.println(c.getName());//String或Integer
//將上面注釋的內(nèi)容變成一句話
Class c = (Class)((ParameterizedType)(this.getClass().
getGenericSuperclass())).getActualTypeArguments()[0];
System.out.println(c.getName());
}
}
class B extends A<String> {
}
class C extends A<Integer> {
}
反射注解
上面講述了如何通過反射來得到類的類型盯拱,那么要通過反射得到注解,要求注解的保留策略必須是RUNTIME
反射注解需要從作用目標開始反射
- 類上的注解例嘱,需要使用Class來獲取
- 方法上的注解需要Method來獲取
- 構造器上的注解需要Constructor來獲取
- 成員山谷的需要使用Field來獲取
一下?lián)碛锌梢垣@取注解的方法:
- Class:
- Method、Constructor宁舰、Field有共同的父類:AccessibleObject
它們都有一個方法: - Annotation getAnnotation(Class),返回目標上指定類型的注解
- Annotation[] getAnnoations(),返回目標注解上所有的注解
定義一個注解MyAnno1 拼卵,在定義一個類A來使用注解
@MyAnno1(name="A類",age=20,sex="男")
class A{
@MyAnno1(name="test1方法",age=10,sex="女")
public void test(){
}
}
@Retention(RetentionPolicy.RUNTIME)//注意聲明保留策略,否則獲取不到
@interface MyAnno1 {
String name();
int age();
String sex();
}
通過下面的代碼來演示如何獲得作用在A類上的注解
public class Demo2 {
@Test
public void test1(){
/*
* 1.得到作用目標
*/
Class<A> c = A.class;
/*
* 2.獲取指定類型的注解
*/
MyAnno1 myAnno1 = c.getAnnotation(MyAnno1.class);
System.out.println(myAnno1);//結果@demo2.MyAnno1(name=A類, age=20, sex=男)
}
@Test
public void test2() throws NoSuchMethodException, SecurityException{
/*
* 1.得到作用目標
*/
Class<A> c = A.class;
Method method = c.getMethod("test");
/*
* 2.獲取指定類型的注解(獲取方法上的注解)
*/
MyAnno1 myAnno1 = method.getAnnotation(MyAnno1.class);
System.out.println(myAnno1.name()+ ", " + myAnno1.age() + ", " +myAnno1.sex());
}
}
通過上述內(nèi)容蛮艰,我們了解了什么是如何通過反射獲取類型參數(shù)腋腮,以及什么是注解,注解該如何通過反射獲取壤蚜,那么作為練習即寡,下面就完成通過注解來寫一個BaseDao
的實驗
完成注解案例
首先我們根據(jù)實驗的分析,創(chuàng)建注解類
/**
* <p>Title: Table</p>
* <p>Description: Table 注解類用于標識哪個對象對應數(shù)據(jù)庫的哪一張表袜刷,作用在類上</p>
* @author guqin
* @date 2018年9月23日
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <p>Title: Column</p>
* <p>Description: 該注解類用于將數(shù)據(jù)庫字段映射到java對象屬性上</p>
* @author guqin
* @date 2018年9月23日
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value();
}
/**
* <p>Title: ID</p>
* <p>Description: ID注解類用于在對象屬性上標識哪一個字段是數(shù)據(jù)庫主鍵</p>
* @author guqin
* @date 2018年9月23日
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
String value();
}
為什么沒有數(shù)據(jù)庫名的注解呢聪富,眾所周知,連接數(shù)據(jù)庫需要指定庫名著蟹,用戶名和密碼才能訪問墩蔓,這些信息都是配置在外部的配置文件中的,比如JDBC萧豆、Hibernate或者其他奸披,于此同時還會使用數(shù)據(jù)庫連接池比如DBCP、C3P0連接池等涮雷,那么數(shù)據(jù)庫名及用戶名密碼就會配置在這些的配置文件當中阵面,比如可以寫一個jdbc.properties
,所以是不需要指定庫名的注解的而且這中信息還是多個對象公用的。
下面以查詢user
表為例來映射一個User
對象:
import guqing.basedao.Column;
import guqing.basedao.ID;
import guqing.basedao.Table;
/**
* <p>Title: User</p>
* <p>Description: </p>
* @author guqin
* @date 2018年9月23日
*/
@Table("user")//它的值表示當前類對應的表
public class User {
@ID("u_id")//當前屬性對應的列明样刷,而且說明這個列是主鍵列
private String uid;
@Column("uname")
private String username;
@Column("password")
private String password;
@Column("state")
private boolean state;
@Column("price")
private double price;
//get set方法省略
}
完成了以上工作之后仑扑,現(xiàn)在就需要:
- 獲取注解的信息
- 拼接sql語句查詢數(shù)據(jù)庫
- 映射結果集
獲取注解信息
package guqing.basedao;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* <p>Title: MatchField</p>
* <p>Description: 該類用于字段匹配,將數(shù)據(jù)庫字段與java對象屬性映射 </p>
* @author guqin
* @date 2018年9月26日
*/
public class MatchField {
private static String primary;//數(shù)據(jù)庫主鍵
private static String primaryName;//標有@ID注解的對象屬性名
//通過單例設計模式颂斜,返回唯一實例
private static MatchField matchField = new MatchField();
//私有構造方法夫壁,對外提供獲取該對象的方法來拿到唯一靜態(tài)實例
private MatchField(){}
/**
* @Title: getMatchFieldInstance
* @Description: 獲取該對象的句柄
* @param @return
* @return MatchField
* @throws
*/
public static MatchField getMatchFieldInstance() {
return matchField;
}
/**
* @Title: MappingField2Map
* @Description: 字段映射方法,返回一個Map沃疮,key=對象屬性名 ,value=數(shù)據(jù)庫字段名
* @param @param beanClass
* @param @return
* @return Map<String,String>
* @throws
*/
public Map<String,String> MappingField2Map(Class<?> beanClass){
Map<String,String> fieldMapping = new HashMap<String,String>();
//反射獲取到主鍵的值
Field[] fields = beanClass.getDeclaredFields();
for(Field field : fields){//遍歷之
ID id = field.getAnnotation(ID.class);
Column column = field.getAnnotation(Column.class);
if(id!=null) {
//拿到至關重要的數(shù)據(jù)庫主鍵字段與@ID注解的對象屬性名盒让,將其放到Map中形成映射鍵值對
primary = id.value();
primaryName = field.getName();
fieldMapping.put(primaryName, primary);//對象的成員變量名-->表字段名
} else if(column!=null) {
//拿到其他普通數(shù)據(jù)庫字段與普通字段對應的java對象屬性,同樣放到Map中形成映射關聯(lián)
fieldMapping.put(field.getName(), column.value());
} else {
//如果都不是那么默認就是字段名-->對應數(shù)據(jù)庫字段名
//也就是對象屬性可以不標識注解表示與數(shù)據(jù)庫字段同名
//也可以自己再改進一下比如設置默認值或者駝峰命名等
//甚至還可以寫一個xml配置文件用于配置數(shù)據(jù)庫字段與對象屬性的映射
//然后讀取配置文件的內(nèi)容司蔬,這就比較類似于hibernate邑茄,總之自由發(fā)揮吧
fieldMapping.put(field.getName(), field.getName());
}
}
//如果用戶忘記注解主鍵那么拋出異常
if(primaryName==null||primary==null){
throw new RuntimeException("syntax error:unknown primary key columns,"
+ " try again after annotating the primary key in the Javabean with @ID(value)");
}
//返回數(shù)據(jù)庫字段與對象屬性映射關系的Map
return fieldMapping;
}
/**
* @Title: precursorSelectSql
* @Description: 通過上面的MappingField2Map方法拼湊查詢語句的前半部分
* @param @param beanClass
* @param @return
* @return String
* @throws
*/
public String precursorSelectSql(Class<?> beanClass){
//通過beanClass得到字段映射Map
Map<String,String> mappingField = MappingField2Map(beanClass);
//拿到javaBean所有成員變量
Field[] fields = beanClass.getDeclaredFields();
//創(chuàng)建查詢語句前驅
StringBuilder sb = new StringBuilder("select ");
//拼湊sql語句
for(int i=0;i<fields.length;i++){
sb.append("`"+mappingField.get(fields[i].getName())+"`");//通過鍵獲得表的字段名稱
sb.append(" as "+"`"+fields[i].getName()+"`");
if(i<fields.length-1){
sb.append(",");//最后一個不加逗號
}
}
sb.append(" ");
return sb.toString();
}
/**
* @Title: getBeanPrimaryName
* @Description: 返回javabean中作為主鍵的成員變量名稱
* @param @return
* @return String
* @throws
*/
public String getBeanPrimaryName(){
return primaryName;
}
}
其實有了數(shù)據(jù)庫字段與對象屬性的映射就可以比較方便的完成查詢更新刪除添加等操作了,都是寫平湊sql語句的活俊啼。缺點是只能進行簡單屬性的映射肺缕。
下面就是通過上面的映射方式寫的BaseDao
可以參考下一:
package guqing.basedao;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import cn.itcast.jdbc.TxQueryRunner;
/**
* <p>Title: BaseDao1</p>
* <p>Description: </p>
* @author guqin
* @date 2018年9月23日
*/
public class BaseDao<T> {
private QueryRunner qr = new TxQueryRunner();
private Class<T> beanClass;//通過父類獲取子類類型
private String primaryName;//Dao主鍵字段名稱
private String tablename;//表名稱
private Map<String,String> mappingField;//存儲字段映射
//獲取到MappingField對象
private MatchField matchField = MatchField.getMatchFieldInstance();
//構造方法
@SuppressWarnings("unchecked")
public BaseDao(){
//通過父類獲取子類類型
beanClass = (Class<T>)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
//先獲取字段映射Map
mappingField = matchField.MappingField2Map(beanClass);
//再獲取主鍵名稱
primaryName = matchField.getBeanPrimaryName();
//獲取到表名稱
Table table = beanClass.getAnnotation(Table.class);
if(table!=null){
tablename = table.value();
} else {
String clazzSimpleName = beanClass.getSimpleName();
tablename = clazzSimpleName.substring(0, 1).toLowerCase() + clazzSimpleName.substring(1);
}
}
public void save(T bean) throws SQLException{
//String sql ="insert into 表名 values(幾個?)";
//通過反射將類中屬性的個數(shù)清楚就是問號的個數(shù)
Field[] fields = beanClass.getDeclaredFields();
//獲取成員變量上的注解
List<Object> params = new LinkedList<Object>();
String sql ="insert into " + "`"+tablename +"`"+" values(";
//拼湊sql字符串
for(int i=0;i<fields.length;i++){
sql += "?";
//拼湊參數(shù)
try {
params.add(beanClass.getMethod(getGetMethodString(fields[i].getName())).invoke(bean));
} catch (Exception e) {
try {
Object param = beanClass.getMethod(getIsMethodString(fields[i].getName())).invoke(bean);
if(param=="false"){
params.add(0);
}else if(param=="true"){
params.add(1);
}else{
params.add(param);
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
if(i < fields.length-1){
sql +=",";
}
}
sql += ")";
qr.update(sql,params.toArray());
}
/**
* @Title: update
* @Description: 更新方法
* @param @param bean
* @return void
* @throws
*/
public void update(T bean){
try {
Map<String,Object[]> updateSqlParam = bean2UpdateSql(bean);
/*
* update car_number_track set ...+where cid=cid子句
*/
qr.update((String) updateSqlParam.get("sql")[0], updateSqlParam.get("params"));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* @Title: baseDelete
* @Description: 根據(jù)主鍵刪除
* @param @param id
* @param @return
* @return Boolean
* @throws
*/
public void deleteById(Object id){
try{
String sql = "delete from " + "`" + tablename +"`"+ " where " + mappingField.get(primaryName) +"=?";
qr.update(sql,id);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* @Title: get
* @Description: 通過主鍵查詢
* @param @param id
* @param @return
* @return T
* @throws
*/
public T get(Object id) {
try {
String preSelectSql = matchField.precursorSelectSql(beanClass);
preSelectSql = preSelectSql + "from " + "`" + tablename +"`" + " where " + mappingField.get(primaryName)+"=?";
return qr.query(preSelectSql, new BeanHandler<T>(beanClass),id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* @Title: list
* @Description: 查詢方法
* @param @return
* @return List<T>
* @throws
*/
public List<T> list(){
try {
//查詢語句的前半部分 + 用戶自定義的后半部分查詢語句
String querySql = matchField.precursorSelectSql(beanClass) + Query.getThatSql();
if(querySql.contains(":")||querySql.contains("?")){
throw new RuntimeException("Syntax error: the number of parameters is insufficient."
+ " Please try again after checking[大哥參數(shù)個數(shù)沒給夠]");
}
return qr.query(querySql, new BeanListHandler<T>(beanClass));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Title: getGetMethodString
* @Description: 通過成員變量屬性名稱獲取到set方法
* @param @param fieldName
* @param @return
* @return String
* @throws
*/
private String getGetMethodString(String fieldName) {
String getMethodString = "get" + fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
return getMethodString;
}
private String getIsMethodString(String fieldName) {
String getMethodString = "is" + fieldName.substring(0, 1).toUpperCase() +
fieldName.substring(1);
return getMethodString;
}
private Map<String,Object[]> bean2UpdateSql(T bean){
List<Object> params = new ArrayList<Object>();
Map<String,Object[]> updateSqlParams = new HashMap<String,Object[]>();
//給出sql語句的前半部分
StringBuilder firstHalfSql = new StringBuilder("update "+"`"+tablename+"`");//前綴
StringBuilder setSql = new StringBuilder();//后綴
Object primaryValue = null;
//先在第一個占位符也就是cid出添加參數(shù)
setSql.append(" set "+"`"+mappingField.get(primaryName)+"`"+"=?");
try {
primaryValue = beanClass.getMethod(getGetMethodString(primaryName)).invoke(bean);
params.add(primaryValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* 1.判斷條件,完成sql中追加where子句
* 2.創(chuàng)建ArrayList授帕,保存參數(shù)
*/
Field[] fields = beanClass.getDeclaredFields();
for(Field field : fields){
if(field.getName().equals(primaryName)){
continue;//略過主鍵
}
Column column = field.getAnnotation(Column.class);
try {
String returnType = beanClass.getMethod(getGetMethodString(field.getName())).getGenericReturnType().getTypeName();
Object getMethod = beanClass.getMethod(getGetMethodString(field.getName())).invoke(bean);
if(returnType.contains("String")||returnType.contains("Integer")){
String param = (String) getMethod;
if(param!=null && !param.trim().isEmpty() && param!="null" && column!=null){
setSql.append(","+"`"+column.value()+"`"+"=?");
params.add(param);
} else if(param!=null && !param.trim().isEmpty() && param!="null" && column==null) {
setSql.append(","+"`"+ mappingField.get(field.getName()) +"`"+"=?");
params.add(param);
}
}else if(returnType.contains("double")){
double param = (double)getMethod;
if(param!=0.0 && column!=null){
setSql.append(","+"`"+column.value()+"`"+"=?");
params.add(param);
} else if(column==null) {
setSql.append(","+"`"+ mappingField.get(field.getName()) +"`"+"=?");
params.add(param);
}
}else if(returnType.contains("int")){
int param = (int) getMethod;
if(param!=0 && column!=null){
setSql.append(","+"`"+column.value()+"`"+"=?");
params.add(param);
} else if(column==null) {
setSql.append(","+"`"+ mappingField.get(field.getName()) +"`"+"=?");
params.add(param);
}
} else if(returnType.contains("boolean")){
Object param = (Object)getMethod;
if(column!=null && param!=null && !param.equals("null")){
setSql.append(","+"`"+column.value()+"`"+"=?");
params.add((boolean)param);
} else if(param==null){
continue;
}else{
setSql.append(","+"`"+ mappingField.get(field.getName()) +"`"+"=?");
params.add((boolean)param);
}
}
} catch (Exception e) {
try {
String returnType = beanClass.getMethod(getIsMethodString(field.getName())).getGenericReturnType().getTypeName();
Object getMethod = beanClass.getMethod(getIsMethodString(field.getName())).invoke(bean);
if(returnType.contains("boolean")){
Object param = (Object)getMethod;
if(column!=null && param!=null && !param.equals("null")){
setSql.append(","+"`"+column.value()+"`"+"=?");
params.add((boolean)param);
} else if(param==null){
continue;
}else{
setSql.append(","+"`"+ mappingField.get(field.getName()) +"`"+"=?");
params.add((boolean)param);
}
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
/*
* 追加where語句的參數(shù)
*/
String finalSql = firstHalfSql.append(setSql).append(" where "+
"`"+mappingField.get(primaryName)+"`"+"=?").toString();
params.add(primaryValue);
String[] sql = {finalSql};
updateSqlParams.put("sql",sql);
updateSqlParams.put("params", params.toArray());
return updateSqlParams;
}
}