注解的重要性就不用我来说了,controller层有@controller注解,service层有@service注解,基本上到处都是注解,任何一个Java框架都是通过
注解+反射
来实现的!所以注解是Java程序员的必备技能,如果你对注解还不是很了解,那么我强烈建议您把这一篇文章好好读一下!
本篇文章相对来说比较长,但是很多都是代码示例,有时候光通过理论并不能很好的掌握新技能,当看完理论感觉确实掌握了,但是过一段时间就忘了,所以学习一项技能,一定是理论加实践才会更加牢固!
Java注解(Annotation),也叫元数据
。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数
等的前面,用来对这些元素进行说明,注释。
如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:
注解是以‘@注解名
’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解
、单值注解
、完整注解
三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问
。
首先一定要明白:
注解他本身是没有任何作用的
,比如@RequestMapping
,在controller层@RequestMapping
基本上可以说是必见的,我们都知道他的作用是请求映射,通过他来设置请求的url
地址,举例:将@RequestMapping("config")
写在方法上,然后我们就可以通过url
地址访问到这个方法了,但是记住这并不是@RequestMapping
注解的功能,SpringMVC会通过反射将注解当中设置的属性值config
拿到,然后将url
和你声明注解的方法进行绑定。记住:注解只是用来标记,而这个注解真正的功能都是由框架通过反射来实现的
。
元数据是一个非常广泛的概念,元数据的定义也非常简单,只要是描述数据的数据都是元数据
。很简单,一个数字170,单看数据你肯定不知道这个数据代表什么,这就需要元数据支持,你才能明白数据代表的事实是什么。它可能是一个人的身高,也可能指一个人的体重,这需要数据对应的标题、单位等信息来描述这个数据,这些就是描述这个数据的元数据了
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。
赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。
根据注解参数的个数,我们可以将注解分为:标记注解
、单值注解
、完整注解
三类。
标记注解不包含成员/元素
。它仅用于标记声明。
其语法为:@AnnotationName()
@Override
单个元素注解仅包含一个元素。
其语法为:@AnnotationName(elementName = "elementValue")
@AnnotationName(value = "elementValue")
@AnnotationName("elementValue")
这些注解包含多个用逗号分隔的元素。
其语法为:@AnnotationName(element1 = "value1", element2 = "value2")
这几个注解都是java.lang
包下的,也就是Java提供的基础注解,我们在使用的时候是不需要导包的!
此注解可以用在方法,属性,类上,表示不推荐程序员使用,但是还可以使用,示例如下:
/**
* 测试Deprecated注解
* @author Administrator
*/
public class DeprecatedDemoTest {
public static void main(String[]args) {
// 使用DeprecatedClass里声明被过时的方法
DeprecatedClass.DeprecatedMethod();
}
}
class DeprecatedClass {
@Deprecated
public static void DeprecatedMethod() {
}
}
它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
public interface Test {
public String getStr();
}
class TestImpl implements Test{
// 假如返回参数和方法参数其中一个不一致,就会警告
@Override
public String getStr() {
return null;
}
}
我们在写代码的时候,不论是导入的包,还是声明的对象,有时候会出现黄线,感觉就很难受!
@SuppressWarnings注解主要用在取消一些编译器产生的警告,警告对于运行代码实际上并没有影响,但是出于部分程序员具有洁癖的嗜好,通常会采用@SuppressWarnings来消除警告。
示例一:警告如图所示
public interface BannerService {
}
这时候我们在方法上加上@SuppressWarnings注解就可以消除这些警告的产生,注解的使用方式有三种:
通过源码分析可知@SuppressWarnings其注解目标为类、字段、函数、函数入参、构造函数和函数的局部变量。建议把注解放在警告发生的位置。
消除警告:
这个警告是spring framerwork 4.0以后开始出现的,spring 4.0开始就不推荐使用属性注入,改为推荐构造器注入和setter注入。当然他只是推荐,如果我们想要消除警告也可以直接使用
@Resource
。尽管他推荐了,但是一般实际开发当中很少会使用构造器注入和setter注入。
@Autowired 是Spring提供的,@Resource 是J2EE提供的。
@Autowired与@Resource都可以用来装配bean,都可以写在字段或setter方法上
@Autowired默认按类型
装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false
。如果想使用名称装配可以结合@Qualifier
注解进行使用。
@Resource,默认按照名称
进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找
。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配
。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配
。
示例二:警告如图所示
通过添加@SuppressWarnings("unchecked")
来消除unchecked
的警告,这个警告实际上主要是集合没有加泛型导致的!
在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked
警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs
进行标记,这样的话,Java编译器就不会报unchecked警告。
使用前提:
static或者final修饰的方法
!@SafeVarargs
public static <T> T useVarargs(T... args) {
return args.length > 0 ? args[0] : null;
}
被@FunctionalInterface注解标记的类型表明这是一个函数接口
。从概念上讲,函数接口只有一个抽象方法
。也就是一旦不满足函数接口,就会报错,比如他有两个抽象方法。
@FunctionalInterface注解,只能用于接口类。其实,它的应用范围更小,只能应用于接口类型。
@FunctionalInterface
public interface Test {
public String getStr();
}
以下的注解都来源于java.lang.annotation
,描述数据的数据都是元数据
,描述注解的注解都是元注解
!也就是这些注解只能用在修饰注解
上,不能使用在其他地方,比如方法、类等等!
@Native注解除外,他的使用范围是FIELD(字段、枚举的常量),但是@Native也是在
java.lang.annotation
包下!
注解按生命周期来划分可分为3类:
lombok可以通过@Data注解省去get set 方法,实际上@Data的生命周期就是RetentionPolicy.SOURCE,他是通过注解来标记这个方法要生成get set方法,然后在编译的时候直接会生成get set。生成过后,就被抛弃了。
@Documented
和@Deprecated
注解长得有点像,@Deprecated
是用来标注某个类或者方法不建议再继续使用,@Documented
只能用在注解上,如果一个注解@B,被@Documented
标注,那么被@B修饰的类,生成文档时,会显示@B。如果@B没有被@Documented
标注,最终生成的文档中就不会显示@B。这里的生成文档指的JavaDoc文档!
@Deprecated
注解基本上所有框架自定义的注解都会添加,所谓javadoc其实就是JDK给我们提供的一个生成文档的工具!
由于篇幅问题,@Documented详解请看这篇文章:https://blog.csdn.net/weixin_43888891/article/details/126981711
@Target:@Target只能用在注解上,指定修饰的注解的使用范围
比如@Target({ElementType.TYPE, ElementType.METHOD})
,就代表着@RequestMapping可以用在 接口、类、枚举、注解上、还可以用在方法上!
如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解。
当用了
@Inherited
修饰的注解的@Retention是RetentionPolicy.RUNTIME
,则增强了继承性,在反射中可以获取得到
代码示例:
1.首先自定义一个注解使用@Inherited
修饰,然后这个注解用来修饰到父类上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ATable {
public String name() default "";
}
2.这个注解带不带@Inherited都可以,我们主要用来修饰子类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BTable {
public String name() default "";
}
2.定义一个父类跟一个子类,然后在父类上用上我们自定义的@ATable注解
@ATable
public class Super {
public static void main(String[] args) {
Class<Sub> clazz = Sub.class;
System.out.println("============================AnnotatedElement===========================");
// 获取自身和父级带有@Inherited修饰的注解。如果@ATable未加@Inherited修饰,则获取的只是自身的注解而无法获取父级修饰的@ATable注解
System.out.println(Arrays.toString(clazz.getAnnotations()));
System.out.println("------------------");
}
}
@BTable
class Sub extends Super {
}
3.这个是@ATable带有@Inherited修饰的 运行结果。
@Repeatable 注解是用于声明其它类型注解的元注解,来表示这个声明的注解是可重复的。@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解
。
代码示例:
1.自定义Value注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Values.class)
public @interface Value {
String value() default "value";
}
2.自定义Values 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Values {
Value[] value();
}
其中,
@Value
注解上的元注解@Repeatable
中的值,使用了@Values
注解,@Values
注解中包含的值类型是一个@Value
注解的数组!这就解释了官方文档中@Repeatable中值的使用。
3.测试
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
public class AnnotationClass {
@Value("hello")
@Value("world")
public void test(String var1, String var2) {
System.out.println(var1 + " " + var2);
}
public static void main(String[] args) {
Method[] methods = AnnotationClass.class.getMethods();
for (Method method : methods){
if (method.getName().equals("test")) {
Annotation[] annotations = method.getDeclaredAnnotations();
System.out.println(annotations.length);
System.out.println(method.getName() + " = " + Arrays.toString(annotations));
}
}
}
}
4.运行结果
5.假如Value注解没有使用@Repeatable修饰,编译器会报错,是不允许出现注解重复的
周期长度为 SOURCE(源码) < CLASS (字节码) < RUNTIME(运行)
@Native注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可!
我们自定义一个注解,然后这个注解可以在entity当中的set方法上初始化值。只是一个简单案例,供大家参考学习!
第一步:自定义@Init
注解
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) //可以在字段、枚举的常量、方法
@Retention(RetentionPolicy.RUNTIME)
public @interface Init {
String value() default "";
}
第二步:创建User类
public class User {
private String name;
private String age;
public String getName() {
return name;
}
@Init("louis")
public User setName(String name) {
this.name = name;
return this;
}
public String getAge() {
return age;
}
@Init("22")
public User setAge(String age) {
this.age = age;
return this;
}
}
第三步:创建User的工厂类,所谓工厂类就是专门为了生产User对象
import java.lang.reflect.Method;
public class UserFactory {
public static User create() {
User user = new User();
// 获取User类中所有的方法(getDeclaredMethods也行)
Method[] methods = User.class.getMethods();
try {
for (Method method : methods) {
// 判断方法上是否存在Init注解,存在就返回true,否则返回false
if (method.isAnnotationPresent(Init.class)) {
// 此方法返回该元素的注解在此元素的指定注释类型(如果存在),否则返回null
Init init = method.getAnnotation(Init.class);
// 如果Method代表了一个方法 那么调用它的invoke就相当于执行了它代表的这个方法,在这里就是给set方法赋值
method.invoke(user, init.value());
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return user;
}
}
第四步:测试
public static void main(String[] args) {
User user = UserFactory.create();
user.setAge("30");
System.out.println(user.getName());
System.out.println(user.getAge());
}
第五步:运行结果,我们并没有设置User的name属性,只是在User类的 set方法 添加了一个注解。输出结果如下:
JAVA机制反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java注解一旦离开了反射就什么都不是!!!
1.只能拿到public方法(包括继承的类或接口的方法)
public Method[] getMethods()
2.getDeclaredMethods返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
public Method[] getDeclaredMethods()
3.在Class对象和Method对象是都存在isAnnotationPresent这个方法的,作用是判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
4.通过 getAnnotation() 方法来获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
5.或者是 getAnnotations() 方法,获取所有的注解
如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。
public Annotation[] getAnnotations() {}
6.获取到Annotation之后,就可以通过annotationType获取注解的class结构信息,有了class信息就可以获取注解的变量名,等等
Class<? extends Annotation> annotationType();
7.getParameterAnnotations :返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组
public Annotation[][] getParameterAnnotations();
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
import java.lang.reflect.Method;
@TestAnnotation(id = 222,msg = "awdawd")
public class Test {
public static void main(String[] args) {
Method[] declaredMethods1 = TestAnnotation.class.getDeclaredMethods();
for (Method meth : declaredMethods1) {
System.out.println("注解的变量名为:" + meth.getName());
}
// 首先判断Test类上是否使用了TestAnnotation注解
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
// 获取注解,这个相当于是真正的拿到注解了,只有获取到这个才能获取到注解当中设置的值
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("id:" + testAnnotation.id());
System.out.println("msg:" + testAnnotation.msg());
}
}
}
输出结果:
public class User {
private String name;
private String age;
public String getName() {
return name;
}
@Init("louis")
public User setName(String name) {
this.name = name;
return this;
}
public String getAge() {
return age;
}
@Init("22")
public User setAge(String age) {
this.age = age;
return this;
}
}
//读取注解信息
public class ReadAnnotationInfoTest {
public static void main(String[] args) throws Exception {
// 测试AnnotationTest类,得到此类的类对象
Class c = Class.forName("com.gzl.cn.springbootnacos.my.User");
// 获取该类所有声明的方法
Method[] methods = c.getDeclaredMethods();
// 声明注解集合
Annotation[] annotations;
// 遍历所有的方法得到各方法上面的注解信息
for (Method method : methods) {
// 获取每个方法上面所声明的所有注解信息
annotations = method.getDeclaredAnnotations();
System.out.println("方法名为:" + method.getName());
if (method.isAnnotationPresent(Init.class)){
Init annotation = method.getAnnotation(Init.class);
System.out.println("注解设置的value值为:" + annotation.value());
}
for (Annotation an : annotations) {
System.out.println("其上面的注解为:" + an.annotationType().getSimpleName());
// 获取注解class对象
Class<? extends Annotation> aClass = an.annotationType();
// 通过class对象获取他的所有属性方法
Method[] meths = aClass.getDeclaredMethods();
// 遍历每个注解的所有变量
for (Method meth : meths) {
System.out.println("注解的变量名为:" + meth.getName());
}
}
}
}
}
输出结果:
public class User {
@Init("张三")
private String name;
@Init("33")
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
public static void main(String[] args) throws IllegalAccessException {
User user = new User();
user.setAge("111");
user.setName("www");
Class<User> userClass = User.class;
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
System.out.println("属性名称是:" + field.getName());
Init annotation = field.getAnnotation(Init.class);
System.out.println("注解的value值是:" + annotation.value());
// 字段是私有的想要获取就需要将Accessible设置为true,否则报错
field.setAccessible(true);
Object o = field.get(user);
System.out.println("user对象的属性值是:" + o);
System.out.println();
}
}
输出的结果:
@RequestMapping(value = "/get", method = RequestMethod.GET)
@ResponseBody
public boolean get(@RequestParam("username") String usname) {
return useLocalCache;
}
public static void main(String[] args) throws NoSuchMethodException {
Class<ConfigController> configControllerClass = ConfigController.class;
// 获取get方法,第一个参数是方法名,第二个是参数类型
Method get = configControllerClass.getDeclaredMethod("get", String.class);
// 方法上可能有多个参数,每个参数上可能有多个注解,所以是二维数组
// annotations[0][0]表示第1个参数的第1个注解
// annotations[0][1]表示第1个参数的第2个注解
Annotation[][] annotations = get.getParameterAnnotations();
for (int i = 0; i < annotations.length; i++) {
for (int j = 0; j < annotations[i].length; j++) {
if (annotations[i][j] instanceof RequestParam) {
RequestParam myParam1 = (RequestParam) annotations[i][j];
System.out.println(myParam1.value());
}
}
}
}
输出结果:
@RequestMapping当中method属性就是使用的枚举。
@RequestMapping(value = "/get", method = RequestMethod.GET)
@ResponseBody
public boolean get(@RequestParam("username") String usname) {
return useLocalCache;
}
public static void main(String[] args) throws NoSuchMethodException {
Class<ConfigController> configControllerClass = ConfigController.class;
// 获取get方法,第一个参数是方法名,第二个是参数类型
Method get = configControllerClass.getDeclaredMethod("get", String.class);
RequestMapping annotation = get.getAnnotation(RequestMapping.class);
RequestMethod[] method = annotation.method();
RequestMethod requestMethod = method[0];
switch (requestMethod) {
case GET:
System.out.println("get请求");
break;
case POST:
System.out.println("post请求");
break;
}
}
输出结果:
写作不易,如果这篇文章确实帮助到您了,麻烦您给小编留个赞,谢谢!