前言
最近在读Spring官网的时候,就冒出了一个问题。在纯Java JDK下,如果我们有一个User表,项目中有一个User类,那么怎么关联我们的User表和User类呢?想来想去Java里面似乎就没有什么有效的办法关联这俩东西,于是就想到了一个能作妖的东西xml。我们把元数据都放在xml里面由Java去解析出来岂不美哉。这里的元数据可以是真正的数据,也可以是配置文件,也可以是描述,就像Spring框架做的一样。于是就去官网乱翻,没想到Spring早就想好了这个事儿,而且提供一个一套非常便捷的方法去做这个事情,这就是自定义注解。更多Spring内容进入【Spring解读系列目录】。
构建基础
按照我们的构想,首先要有一个User的类,我们命名为UserEntity,创建好getter和setter方法。
public class UserEntity {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
假设我现在有一个主逻辑类里面有一个方法要生成一个广义的查询语句Query SQL,我希望这个类通过一个对象构建一个sql语句。但是这里就有一个很大的问题了这里如果写死,那么这个就变成了一个固定的死查询了。那么我们的这个方法就没有意义了,传进去的Object也就成了摆设,所以不能写死。
public class CommonQuery {
public static String query(Object object){
//这里应该是一个select * from user。
return "";
}
}
接着说我们有一个main方法,去设置了这个类,希望这个类能够按照我们的目标生成一个查询语句又该怎么做呢。
public class Test {
public static void main(String[] args) {
UserEntity userEntity=new UserEntity();
userEntity.setId("1");
userEntity.setName("tom");
String sql= CommonQuery.query(userEntity);
System.out.println(sql);
}
}
首先我们分析下,能够利用的东西。首先我们必须得有select * from
作为基础,然后传递表名进来拼成一个查询语句,所以表名就成了关键。进一步来讲,怎么样才能把这个userEntity这个对象和我的user表关练起来呢?在原生的JDK中除了xml,就没有别的办法了,这里我们刨除各种框架包,不然我们就讲不下去了。
那么在这种情况下我们还可以把UserEntity用自定义注解给关练上user表。既然要自定义注解,当然第一步就是先创建一个注解。
创建一个注解
创建一个注解十分的简单,首先创建一个接口interface,然后接口前加上一个@就完了,简单到令人发指对不对。
public @interface TableMapper { //这样一个注解就搞定了
}
然后我们就可以在UserEntity 类里面使用这个注解了。特意把包也贴出来了,大家看就是我们创建的包下的注解。但是我们知道一般的注解都会有一个值,比如@Repository("abcd")
,这个要怎么做呢?
import com.demo.anno.TableMapper; //这里引用的就是我们自己定义的注解,包就是我们自己的包
@TableMapper
public class UserEntity {
/**略**/
}
也很简单,直接去我们自己的注解里加上相应的方法就可以了。所谓的注解这里加的参数,都是一个又一个的方法。后面的default ""
就是我们设置的默认值,我这里设置为空。但是我们知道Spring里面的默认值是一个算法,一般都是类名首字母替换小写,就是这么简单的原理。
public @interface TableMapper {
public String value() default ""; //要返回的值,这里设置的是一个String
String name() default "";
}
这样定义了以后我们就可以这样用了,因为声明的还有name方法,所以还可以加上name="abcd"
等等。而且如果注解里只有一个value,也可以直接写@TableMapper("user")
。但是如果不叫value,必须写上对应的方法名字。
@TableMapper(value = "user")
public class UserEntity {
/**略**/
}
那么下面就有一个非常关键的地方了,我们怎么把这个东西拿出来呢?如果这里是xml,我们有很多办法拿出来并放到Java中,比如dom4j,jdom等等一大堆。但是自定义注解就只有一个方法,也是Java的原生方法。回到CommonQuery 这个类的方法中去,我们现在要做的就是把数据从object对象中拿出来为我所用。我们从外面传入的是UserEntity,那么上面注解的值该怎么去拿呢?这里第一步是要拿到这个类的类对象,面向对象编程一切皆是对象。当然少不了安全性检查,JDK也给我们提供了一个方法用来判断是否加了注解isAnnotationPresent(Class class)。然后用getAnnotation(Class class)方法拿到注解,再用我们注解里面写的方法把UserEntity上面配置的user
字段拿出来。
public class CommonQuery {
public static String query(Object object){
Class clazz=object.getClass(); //拿到对应的类对象
String sql = "";
if (clazz.isAnnotationPresent(TableMapper.class)){ //安全性检查,把我们的注解文件整进去。
//得到注解
TableMapper table= (TableMapper) clazz.getAnnotation(TableMapper.class);
//调用value方法,拿到字符串值"user"拼接到sql后面
sql="select * from " + table.value();
}
return sql;
}
}
注解可见
到这里基本上能做的都做完了,但是现在并不能直接用。因为这个注解也是有生命周期的,默认情况下,我们自定义注解只会存在源码当中,也就是说只能存在于Java文件当中。当Java虚拟机把源码编译到字节码(也就是生成class文件)的时候,我们自定义注解就自动丢失了。这个怎么解决呢?现在就需要引入我们的元注解的概念了,Java中元注解就是那些可以加在注解上的注解,给注解提供更加基础的服务的注解。我记得Java中一共有四个,我们这次使用的是@Retention
。
找到我们的注解类加上这个@Retention
,设置参数为RUNTIME
可见,就能在运行时可见了。这里的参数就代表注解生命周期,当前设置的生命周期是运行时。还有SOURCE
这个生命周期状态,这个参数就是限定在源代码存在,一旦加上注解,编译为Class字节码就消失了,这个也是默认值。还有CLASS
状态,这个就表示源代码中的自定义注解可以被编译为字节码,但是运行时Java虚拟机对其依然是忽略的。
@Retention(RetentionPolicy.RUNTIME)
public @interface TableMapper {
public String value() default "";
}
加上以后再次运行,就能拿到我们的sql语句了"select * from user"
。
自定义注解位置
到这里应用是讲完了,但是还有一个小问题。我们知道在正常编程中,Java自带的注解是有位置要求的。以@Override为例,如果你加到一个普通的方法(就加在CityEntity.getId()
)上会报错,就在说Method does not override method from its superclass
。但是如果加到类(就加在CityEntity
)上也会报错,但是报错信息就变了'@Override' not applicable to type
。这说明@Override是有位置要求的。但是反观我们自己的注解,想加在哪里就加在哪里,随意的很。这个限定位置要怎么做呢?这个就说到了本文的第二个元注解@Target
。我们把其中的ElementType参数设定为Type,于是你再把注解加到别的地方就一定会报错。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableMapper {
public String value() default "";
}
那么最后把位置参数一一列举作为本篇的一个结尾。
@Target 位置参数
ElementType.TYPE ------>加到类上
ElementType.FIELD ------>加到字段上
ElementType.ANNOTATION_TYPE ------>加到注解类上
ElementType.CONSTRUCTOR ------>加到构造方法上
ElementType.LOCAL_VARIABLE ------>加到本地变量上
ElementType.METHOD ------>加到方法上
ElementType.PACKAGE ------>加到包上
ElementType.PARAMETER ------>加到参数上
附 写完以后想到的面试小问题
问:Xml文件在项目中使用是用来做什么的
答:1. 配置文件;2. 描述数据,或者描述元素