1注解的概念
1 注解的引入
为什么需要学习注解?
1.能够看懂别人写的一些代码,最后好能够看懂一写框架代码
2.注解结合反射和泛型能够将代码写的更加灵活,更加动态,更加简洁,提高了代码扩展性和可维护性
3.能够使用自定义注解解决开发中的一些问题
2注解的概述
Java提供了一种源程序中的元素和任何信息或者元数据相关联的方法或者渠道
源程序中元素: Java文件 构造方法、成员变量、成员方法…
任何信息: 就是解释说明,可以理解为注释,但是和注释又不同,注释只能够在源码中存在,程序运行的时候,不会被解释执行,注解可以作用在不仅是源码上,还可以作用在编译时期以及运行时期,特别是在运行时期, 能够通过反射获取到这些信息,根据这些信息能够动态地改变程序的逻辑,从而提高代码的扩展性和可维护性,就是一些配置数据
元数据: 就是元注解,对注解进行注解,可以理解为满足某种结构的数据 key = value
关联: 注解和元素绑定的手段
简单来说: 就是相当于一些类似于注释的信息 和 源程序中的元素 绑定的一种技术,这种技术叫做注解, 这种手段可以通过反射获取到元素对象,在获取到这个元素上面绑定的注解,通过绑定的注解获取里面配置信息, 从而达到能够在程序运行阶段通过这些信息动态地改变程序的运行逻辑
Java常见的一些注解:
@Override: 表示该注解修饰的方法必须满足重写的规则,否则编译报错,该注解由编译器提取识别
@Deprecated: 表示成员过时,编译器会在程序运行(编译时/源码是)的时候获取到成员变量,
提取类中的成员上是否存在@Deprecated这个注解,如果存在,编译器就会添加删除线
@SuppressWarnings: 表示忽略警告,我们可以通过这个注解消除黄色警告线,但是并没有去除安全隐患
@FunctionInterface: 表示当前接口是一个函数式接口
函数式接口: 当一个接口中只有一个抽象方法的时候成为函数式接口
3注解的分类:
按照运行机制分类
1源码注解: 注解只在源码有效,当编译器编译生成字节码文件的时候,该注解就不存在,其实就是和 注释一模一样
2编译时注解: 该注解在源码和编译时期有效,在JVM执行的过程中,该注解就不存在了
3运行时注解: 该注解在源码和编译时甚至运行时有效,在程序执行过程开发者还可以获取到该注解,同时获取到注解上的元数据,从而动态地改变程序的运行逻辑以及执行结果
4元注解: 对注解进行注解的注解
按照来源分类
JDK中自带的注解: @Override,@Deprecated
第三方注解: @Table,@Column,@Component,@Service,@Bean
自定义注解: 自己定义注解自己使用在自己的代码中
元注解: 对注解进行注解的注解
// 这是注解测试类
public class AnnotationDemo01 {
// desc="主方法", method="main", arguments=args
public static void main(String[] args) {
}
}
class Fu {
@SuppressWarnings("unused")
public void method() {
// @SuppressWarnings("unused")
int a;
}
}
@Deprecated
class Zi extends Fu{
@Override
@Deprecated
public void method() {
super.method();
}
}
@FunctionalInterface
interface Inter{
void show();
// void method();
}
2注解的基本语法
1注解语法
1.注解是由@interface来修饰
2.注解也是有成员的,但是这里的成员类似于成员方法,但是又要理解为成员变量
注意: 这里的成员本质是属性,可以理解为成员变量,成员变量不能够抛出异常,也不能有参数
3.成员可以是常量,也可以有变量
4.注解成员的数据类型
only primitive type, String, Class, annotation,
enumeration are permitted or 1-dimensional arrays thereof
八大基本类型 String Class 注解 枚举 一维数组
5.注解的默认值格式: int age() default 18;
6.如果一个注解没有任何成员,表示标记注解
7.有一个默认的规范,如果一个注解只有一个成员,那么该注解的成员一般叫做value
2元注解
通过学习元注解来学习注解的语法
JDK中非常重要的四个元注解:
@Target: 该元注解所标识的注解能够作用在什么元素(元素的参考范围可以参考 ElementType枚举类)
@Retention: 该元注解所标识的注解的生命周期 (生命周期参考范围参考RetentionPolicy枚举类)
@Inherited: 该元注解所标识的注解可以被继承
public class AnnotationDemo02 {
}
@interface Table {
String name() default "隔壁老王";
int age() default 18;
final int num = 100;
// List<String> list();
Class<String> c();
Override override();
ElementType type();
int[] arr();
// double[][] arr2();
}
@interface Column {
String value();
}
3注解的使用
注解的使用
四种格式:
格式一: @注解名称(属性名称1=属性的值1,属性名称2=属性的值2,属性名称3=属性的值3,…,属性名称n=属性的值n)
注意: 有多少个属性就写多少个
格式二: @注解名称(属性的值1)
注意: 适用于只有一个属性的情况
格式三: @注解名称({属性的值1,属性的值2,属性的值3,…,属性的值n})
注意: 适用于属性类型是数组的情况
格式四: @注解名称
注意: 适用于是标记注解的情况
public class AnnotationDemo03 {
}
@MyAnnotation(name = "张三", age = 22)
class Person {
}
class Student extends Person{
@MyAnnotation(name = "张三", age = 22)
private String name;
private int age;
private String gender;
@MyAnnotation(name = "张三", age = 22)
public Student() {
super();
}
public Student(String name, int age, String gender) {
super();
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
@MyAnnotation(name = "张三", age = 22)
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", gender=" + gender + "]";
}
}
4使用反射解析注解信息
1.当使用反射解析类或者接口或者枚举上面的注解的时候,使用字节码文件对象来获取注解对象
2.解析除了类或者接口或者枚举上面的注解的时候,必须使用对应元素的对象来获取注解对象
3.在获取某个元素上的注解对象的时候,必须先判断元素上面的注解是否存在【存在性判断】,如果存在才获取
4.如果要想获取注解里面的成员信息【元数据】,必须先获取到注解对象
5.获取到注解之前,第一步还必须要判断该注解是运行时注解
原因: 如果是源码注解或者编译注解,那么获取的注解对象是null的获取不到
1通过反射获取注解对象相关的方法:
Annotation[] getAnnotations() 获取到元素上面的所有注解
Annotation[] getDeclaredAnnotations() 获取到元素上面的所有注解
A getAnnotation(Class annotationClass) 获取到元素上面指定的注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断指定注解是否存在
public class AnnotationDemo01 {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
Class<Student> c = Student.class;
boolean annotationPresent = c.isAnnotationPresent(MyAnnotation.class);
System.out.println(annotationPresent);
MyAnnotation myAnn = c.getAnnotation(MyAnnotation.class);
String name = myAnn.name();
int age = myAnn.age();
System.out.println(name + "=" + age);
Field nameField = c.getDeclaredField("name");
boolean isMyAnnoPresentInNameField = nameField.isAnnotationPresent(MyAnnotation.class);
if (isMyAnnoPresentInNameField) {
MyAnnotation annotation = nameField.getAnnotation(MyAnnotation.class);
if (annotation != null) {
// 提取注解信息
System.out.println(annotation.name() + "=" + annotation.age());
}
}
// 获取toString方法上的注解信息
Method toStringMethod = c.getMethod("toString");
Annotation[] annotations = toStringMethod.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
if (annotation instanceof Deprecated) {
Deprecated deprecated = (Deprecated) annotation;
System.out.println(deprecated.toString());
} else if (annotation instanceof MyAnnotation) {
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println(myAnnotation.name() + "=" + myAnnotation.age());
}
}
System.out.println("=========================");
// 获取Student类上的注解
Annotation[] annos = c.getAnnotations();
for (Annotation annotation : annos) {
System.out.println(annotation);
if (annotation instanceof Table) {
Table table = (Table) annotation;
System.out.println(table.value());
}
}
}
}
2定义注解
// 希望定义注解的作用域
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD })
// 希望定义注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
// 希望定义该注解能够在生成API的时候出现
@Documented
// 希望定义该注解能够被继承
3编写工具类
利用反射 + 注解 + 泛型 编写一个map和javabean互相转换的工具类
Map和JavaBean进行相互转换 如下
public class MapBeanUtils {
private MapBeanUtils() {
}
/*
* 将任意的Map集合转换成对应Bean
*/
public static <T> T mapToBean(Map<String, Object> map, Class<T> clazz) throws Exception {
// 通过反射创建Bean对象
T t = clazz.newInstance();
// 通过反射获取到每一个成员变量对象
Field[] fields = clazz.getDeclaredFields();
// 遍历每一个成员
for (Field field : fields) {
if (field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
String key = column.value();
Object o = map.get(key);
if (o != null) {
String fieldName = field.getName();
// 获取set方法
String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method setMethod = clazz.getMethod(setMethodName, o.getClass());
setMethod.invoke(t, o);
}
}
}
}
return t;
}
/*
* 将Bean转换成对应的Map集合
*/
public static <T> Map<String, Object> beanToMap(T t) throws Exception {
// 创建一个空的容器来装载Bean的数据
Map<String, Object> map = new HashMap<>();
// 获取bean对象中的字节码文件对象
Class<?> c = t.getClass();
// 获取到所有成员量对象
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
// 获取到每一个成员变量对象
if (field.isAnnotationPresent(Column.class)) {
Column columnAnnotation = field.getAnnotation(Column.class);
if (columnAnnotation != null) {
String key = columnAnnotation.value();
// 通过注解绑定的属性名,去bean中获取值
String fieldName = field.getName();
// 获取到get方法
String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1);
Method getMethod = c.getMethod(getMethodName);
Object getValue = getMethod.invoke(t);
map.put(key, getValue);
}
}
}
return map;
}
}
4 单元测试
单元测试: 针对某个方法进行独立测试,提高了程序的可维护性
自定义单元测试注解,来测试我们写的方法
单元测试的使用步骤
1.书写一个方法
这个方法必须没有参数没有返回值
访问权限修饰是public
方法名建议使用test+需要测试的类或者方法的名称
2.添加单元测试注解
3.右键 Run as -> Junit Test
public class JunitTest {
public static void main(String[] args) {
// 告诉主方法你要测试哪个类的哪个方法
try {
TestHandler.process("com.sxt.junitdemo.MyJunitTest");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
* 定义一个测试处理类,用来处理我需要测试的方法
*/
class TestHandler {
public static void process(String className) throws Exception {
int success = 0;
int faild = 0;
Class<?> c = Class.forName(className);
Object instance = c.newInstance();
Method[] methods = c.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Testable.class)) {
try {
method.invoke(instance);
success ++;
} catch (Exception e) {
System.out.println("方法" + method.getName() + "运行异常:" + e);
faild ++;
}
}
}
System.out.println("一共运行了" + (success + faild) + "个方法,"
+ success + "个方法成功了," + faild + "个方法失败了");
}
}
public class TestJunit {
Expression expression = null;
int result = 0;
@Before
public void before() {
System.out.println("测试前的代码");
expression = new Expression();
}
@Test
public void test() {
result = expression.divide(10, 5);
}
@Test
public void test2() {
result = expression.multiply(10, 5);
}
@After
public void after() {
System.out.println("测试后的代码");
System.out.printl