跟光磊学Java开发-Java注解
跟光磊学Java开发注解概述
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记以后,java编译器、开发工具或者其他的框架就可以通过反射来获取类以及类的成员上的注解,然后通过作相应的处理。
在方法上使用过的@Override
注解,编译器在编译时会检查方法是不是重写父类的方法。
在方法上使用的@Deprecated
注解表示方法已经过时,未来版本可能会删除
在变量、方法、类上使用的@SuppressWarning(all)
忽略警告
在接口上定义的@FunctionInterface
注解,编译器在编译时会检查该接口中是否只有一个抽象方法。
在方法上使用的@Test
注解,用于程序运行时检检测是否符合TestNG单元测试方法规范。
后期再学习框架(MyBatis,Spring Framework,SpringBoot)时还会使用大量的注解配置来简化程序。
自定义注解
在开发框架时会使用到自定义注解,注解和类、接口、枚举(Enum)是同级别关系。
自定义的注解格式如下
public @interface 注解名{
属性
}
注解中可以包含0-N个属性,属性的定义个格式为:数据类型 属性名();
,其中数据类型可以是如下几种
- 八种基本类型:byte,short,int,long,float,double,char,boolean
- java.lang.String类型
- java.lang.Class类型
- 注解类型
- 枚举类型
- 以上5种数据类型的一维数组类型
自定义注解@MyTest
,该注解没有属性
package net.ittimeline.java.core.jdk.feature.java5.annotation;
/**
* 自定义不含属性的注解
*
* @author liuguanglei 18601767221@163.com
* @version 2021/1/1 4:19 下午
* @since JDK11
*/
public @interface MyTest {
}
注解定义完成之后就可以使用注解
/**
* 在方法上使用没有属性的自定义注解@MyTest
*/
@MyTest
public void testAnnotation() {
}
自定义包含多个属性的注解@Table
package net.ittimeline.java.core.jdk.feature.java5.annotation;
/**
* 自定义带两个属性的注解
*
* @author liuguanglei 18601767221@163.com
* @version 2021/1/1 4:23 下午
* @since JDK11
*/
public @interface Table {
/**
* 表名
*
* @return
*/
String name() default " ";
/**
* 表名前缀
*
* @return
*/
String prefix() default "tbl_";
}
然后可以在类上使用带属性的注解,给注解的属性赋值时,如果有多个属性,需要使用逗号分割。
/**
* 在类上使用自定义注解@Table
* 多个属性赋值使用,分割
*/
@Table(name = "tbl_user_info", prefix = "tbl_")
class UserInfo {
}
在使用注解时,有一些注意事项
- 如果注解有了属性,必须赋值,注解的属性可以有默认值,例如
String prefix() default "tbl_";
表示prefix属性的默认值为tbl_,如果不想使用默认值,在使用注解时再给属性赋值。 - 如果属性的类型是一维数组,当数组的值只有一个的时候可以省略
{}
- 如果注解中只有一个属性,并且属性名是value,那么给属性赋值时属性名value可以省略
元注解
JDK提供了@Target、@Retention两个元注解, 元注解表示定在注解上的注解。
@Target
- @Target元注解用于限制注解作用在指定的位置上,默认注解可以在任意位置使用。
@Target注解的value()属性取值类型是ElementType[]数组,常用的位置有- ElementType.METHOD 表示注解作用在方法上
- ElementType.TYPE 表示注解作用在类上
- ElementType.FIELD 表示注解作用在属性上
- ElementType.CONSTRUCTOR 表示注解作用在构造器上
package net.ittimeline.java.core.jdk.feature.java5.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 自定义不含属性的注解
*
* @author liuguanglei 18601767221@163.com
* @version 2021/1/1 4:19 下午
* @since JDK11
*/
@Target(ElementType.METHOD) //自定义MyTest注解只能作用在方法上
public @interface MyTest {
}
@Retention
- @Rentention :定义该注解保留在指定的阶段,取值为RetentionPolicy类型,常用的取值有
- RetentionPolicy.SOURCE 注解保留在源码阶段
- RetentionPolicy.CLASS 注解保留在编译阶段
- RetentionPolicy.RUNTIME 注解保留在运行阶段
Java程序的执行流程是从源码由javac编译生成字节码,然后由JVM解释运行,因此如果@Retentinon注解的属性值是RetentionPolicy.RUNTIME ,意味着源码、编译和运行都会保留注解。
自定义注解@MyTest,保留到运行阶段,只能作用在方法上
package net.ittimeline.java.core.jdk.feature.java5.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义不含属性的注解
*
* @author liuguanglei 18601767221@163.com
* @version 2021/1/1 4:19 下午
* @since JDK11
*/
@Target(ElementType.METHOD) //自定义MyTest注解只能作用在方法上
@Retention(RetentionPolicy.RUNTIME) //自定义MyTest注解作用在运行阶段
public @interface MyTest {
}
自定义注解@Table,只能作用在类上,保留到运行阶段
package net.ittimeline.java.core.jdk.feature.java5.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义带两个属性的注解
*
* @author liuguanglei 18601767221@163.com
* @version 2021/1/1 4:23 下午
* @since JDK11
*/
@Target(ElementType.TYPE) //表示@Table只能在类上使用
@Retention(RetentionPolicy.RUNTIME) // @Table注解保存到运行阶段
public @interface Table {
/**
* 表名
*
* @return
*/
String name() default " ";
/**
* 表名前缀
*
* @return
*/
String prefix() default "tbl_";
}
注解解析
系统自带的注解由系统解析,而自定义注解@Table和@MyTest默认只有标记,并没有其他的功能,如果想要注解提供额外的功能,就需要手动实现。
反射的API们Class,Field,Constructor都实现了java.lang.reflect.AnnotatedElement接口
通过该接口提供的public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
方法就可以获取指定的注解对象以及该对象的属性值
/**
* 自定义注解的使用,获取UserInfo类的@Table注解,并且获取该注解的属性值
*
* @see Class#getAnnotation(Class) 获取指定注解Class对象的注解对象,返回指定的注解对象
* @see Table#name()
* @see Table#prefix()
*/
@Test
public void testUserInfoTableAnnotation() {
//获取UserInfo的class对象
Class<UserInfo> userInfoClass = UserInfo.class;
//获取UserInfo的@Table注解,并返回Table注解对象
Table table = userInfoClass.getAnnotation(Table.class);
//根据注解对象获取属性值
log.info("获取UserInfo类上@Tabele注解的name属性值是{}", table.name());
log.info("获取UserInfo类上@Tabele注解的prefix属性值是{}", table.prefix());
}
程序运行结果
不仅如此,我们还可以使用@MyTest注解实现TestNG的@TestNG的功能
首先准备一个类,该类中包含了9个方法,其中有8个方法使用了@MyTest注解,一个方法没有使用。
@Log4j2
class OrderHandler {
@MyTest
public void testRegister() {
log.info("注册成功");
}
@MyTest
public void testLogin() {
log.info("登录成功");
}
@MyTest
public void testBrowser() {
log.info("浏览商品");
}
@MyTest
public void testAddShopCart() {
log.info("添加购物车成功");
}
@MyTest
public void pay() {
log.info("支付成功");
}
@MyTest
public void order() {
log.info("下单成功");
}
@MyTest
public void doStatisticsIP(UserInfo userInfo) {
log.info("统计用户登录IP信息");
}
public void doStatisticsLoginCount(UserInfo userInfo) {
log.info("统计用户登录次数信息");
}
@MyTest
public String getCurrentDefaultUser() {
return "admin";
}
}
在TestNG框架中,要求可以执行的单元测试方法必须没参数,没返回值,访问权限必须是public,否则无法执行单元测试
然后编写一个处理该注解的handleMyTestAnnotation方法,只需要传一个Class对象即可。
/**
* 处理@MyTest注解,即会反射调用包含@MyTest注解的方法
*
* @param clazz
* @param <T>
*/
public <T> void handleMyTestAnnotation(Class<T> clazz) {
//首先获取Class对象的所有公共权限方法
Method[] methods = clazz.getMethods();
try {
// 然后创建Class的对象
T type = clazz.newInstance();
//然后遍历每个方法
for (Method method : methods) {
//获取方法的名称
String methodName = method.getName();
//如果方法上使用了@MyTest注解
if (method.isAnnotationPresent(MyTest.class)) {
//获取方法的返回值
Class<Void> returnType = (Class<Void>) method.getReturnType();
//方法的返回值必须是void
if (returnType.getName().equals("void")) {
//获取方法的参数
Class<?>[] parameterTypes = method.getParameterTypes();
//方法必须无参数
if (parameterTypes != null && parameterTypes.length == 0) {
//执行方法
method.invoke(type);
} else {
try {
String message = "方法" + methodName + "方法必须没有参数";
throw new IllegalArgumentException(message);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
}
} else {
try {
String message = "方法" + methodName + "的返回值必须是void";
throw new IllegalArgumentException(message);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
}
} else {
log.info("方法{}没有@MyTest注解,不执行", methodName);
}
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
然后在测试方法中testHandleMyTestAnnotation()中调用handleMyTestAnnotation()方法传入OrderHandler的Class对象即可
/**
* 测试解析@MyTest注解
*
* @see CustomAnnotationTest#handleMyTestAnnotation(Class)
*/
@Test
public void testHandleMyTestAnnotation() {
handleMyTestAnnotation(OrderHandler.class);
}
程序运行结果
程序运行结果