在学习任何一种java框架之前,我们基本都要先了解这个框架的注解。例如:spring框架中的@Controller、@Bean、@Component、@EnableCaching等;mybatis框架中的@Select、@Delete、@ResultMap等;甚至于jdk本身也有@Override等注解。这些注解带给我们一种感受,注解本身代表了框架的一系列功能,我们按照框架规定的方式使用注解,就能实现对应的功能。java注解想要表达的思想就是,约定大于配置。
就像我现在打字用的键盘,键盘本身不能写字,但它提供了26个字母以及其他符号,我在键盘上按照约定敲击对应的键位,电脑会生成相应的指令做出相关的处理。同样,注解本身并不会实现功能,是java框架读取用户通过注解写入的值,通过反射机制实现一系列功能。
本文会简单介绍一下jdk自带的注解,以及如何自定义注解。接着会通过 “注解+反射”的例子,简单实现一个orm框架。
介绍jdk内置的几个常用注解:@Override,@Deprecated,@SuppressWarnings。
@Override,很常见,表明这个方法是重写的父类的方法,当你把@Override放到一个方法上时,编译器会自动去父类中查找是否有相应的方法,如果没有,说明注解使用错误,或者重写的方法名、参数等写错了,那么编译器就会给出编译错误,让你去修改。
@Deprecated,表明这个属性被弃用,可以修饰的范围很广,包括类、方法、字段、参数等等。当你使用它的时候,编译器就会给出提醒。不过,它是一种警告,而不是强制性的,在IDE中会给Deprecated元素加一条删除线以示警告,在 声明元素为@Deprecated 时,应该用Java 文档注释的方式同时说明替代方案,就像 Date 中的API文档那样,在调用@Deprecated 方法时,应该先考虑其 建议的替代方案。
@SuppressWarnings,表明这不是一个警告,那么编译器就不会把它当做警告给提示出来。参数,表示压制哪种类型的警告,它也可以修饰大部分代码元素,在更大范围的修饰也会对内部元素起效,比如,在类上的注解会影响到方法,在方法上的注解会影响到代码行。对于 Date 方法的调用,可以这样压制警告
@SuppressWarnings({" deprecation", " unused"}) public static void main(String[] args) { Date date = new Date(2017, 4, 12); int year = date.getYear(); }
我们先看看 几个注解的例子。
jdk中的@Override注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
spring中的@FeignClient注解
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { @AliasFor("name") String value() default ""; /** @deprecated */ @Deprecated String serviceId() default ""; @AliasFor("value") String name() default ""; String qualifier() default ""; String url() default ""; boolean decode404() default false; Class<?>[] configuration() default {}; Class<?> fallback() default void.class; Class<?> fallbackFactory() default void.class; String path() default ""; boolean primary() default true; }
可以发现几个有意思的信息:
修饰注解的注解,叫做元注解。java里面有下面四种元注解:
(1)@Target:用来修饰注解的作用域。
(2)@Retention:用于指明当前注解的生命周期。
(3)@Documented:申明注解是否应当被包含在 JavaDoc 文档中。
(4)@Inherited:是否允许子类继承该注解。
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
成员变量的类型可以是java基本类型,加类、接口、枚举、注解,以及他们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。
还有一种约束,当注解类中只有一个成员变量时,约束成员变量名必须是value,应用这个注解时可以直接接属性值填写到括号内。例如:@Select("select from table") 和@Select(value="select from table")。
很多全自动的orm框架,为了建立pojo类和数据库表之间的映射关系,都通过注解的方式,对pojo类注入表名,对pojo里面的属性注入表字段名。以前的hibernate就是通过这种方式,框架后台基于pojo对应的表和字段,动态生成jdbc所需的sql。mybatis不用,以前我们介绍过,mybatis是基于sql的半自动的orm框架,并不需要pojo类的映射。
前面说过,注解本身并没有功能,它是作为一种类似于键盘的约束,一般框架通过反射机制去赋予它相应的功能。那么这个示例就是通过 注解 + 反射,去模拟一个orm框架的功能。
这里创建两个注解类,分别是 @KTable 和 @KColumn 。
KTable.java 作用在pojo类上,映射数据库的表,所以作用域是ElementType.TYPE。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface KTable { String value(); }
KColumn.java 作用在pojo类上,映射数据库表里面的字段,所以作用域是ElementType.FIELD。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface KColumn { String value(); }
预先在数据库里面创建了一张表 fnd_user
| 字段名| 属性|
| --- | --- |
| id| varchar |
| name| varchar |
| age| int|
| role_code| varchar |
那么现在创建pojo类(FndUser.java),加上我们之前定义好的注解。
首先是pojo加上表名@KTable的注解。pojo类里面的属性,我们约束好,如果加了@KColumn的注解,则框架取注解里面的值作为表字段名,如果不加注解,则取pojo类的属性名作为表字段名。
@KTable("fnd_user") public class FndUser { private String id; private String name; private int age; @KColumn("role_code") private String roleCode; 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; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getRoleCode() { return roleCode; } public void setRoleCode(String roleCode) { this.roleCode = roleCode; } }
框架核心的代码来了,我们定义了一个方法,在传入pojo的对象后,会返回对应的查询sql。因为这部分代码的注释比较完整了,就不在多说了,主要通过反射,拿到注解的表名,列名,以及属性值,然后拼接sql。
KpaasQuery.java
@Component public class KpaasQuery { public String bindQuerySql(Object pojo) { StringBuffer stringBuffer = new StringBuffer(); Class c = pojo.getClass(); //拿到表名 boolean isTableExist = c.isAnnotationPresent(KTable.class); if (!isTableExist) { return null; } KTable kTable = (KTable) c.getAnnotation(KTable.class); String tableName = kTable.value(); //拼接表sql stringBuffer.append("select * from ").append(tableName).append(" where 1=1"); //拿到列名 // getDeclaredFields()拿到所有字段,getFields()拿到public字段 Field[] fields = c.getDeclaredFields(); for (Field field : fields) { //拿到字段名(fieldName)、列名(columnName)、字段值(fieldValue) String fieldName = field.getName(); String columnName = fieldName; Object fieldValue = null; boolean isColumnExist = field.isAnnotationPresent(KColumn.class); if (isColumnExist) { KColumn kColumn = field.getAnnotation(KColumn.class); columnName = kColumn.value(); } //拿到字段值 String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); try { Method getMethod = c.getMethod(getMethodName); fieldValue = getMethod.invoke(pojo); } catch (Exception e) { e.printStackTrace(); } //拼接列sql if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) { continue; } stringBuffer.append(" and " + columnName); if (fieldValue instanceof String) { stringBuffer.append(" = '").append(fieldValue).append("'"); }else if(fieldValue instanceof Integer){ stringBuffer.append(" = ").append(fieldValue); } } return stringBuffer.toString(); } }
写一个controller,看能不能按照我们的要求返回sql。
@RestController public class UserController { @Autowired private KpaasQuery kpaasQuery; @RequestMapping(value = "/getSql", method = RequestMethod.GET) public String getSql() { FndUser fndUser = new FndUser(); fndUser.setName("kerry"); fndUser.setAge(24); fndUser.setRoleCode("admin"); String sql = kpaasQuery.bindQuerySql(fndUser); return sql; } }
调用接口,返回的结果是:select * from fnd_user where 1=1 and name = 'kerry' and age = 24 and role_code = 'admin'
OK,符合我们的预期,那么这个简单的orm功能就实现了。
本公司的同事,看完这些,我猜想你的脑海中应该会浮现出一个框架-- 倚天 。我也是最早在使用倚天的时候,对注解产生了莫大的兴趣。当然,倚天的orm部分代码实现高深的多,功能也丰富的多。
最早的时候我挺怀疑倚天框架的必要性,我想明明直接可以通过mybatis就能实现的功能,为什么非要再封装成全自动的orm框架?后来我明白了,倚天给我们带来最大的好处,不是实现orm的功能,而是框架的约定。而这些约定让我们的代码更规范,开发人员只需要考虑业务逻辑和核心代码,很多涉及到代码质量、基础性能的问题,框架默默的就实现了。
拿本文的注解而言,因为倚天pojo类里面@RowID注解,我们不用考虑根据主键删除的安全性,框架会转换成加密后的rowId。
包括@SystemColumn的五个基础字段的主键,我们不用去写版本号的自增长,和创建人、创建时间、最后更新人、最后更新时间,这些重复而又枯燥无味的代码。
就像你写pojo类时,不想手动去敲那些get、set方法一样,你期待ide能自动生成。一个好的框架能够让你尽量少的浪费时间,集中精力,更好的提高自身能力。