一、认识注解
一直准备写一波关于学习Java SE方面的文章,但是从我大学第一次写博客开始,就比较零零散散,但是无论如何JavaSE还是基础,还是需要认真打好基础。
废话不多说,Java注解在开发中可是经常遇到,Android的框架中都有注解的身影,例如Retrofit、butterknife框架,到处都是注解,那么注解到底是神马呢?如何使用注解?我们怎样自定义一个注解呢?别急,今天不仅有基础知识,博客最后还会包含一个小项目带大家实践实践,试试手。
首先带带大家看看什么是注解:
其中@Override就是一个常见的注解,它代表的是重写的意思,由于它是位于一个方法之上,所以他重写了该方法,至于重写父类什么并不是我们关心的重点,再来看一个:
@TargetApi这个注解是安卓开发中用来标识该方法是哪个安卓版本的Api,其中小括号里的参数即代表哪个版本的Android系统API,可以看到,@TargetApi这个注解也是作用于方法上的。类似于这种格式语法的注解有很多种。
好的,那么学习注解有什么好处呢?
我列出以下三点:
1.能够读懂别人写的代码,特别是框架相关的代码!
2.让编程更加简洁,代码更加清晰
3.让别人高看一眼,装逼!
好了,直接上注解的定义:
注解:Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。
(Annotation(注解)是JDK1.5及以后版本引入的)
注解的分类有以下几种:
按照运行机制来分(作用时间):
1.源码注解 //注解只在源码中存在,编译成.class文件就不存在了
2.编译时的注解、 //注解在源码和.class文件中都存在,只在编译时起作用。@Override
3.运行时的注解 //在运行阶段还起作用,甚至会影响运行逻辑的注解。
按照来源来分
1.来自JDK的注解
2.来自第三方的注解‘
3.我们自己定义的注解
二、创建注解
现在我们来看看一个自定义注解的语法要求:
// 我们自定义一个注解的基本格式
@Target({ElementType.METHOD,ElementType.TYPE}) //可以指定被注解的类型
@Retention(RetentionPolicy.RUNTIME) //注解类型:运行时、编译时
@Inherited //是否允许子类继承注解 接口不起作用 需要是类继承
@Documented
//如果注解只有一个成员,则这个成员名字必须取名为value(),方便在使用时的默认参数
public @interface Description {
//成员必须无参 无异常方式声明
String desc();
//成员类型是受限的,合法类型:String Class Annotation Enumeration
String author();
int age() default 18;
}
可以看到这就是一个自定义注解的大体结构。
@interface 自定义注解的关键字
下面四个是元注解:
@Target 指定作用域 ,参数可以为构造方法、字段、局部变量、方法、包、参数、类或接口Type
@Retention 指定注解类型
@Inherited 是否允许继承
@Documented 生成doc说明文档
然后自定义注解 public @interface 注解名 {
定义各种方法
}
大括号里面成员必须符合:
成员类型是受限制的,只能是String、Class、Annotation、Enumeration类型
成员必须无参数且无异常方式声明。
可以指定default为成员指定一个默认值
注意:如果注解只有一个成员的话,则成员名必须为value(),方便在使用时忽略成员名和赋值号
注解可以没有成员,则这个注解称为标识注解!
好了,这样一个叫Description的注解被我们create出来了,而且是可以在方法上和类上都能使用这个注解,我们把它声明为是一个运行时注解,不允许子类继承,可以生成doc文档。
注意:这个注解里面含有三个方法。
三、使用注解
那么我们怎么使用这个注解呢?
使用注解的语法格式:
@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>,....)
拿上面注解的举个例子:
@Description(desc="I am eyeColor",author="Mooc boy",age=18)
public String eyeColor() {
return "red";
}
可以看到这个使用自定义注解是作用于方法上。
Ok,create注解和使用自定义注解的都已经完成,还是很简单的。但是我们这个注解还没有实际的作用,我们可以利用反射来完成注解的逻辑功能。
解析注解
为了让我们自定义的注解能有“实际意义”,我们可以通过解析自定义注解,来让我们创建的代码能够控制逻辑。
通过反射获取类,函数,或成员上的运行时注解信息,从而实现动态控制程序运行时的逻辑
为了便于理解,这里我举个简单完整的例子:
1.Create一个名叫Guikai注解
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE}) //元注解 注解作用类型
@Retention(RetentionPolicy.RUNTIME) //指定注解的类型 运行时
@Documented
@Inherited
public @interface Guikai {
String Name();
String Num();
String Class();
}
2.新建一个类,使用这个注解
@Guikai(Name = "这是一个类上的注解",Num = "2014911006",Class = "计本一班")
public class Person {
@Guikai(Name = "这是一个方法上的注解",Num = "2014911006",Class = "计本一班")
public String name() {
return null;
}
public int age() {
return 0;
}
}
可以看到我们在方法和类上都加上我们自定义的注解
3.解析注解代码实例
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* 解析注解:
* 通过反射获取类,函数,或成员上的运行时注解信息,从而实现动态控制程序运行时的逻辑
*/
public class Sample {
public static void main(String[] args) {
try {
//获取类对象,判断类上面是否有注解
Class c = Class.forName("annotation.Person");
boolean isExist = c.isAnnotationPresent(Guikai.class);
if (isExist) {
Guikai d = (Guikai) c.getAnnotation(Guikai.class);
System.out.println(d.Name() + d.Num() + d.Class());
}
//接下来获得方法上的注解
Method[] ms = c.getMethods();
for (Method item : ms) {
//反射获取方法列表,然后遍历每个方法,判断是否有注解
boolean isMExit = item.isAnnotationPresent(Guikai.class);
if (isMExit) {
Guikai g = item.getAnnotation(Guikai.class);
System.out.println(g.Name() + g.Num() + g.Class());
}
}
//第二种方法
for (Method m : ms) {
Annotation[] as = m.getAnnotations();
for (Annotation a : as) {
if (a instanceof Guikai) {
Guikai d = (Guikai) a;
System.out.println(d.Name() + d.Num() + d.Class());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果:
这是一个类上的注解2014911006计本一班
这是一个方法上的注解2014911006计本一班
这是一个方法上的注解2014911006计本一班
这样我们就通过反射,获取到类上面的注解,然后打印出guikai注解上的参数!
接下来,我将附上一个生成sql语句的例子,让大家感受一下注解的强大!
四、Demo实战
注解的基础知识我们讲的差不多了,接下来来一个注解实战:
项目取自一个公司的持久层框架,用来代替Hibernate的解决方法,核心代码就是通过注解来实现,
当然一个商业级的项目过于复杂,我们这里只是抽离出核心代码出来。
需求:
1.有一张用户表,字段包括用户ID、用户名、昵称、年龄,性别,所在城市,邮箱,手机号。
2.方便对每一个字段或字段的组合条件进行检索,并打印出SQL语句。
3.使用的方式需要足够简单!
首先我们需要一个Bean类(对应数据表字段)
Filter.java文件:
package com.example.dao;
@Table("user")
public class Filter {
@Column("id")
private int id;
@Column("user_name")
private String userName;
@Column("nick_name")
private String nickName;
@Column("age")
private int age;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("mobile")
private String mobile;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
在Bean类的类名和方法上,有两个我们自定义的注解,分别为Column、Table,其只有一个成员
Column.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIEID})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value();
}
Table.java
package com.example.dao;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIEID})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
Test.java测试类:
package com.example.dao;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Filter f1 = new Filter();
f1.setId(10); //查询为10的用户
Filter f2 = new Filter();
f2.setUserName("lucy"); //模糊查询用户名为lucy的用户
Filter f3 = new Filter();
f3.setEmail("guikai@qq.com,zh@163.com,77777@qq.com");//查询邮箱为其中任意一个的用户
String sql1 = query(f1);
String sql2 = query(f2);
String sql3 = query(f3);
System.out.println(sql1);
System.out.println(sql2);
System.out.println(sql3);
}
private static String query(Filter f) {
StringBuffer sb = new StringBuffer();
//1.获取到class
Class c = f.getClass();
//2.获取到table的名字
boolean exists = c.isAnnotationPresent(Table.class);
if (!exists) {
return null;
}
Table table = (Table) c.getAnnotation(Table.class);
String tableName = table.value();
sb.append("Select * from ").append(tableName).append(" where 1=1");
//3.遍历所有的字段
Field[] fArray = c.getDeclaredFields();
for (Field field:fArray) {
//4.处理每个字段对应的sql
//4.1 拿到字段的名字
boolean fExists = field.isAnnotationPresent(Column.class);
if (!fExists) {
continue;
}
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
//4.2 拿到字段的值
String filedName = field.getName();
String getMethodName = "get" + filedName.substring(0, 1).toUpperCase()
+ filedName.substring(1);
Object fieldValue = null;
try {
Method getMethod = c.getMethod(getMethodName);
fieldValue = (Object) getMethod.invoke(f);
} catch (Exception e) {
e.printStackTrace();
}
//4.3 拼装sql
if (fieldValue==null ||
(fieldValue instanceof Integer && (Integer)fieldValue==0)) {
continue;
}
sb.append(" and ").append(filedName);
if (fieldValue instanceof String) {
if (((String) fieldValue).contains(",")){
String[] values = ((String) fieldValue).split(",");
sb.append(" in(");
for (String v:values) {
sb.append("'").append(v).append("'").append(",");
}
sb.deleteCharAt(sb.length()-1);
sb.append(")");
} else {
sb.append("=").append("'").append(fieldValue).append("'");
}
} else if (fieldValue instanceof Integer) {
sb.append("=").append(fieldValue);
}
}
return sb.toString();
}
}
可以看到,整个项目比较的简单,通过调用query(Filter f)方法,来实现打印SQL语句,下面我们详细看看这个方法做了什么:
毋庸置疑,也是通过反射Filte类来实现逻辑的,根据反射得到类里面的信息,包括类名上的注解和方法上的注解,然后获取注解的值,最后经过拼接字符串来实现SQL语句返回!
最后附上源码:
https://github.com/Gui-kai/BaseJava
觉得好,请点个赞!