反射
反射的应用场景是非常多的,框架的底层大多都会涉及到反射,包括我们现在要谈的自定义注解,所以在开始之前,先对反射进行一个基础的介绍。
什么是反射?
官方解释:JAVA反射机制是在
运行状态中
,对于任意一个实体类
,都能够知道这个类的所有属性和方法
;对于任意一个对象
,都能够调用它的任意方法和属性
;这种动态获取信息以及动态调用
对象方法的功能称为java语言的反射机制
详细原理,大家也看下这篇文章,在原理上面说的还是较为详细的Java反射的原理解析及使用
反射常用方法操作解析
直接上代码,我在代码上面有注释,主要是反射操作构造器、方法、属性的常见方法,感兴趣的可以看下
@org.junit.Test
public void reflect() {
Person person = new Person();
person.setName("abc");
person.setDesc("你真棒");
person.setIdCard("321");
// 获取类的class信息
Class<? extends Person> personClass = person.getClass();
// 获取该类的所有public的构造器
// Constructor<?>[] constructors = personClass.getConstructors();
// 获取该类的所有构造器
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
System.err.println("----------操作Constructor-------------");
Arrays.stream(declaredConstructors).forEach(constructor -> System.err.println("modifiers:" + Modifier.toString(constructor.getModifiers()) + ",name:" + constructor.getName()));
// // 获取public 修饰的方法
// Method[] methods = personClass.getMethods();
// 获取class中public、protected,default、private修饰,但是除了继承的方法
Method[] declaredMethods = personClass.getDeclaredMethods();
System.err.println("----------操作Method-------------");
Arrays.stream(declaredMethods).forEach(method -> {
// 这里打印的结果可以看到,继承CustomCheckBase的check方法名,并未被打印
System.err.println(method.getName());
// 方法设置为可使用的,这样的话,我们就可以操作private这些方法,而不单单是public修饰的
method.setAccessible(true);
if (method.getName().startsWith("set")) {
try {
// 通过invoke来执行set的method方法,设置新的值
method.invoke(person, "a");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else if (method.getName().startsWith("get")) {
Object invoke = null;
try {
// 通过invoke来执行get的method方法,得到此刻的值
invoke = method.invoke(person);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
System.err.println(invoke.toString());
}
});
System.err.println(person);
System.err.println("----------操作Field-------------");
// // public修饰的属性值
// Field[] fields = personClass.getFields();
// 所有的属性值,除继承的属性值
Field[] declaredFields = personClass.getDeclaredFields();
Arrays.stream(declaredFields).forEach(field -> {
// 获取属性值的名称
System.err.println(field.getName());
field.setAccessible(true);
try {
// 获取属性值的值
System.err.println(field.get(person));
// 设置属性值的具体值
field.set(person, "123");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
注解
注解简介
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。在聊自定义注解前,先对注解的进行基本的介绍。
java元注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication
使用过springboot的朋友们对上面的代码很熟悉吧,下面三个注解主要涉及到springboot的运行原理、加载机制,这个不是我们本篇讨论的范围,主要是聊下上面四个JAVA的元注解。
@Target
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方),主要是通过 枚举类 ElementType类指定的,像ElementType.TYPE 用于描述类、接口(包括注解类型) 或enum声明 Class, interface (including annotation type), or enum declaration
@Retention
@Retention作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.
从注释上看:
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented
在生成javadoc的时候就会把注解给显示出来。
总结:
该注解在javadoc中显示出来
该注解一直存在(生命周期)
该注解作用在注解上
@Inherited
@Inherited是一个标识,用来修饰注解
作用:如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解,即:A使用了XX注解,XX注解若被@Inherited修饰,则A的子类都将默认使用了XX注解
自定义注解
annotation是挺简单的东西.其实就是个声明。然后通过反射去取那些声明了值,我们写出来一个方法,来通过反射,执行自定义注解的功能,那么什么是否会调用呢,难道还要我们通过代码显示的调用一次吗?也太low了吧,spring提供的注解可不是这样,直接一个注解,即可使用该注解提供的功能。于是我们先聊下spring使用注解的方式,spring首先是通过动态代理来生成代理类bean,通过注解反射得到相应的方法,所以spring实现像@Transaction注解时,通过aop面向切面编程,在调用method.invoke()方法,前置、后置方法对应开启事务、提交事务,即可实现具体的功能。
照猫画虎,依附与spring的aop模式,第一种自定义注解的实现方式就来了
一、声明自定义注解
二、通过切面类来执行注解实现,pointCut切入点就是自定义注解;AOP对应的通知:
前置通知Before advice:
在目标方法执行之前执行
后置通知After returning advice:
在目标方法执行之后执行
异常通知After throwing advice:
目标方法执行后抛出异常才执行
最终通知After (finally) advice:
在目标方法执行之后 都会执行的通知在这些通知上执行我们的注解逻辑代码,即可完成我们想要的自定义注解功能
该自定义注解CheckParamNotNot 的功能:作用于方法上,判断方法入参是否为空或者是空字符串
import java.lang.annotation.*;
/**
* 作用于方法上,判断方法的入参都不为空
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CheckParamNotNot {
}
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MethodCheckParamDefine {
// 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
@Pointcut("@annotation(com.fss.custom.annotation.CheckParamNotNot)")
public void pointcut() {
}
@Around("pointcut()")
public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(o -> {
if(null == o || StringUtils.isBlank(o.toString())){
throw new RuntimeException("参数异常");
}
});
return joinPoint.proceed();
}
}
import com.fss.custom.annotation.CheckParamNotNot;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("rest")
@RestController
public class AnnotationController {
@CheckParamNotNot
@GetMapping("hello")
public String helloAnnaotation(String name, String json){
System.err.println("name:"+name+",json:"+json);
return "SUCCESS";
}
}
在方法上面添加@CheckParamNotNot注解,即可使用注解带来的功能增强
第二种自定义注解方式实现
虽然spring在我们日常开发中必不可少,然而注解时JAVA提供的功能,不应该被Spring束缚住,抛开Spring,有其他方式来实现自定义注解的执行吗?
其实核心问题就在于,使用注解时会自动调用注解的具体实现逻辑,帮助我们增强代码功能,Spring的AOP刚好符合,抛开Spring,好像只能我们添加一行代码,来执行注解的逻辑了,这样显然也不是很好,可以考虑使用继承、实现的方式来实现
下面实现的自定义注解,功能主要是判断某个类的某个属性是否是null、是字符串】是数值的功能。
/**
* 自定义check注解:判断非空、非字符、非数字等,最好可根据参数判断
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CustomCheck {
String field();
/**
* 1:非空;2:非字符;3:非数字
*
* @return
*/
char type() default '1';
}
import lombok.Data;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@CustomCheck(field = "name", type = '2')
@Component
@Data
public class Person implements Serializable, CustomCheckBase {
private static final long serialVersionUID = -2762743486784007569L;
private Person(String idCard) {
}
public Person() {
check(this);
}
// transient 修饰的参数,不会被序列化后,映射出具体值
private transient String idCard;
private String name;
private String desc;
}
import java.lang.reflect.Field;
public interface CustomCheckBase {
// 自定义注解的校验逻辑核心代码
default void check(CustomCheckBase customCheckBase) {
try {
Class<? extends CustomCheckBase> customCheckClass = customCheckBase.getClass();
if (customCheckClass.isAnnotationPresent(CustomCheck.class)) {
CustomCheck customCheck = customCheckClass.getAnnotation(CustomCheck.class);
String checkField = customCheck.field();
char type = customCheck.type();
Field classField = customCheckClass.getDeclaredField(checkField);
classField.setAccessible(true);
Object field = classField.get(customCheckBase);
if (field == null) {
throwCheckException('0');
}
boolean flag = false;
switch (type) {
case '1':
flag = field == null;
break;
case '2':
flag = checkIsString(field);
break;
case '3':
flag = checkIsNumber(field);
break;
default:
break;
}
if (flag) {
throwCheckException(type);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
default boolean checkIsNumber(Object classField) {
try {
Long.valueOf(classField.toString());
return true;
} catch (NumberFormatException e) {
return false;
}
}
default boolean checkIsString(Object field) {
return String.class == field.getClass();
}
default void throwCheckException(char type) {
String msg = null;
if ('1' == type) {
msg = "为空";
} else if ('2' == type) {
msg = "是字符串";
} else if ('3' == type) {
msg = "是数字";
} else if ('0' == type) {
msg = "can not null";
} else {
msg = "未知异常";
}
throw new RuntimeException(msg);
}
}
通过这种方式,当使用自定义注解@CustomCheck时,只需要创建的类实现CustomCheckBase,构造器调用下check(this);即可,或者,想要check时,显式person.check(person)调用,这种方式,虽然可以实现自定义注解,但是和通过Spring的方式来实现,还是有一定的不足。主要是在构造器调用check()还是很不方便的,有一定的代码侵入。大家有什么其他的、更好的实现自定义注解的方式,欢迎交流!!!
三、通过拦截器HandlerInterceptor的preHandle来判断