一、自定义注解的案例分析
对于后端开发人员来说,我们在编写的程序的过程当中经常用到,例如现在JDK预定义的注解@Override,@Deprecated,@SuppressWarning。通用springmvc框架就包含的大量的注解内容,如@Controller,@Service,@Transactional等。
实际案例分析:
一、参数非空校验
常见的web请求需要首先对参数进行校验,必传参数不能为空,或者有特殊的格式需求。
1、以参数不能为空为例,首先我们需要自定义一个非空校验的注解:
@Documented
@Inherited
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
boolean require() default true;
}
2、对该注解进行处理:
package reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Logger;
public class StudentParamValidate {
//记录日志
Logger logger = Logger.getLogger("JDKLog");
public void validate() {
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
//判断是否存在注解
if (field.isAnnotationPresent(NotNull.class)) {
//获取注解值
NotNull nn = field.getAnnotation(NotNull.class);
//如果需要参数校验
if (nn.require()) {
if (field.getGenericType().toString().equals("class java.lang.String")
|| field.getGenericType().toString().equals("class java.lang.Integer")) {
try {
//获取get方法
Method getter = this.getClass().getMethod("get" + getMethodName(field.getName()));
//执行get方法
String value = (String) getter.invoke(this);
//判断属性值是否为null
if (value == null) {
System.out.println(field.getName() + "不可为空");
}
} catch (Exception e) {
logger.info(e.getMessage());
}
}
}
}
}
}
/**
* 方法首字母改为大写
*
* @param filedName
* @return
* @throws Exception
*/
public String getMethodName(String filedName) throws Exception {
byte[] items = filedName.getBytes();
items[0] = (byte) ((char) items[0] - 'a' + 'A');
return new String(items);
}
}
3、定义使用该注解的对象:
package reflect;
public class Student extends StudentParamValidate{
@NotNull
private String idNumber;
@NotNull
private String classId;
private String sex;
private String height;
public String getIdNumber() {
return idNumber;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
public String getClassId() {
return classId;
}
public void setClassId(String classId) {
this.classId = classId;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getHeight() {
return height;
}
public void setHeight(String height) {
this.height = height;
}
public void sleep() {
System.out.println("sleep......");
}
}
4、使用该对象:
package reflect;
public class NotNullTest {
public static void main(String[] args) {
Student student = new Student();
student.validate();
}
}
5、运行结果如下:
二、注解的定义和使用
注释:用文字描述程序,给程序员看的;
注解定义:用来说明程序的一个标识,给计算机看的。注解也叫元数据,是一种代码级别的说明。它是jdk1.5之后引入的一个特性,可以使用在字段、类、方法、包、参数等上面。
注意:注解本身并没有任何功能,仅仅起到一个标识性的作用,我们是通过反射去获取到注解,再根据是否有这个注解、注解中的一些属性去判断,执行哪种业务逻辑。
注解作用分类:
①编写文档:通过代码里的注解标识去生成API文档(Swagger);
②代码分析:通过注解去对代码进行逻辑分析(通过反射去操作业务);
③编译检查:通过注解让编译器实现基本的编译检查(Override等)
JDK预定义的一些注解:
①Override:检测该注解标识方法是否继承自父类(接口)
②Deprecated:标识该内容已经过时,后续版本可能会进行移除
③SuppressWarning:压制警告,指定警告级别,这个级别下的警告全部不显示
自定义注解:
在实际开发当中,可能存在一些代码极其复杂或者复用性很低的业务逻辑,比如导出Excel、缓存、将返回值转JSON、事物等等,此时可以使用自定义注解。
注解格式:
Public @interface 注解名称{
属性列表;
}
本质:本质即为接口,该接口继承Annotation接口
属性:
本质:接口中的抽象方法
属性只能是以下几种数据类型:
-
基本数据类型
-
String
-
枚举
-
注解
-
以上类型的数组
如果定义了属性,在使用的时候需要给属性赋值。如果只有一个属性需要赋值,并且这个属性叫value,那么可以省略属性名。数组赋值的事就,值用{}包住,如果数据中只有一个值,那么大括号可以省略。
元注解:
用于描述注解的注解
@Target:描述注解的作用范围
ElementType取值:
TYPE:作用于类;
METHOD:作用于方法;
FIELD:作用于字段
@Rentention:描述注解被保留的阶段
RententionPolicy.RUNTIME:当前描述的注解,会保留到class字节码文件中,并且被JVM读取到
@Documented:描述注解是否被抽取到API文档中;
@Inherited:描述注解是否可以被继承
三、反射的基本原理
反射概念:框架设计的灵魂(框架:半成品软件,开发人员可以在框架的基础之上进行软件开发,简化编码),将类的各个组成部分分装为其他对象,这就是反射机制,注解的运行通过反射进行实现。
Java代码在计算机中经历的阶段:
- 源代码阶段:Hello.java--》(javac编译)--》Hello.class;
- Class类对象阶段:Hello.class--》(类加载器ClassLoader)--》Class类对象(成员变量Field[] fields,构造方法Constructor[] constructors,成员方法Method[] methods)
获取Class类对象的方式:
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象,该方法多用于配置文件,将类名定义在配置文件中,读取文件,加载类;
- 类名.class:通过类名的属性class进行获取,多用于参数的传递
- 对象.getClass():getClass()方法在Object类中已定义,所以所有的对象都会有该方法。多用于通过对象获取字节码方式。
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪种方式获取的Class对象都是同一个。
使用Class对象功能:
获取功能(13个方法,带Declared的表示获取类中所有的成员变量或构造方法或成员方法,无Declared表示只获取类中有public修饰符的成员变量或构造方法或成员方法)
获取成员变量:
Field[] getFields()//获取public成员变量
Field getField(String name)//获取指定名称的public变量
Filed[] getDeclaredFields() //获取所有成员变量
Field getDeclaredFiled(String name)//获取指定名称的成员变量
获取构造方法:
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>… paramterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor getDeclaredConstrctor(类<?>… paramterTypes)
获取成员方法:
Method[] getMethods()
Method getMethod(String name, 类<?>… paramterTypes)
Method [] getDeclaredMethods()
Method getDeclaredMethod (String name, 类<?>… paramterTypes)
获取类名:
String getName()
反射的好处:在程序运行的过程中操作这些对象;解耦,提高程序的可拓展性
反射应用举例:
获取String等类java文件编译成字节码文件,通过将类中的方法放入Class类对象里面的method数组中,形成类的方法提示。Idea运行的程序会加载原有的代码,通过反射的方式获取了方法数组形成了如下图所示提示:
四、其他
学习视频
反射:https://www.bilibili.com/video/av56351262?from=search&seid=5718519849187580994
注解:https://www.bilibili.com/video/av62102209?from=search&seid=15039447101929918322
B站上有非常丰富的技术的视频讲解,尤其是每个视频按照标题进行了系统汇总,资源优质,学习便捷,深感B站是日常网上学习的好工具。