# 解析java注解 **Repository Path**: miuu_note/parsing_java_annotations ## Basic Information - **Project Name**: 解析java注解 - **Description**: 利用反射,解析java注解 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-04-24 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [TOC] > 简介:[慕课网学习笔记] 在项目开发中,注解的使用无处不在。注解的使用简化了代码,减少了程序员的工作量。本课程带领小伙伴们全面认识Java的注解,包括为什么使用注解、Java中的常见注解、注解的分类和如何自定义注解,最后通过一个实战案例来演示注解在实际项目中的应用。 ## 第1章 概述 ### 1-1 Java注解概述 #### 1)为什么要学习注解?它的好处?学完能做什么? 1.能够读懂别人写的代码,特别是框架相关的内容 2.让编程更新简洁,代码更加清晰,如使用注解可以把配置文件或者写的程序的逻辑省略掉 3.显得专业,有水准(spring\mybatis应用了大量的注解) 4.会使用自定义注解来解决问题 #### 2)概念:注解是java1.5版本引入的,Java提供了一种原程序中的元素关联任何信息和任何元数据的方法. ## 第2章 Java中的常见注解 ### 2-1 JDK中的注解 @Override 使用的实现类,表示方法覆盖 @Deprecated 使用的接口,表示方法过时 @Suppvisewarnings 使用:@SuppressWarnings("deprecation") 在方法上使用,压制警告提示 ### 2-2 Java第三方注解 常见第三方注解 ```mermaid graph LR 常见第三方注解: A[Spring] A --> a-1[autowird] A --> a-2[Service] A --> a-3[Repository] B[Mybatis] B --> b-1[InsertProvider] B --> b-2[UpdateProvider] B --> b-3[Options] ``` 如mybatis使用配置文件和注解形式的不同: ```java //类的实现: public class UserManagerImpl implements UserManager { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } } ``` ```xml //配置文件 ``` ```java //引入@Autowired注解 public class UserManagerImpl implements UserManager{ @Autowired private UserDao userDao; ... //自动将实例注入到spring容器中 } ``` ## 第3章 注解的分类 ### 3-0 原理 官方定义的,类似于在代码中打一个标记,有分编译时和运行时。 对注解进行解析的代码在编译器和运行时中,简单说就是编译器和运行时发现类似的@xxx就看看是不是有对应的注解类型,有就对其进行相应的处理。 至于如何去找这个注解标记,那就去看看编译器和jvm的源码了,一般学习不用纠结。 就好比定义一个整型变量就是用int,这就是语法! 如果第三方的话,一般是属于运行时注解,通过反射调用的。即通过反射调用某个方法,加载某个类等等操作的时候,获取注解信息(反射api有提供方法获取),然后针对相应的注解进行处理。 ### 3-1 Java注解的分类 ```mermaid graph TB A[按照运行机制分类] A-->a1[源码注解] A-->a2[编译时注解] A-->a3[运行时注解] B[按照来源分类] B-->b1[来自JDK的注解] B-->b2[来自第三方的注解] B-->b3[自定义注解] ``` #### 含义: **源码注解**: 注解只在源码中存在,编译成.class(二进制字节码)文件就不存在了. **编译时注解**:注解在源码和.class文件中都存在 (jdk注解属于编译时注解 @Override等) **运行时注解**:在运行阶段还起作用,甚至会影响运行逻辑的注解.(如@Autowired,在程序运行时,把成员变量自动注入到spring容器中) **元注解**:注解的注解(自定义注解用到) ## 第4章 自定义注解 ### 4-1 Java自定义注解 #### 语法要求 ```java @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Description { String desc(); String author(); int age() default 18; } ``` 使用@interface关键字定义注解 成员以无参无异常方式声明 可以给成员指定一个默认值 成员的类型是受限的,合法的类型包含原始类型(int,double等基本数据类型)及String,Class,Annotation,Enumeration 如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=), 如@RequestParam(value = "organizationId",required = false) Long organizationId,其value可以省略 没有成员的注解,称为标识注解,如@Deprecated即为此类注解,标识过期 #### 详解: 1) @Target({ElementType.METHOD,ElementType.TYPE}) 注解作用域列表: CONSTRUCTOR - 构造方法声明 FIELD - 字段声明 LOCAL_VARIABLE - 局部变量声明 METHOD - 方法声明 PACKAGE - 包声明 PARAMETER - 参数声明 TYPE - 类,接口 2)@Retention(RetentionPolicy.RUNTIME) [运行机制-生命周期] SOURCE -只在源码显示,编译时会丢弃 CLASS - 编译时会记录到class中,运行时忽略 RUNTIME - 运行时存在,可以通过反射读取 3)@Inherited: 允许子类继承 4)@Documented 生成javadoc时会包含注解信息 ### 4-2 使用自定义注解 使用注解的语法: @<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>,...) ```java @Description(desc=‘“I am eyeColor”,author=“Mooc boy”,age=18) public String eyeColor(){ return “red”; } ``` ### 4-3 解析注解 #### 概念: 通过反射获取类,函数或成员上的运行时注解信息从而实现动态控制程序运行的逻辑. ```java @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Description { String value(); } ``` ```java public interface Person { /** 定义三个方法 */ public String name(); public int age(); @Deprecated public void sing(); } ``` ```java @Description("I am class annotation") public class Child implements Person { @Override @Description("I am method annotation") public String name() { return null; } @Override public int age() { return 0; } @Override public void sing() { } } ``` ```java public class ParseAnn { public static void main(String[] args) { //1.使用类加载器加载类(反射) try { Class c = Class.forName("com.testmiuu.club.annotation.Child"); //2.找到类上面的注解 //判断这个类上是否存在Description的注解 boolean isExist = c.isAnnotationPresent(Description.class); if(isExist) { //拿到注解实例 需要强转 Description d = (Description) c.getAnnotation(Description.class); System.out.println(d.value()); } //4.找到方法上的注解 //遍历所有的方法 Method[] ms = c.getMethods(); for (Method m : ms) { //指定拿Description的注解 boolean isMExist = m.isAnnotationPresent(Description.class); if(isMExist) { Description d = (Description) m.getAnnotation(Description.class); System.out.println(d.value()); } } //另外一种解析方法 for (Method m : ms) { Annotation[] as = m.getAnnotations(); for (Annotation a : as) { //遍历查找Description的注解 if(a instanceof Description) { Description d = (Description)a; System.out.println(d.value()); } } } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } ``` @Inherited:在实现接口没有作用,只有实现的是继承类才有作用 只会继承类上面的注解,而不会继承方法上的注解 ## 第5章 项目实战 ### 5-1 项目需求 #### 项目说明 一个公司的持久层架构,用来代替Hibernate的解决方案,核心代码就是通过注解来实现的. #### 需求 1.有一张用户表,字段包括用户ID,用户名,昵称,年龄,性别,所在城市,邮箱,手机号. 2.方便的对每个字段或字段的组合条件进行检索,并打印出SQL. 3.使用方式要足够简单,见代码示例. ### 5-2 项目实现(上) 首先,考虑代码如何与数据库进行映射(表名,字段名) Filter类和数据库表结构相似,写上注解 @Table(“user”) @Column(“id”) ```java @Setter @Getter @Table("user") public class Filter { @Column("id") private int id; @Column("user_name") private String userName; @Column("nick_name") private String nickName; @Column("age") private int age; @Column("city") private String city; @Column("email") private String email; @Column("mobile") private String mobile; } ``` ```java /** * Table * @author yuzhong 2019/4/23 * 三方认证 */ @Target({ElementType.TYPE}) //ElementType.TYPE 表示作用域是类或接口 @Retention(RetentionPolicy.RUNTIME) //生命周期 public @interface Table { String value(); } ``` ```java @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String value(); } ``` 接下来,考虑query()方法的实现,返回SQL ### 5-3 项目实现(下) ```java package com.testmiuu.club.hibernate; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author yuzhong 2019/4/23 */ public class Test { public static void main(String[] args) { Filter f1 = new Filter(); f1.setId(10); //查询id为10的用户 f1.setEmail("hha@qq.com"); Filter f2 = new Filter(); f2.setUserName("lucy"); //模糊查询用户名为lucy的用户 Filter f3 = new Filter(); f3.setEmail("2211735722@qq.com,yuzhong@163.com,yuzhong@sina.com"); //查询邮箱为其中任意一个的用户 String sq1 = query(f1); String sq2 = query(f2); String sq3 = query(f3); System.out.println(sq1); System.out.println(sq2); System.out.println(sq3); Student s1 = new Student(); s1.setId(1); s1.setUserName("快乐"); s1.setAddress("上海"); System.out.println(query(s1)); } private static String query(Object f) { StringBuilder sb = new StringBuilder(); //1.获取到class Class c = f.getClass(); //2.获取到table的名字 boolean exists = c.isAnnotationPresent(Table.class); if (!exists) { return null; } Table t = (Table) c.getAnnotation(Table.class); String tableName = t.value(); sb.append("select * from ").append(tableName).append(" where 1=1"); //3.遍历所有的字段 Field[] fArray = c.getDeclaredFields(); for (Field field : fArray) { //4.处理每个字段对应的SQL //4.1 拿到字段名 boolean fExists = field.isAnnotationPresent(Column.class); if (!fExists) { continue; } Column annotation = field.getAnnotation(Column.class); String columnName = annotation.value(); //4.2 拿到字段值 //取字段的名称 String fieldName = field.getName(); //获取get方法的名字,通过反射调用这个方法,拿到对应的值 get+字段名 首字母大写 String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); Object fieldValue = null; try { Method getMethod = c.getMethod(getMethodName); fieldValue = getMethod.invoke(f); } catch (Exception e) { e.printStackTrace(); } //4.3 拼装SQL if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) { continue; } sb.append(" and ").append(columnName); //判断字符串要加单引号 if (fieldValue instanceof String) { if (((String) fieldValue).contains(",")) { String[] values = ((String) fieldValue).split(","); sb.append(" in("); for (String v : values) { sb.append("'").append(v).append("'").append(","); } sb.deleteCharAt(sb.length() - 1); sb.append(")"); } else { sb.append("=").append("'").append(fieldValue).append("'"); } } else if (fieldValue instanceof Integer) { sb.append("=").append(fieldValue); } } return sb.toString(); } } ``` ## 第6章 课程总结 ### 6-1 课程总结 #### 1.认识注解 #### 2.注解的作用范围@Target和生命周期@Retention 作用范围: 包/类/字段/方法/方法的参数/局部变量 生命周期:源文件SOURCE/编译CLASS/运行RUNTIME #### 3.能读懂注解 #### 4.能在实际项目中用到注解解决问题,并能自定义注解