专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java注解实战

注解定义

注解其实是一种代码辅助工具,它的核心作用是对类、方法、变量、参数和包进行标注。程序员可以通过反射或者AOP(底层也是反射),获取这些标注信息,从而在运行时改变所注解对象的行为。比如可以对一个方法的调用前后做相应的业务处理等操作。Java注解由内置注解和元注解组成。

注解与注释的区别

  • 注释主要用于代码含义的描述。在编译后的class文件中是不存在的。
  • 注解用于对类、方法、变量、参数和包等信息的标注,根据需要可以保存到class文件中,甚至运行期加载的class对象中。

注解基本组成

创建格式

public @interface 注解名 { }

注解核心要素:元注解

1、 @Retention 定义注解的生命周期:【source -> class ->runtime,一般都使用runtime,表示在运行时存在】
2、 @Documented 文档注解,会被javadoc工具文档化
3、 @inherited 是否让子类继承该注解
4、 @Target 描述了注解的应用范围 【常用的有 -> TYPE :类、解耦、注解、枚举;METHOD:方法;FIELD:属性或枚举常量 】

注解基础案例

根据上面的定义,我们可以尝试定义一个注解@Study,它的生命周期runtime,即运行时存在,应用范围为FIELD和TYPE,因此,它可以放在属性字段或者类上,如Person类所示:

@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Study {

}
@Study
public class Person {

    private String name;
    @Study
    private int age;
}

现在的@Study就是一个最简单的注解。由定义我们可以知道,注解其实就是用来标注信息的,而Java支持注解本身携带一些基本类型的基础信息,从而使得注解能够被更加灵活地使用。现在我们可以在@Study这个注解中,定义两个属性:一个是name,它有默认值 hejianlin;一个是String类型的mores数组,它需要在使用注解时传入。

@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Study {

    //可以包含基本属性,可以定义默认值
    String name () default "hejianlin";

    //这里没有定义默认值,表示在使用注解时需要传入,否则将报错
    String[] mores ();

}
//由于name有默认值,所以这里可以不用传入,mores不传,则会报错
@Study(mores = {"liLei","liMei"})
public class Person {
    private String name;
    @Study(mores = {"liBai","liHei"},name = "liTie")
    private int age;
}

到这里为止,注解其实只是对相应的类或属性做了标注,而我们如何在运行时,对我们所标注的类或属性,实现额外的、自定义的业务呢?这里就涉及到了反射。

反射定义

在运行状态中,能够动态地获取类的所有属性和方法,能够调用任意一个对象的任意一个方法和属性,这种功能就是反射。

常用的反射API

69_1.png

反射基础案例

public class ReflectionDemo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
            InstantiationException, NoSuchMethodException, InvocationTargetException {

        System.out.println("示例1: 通过反射获取class元信息");
        Person person = new Person();
        //通过对象反射得到元信息
        Class<? extends Person> classByObject = person.getClass();
        //通过类名反射得到元信息
        Class<?> classByName = Class.forName("com.hejianlin.model.Person");
        System.out.println("两种方式得到的元信息:"+classByObject+","+classByName+";是否相同:"+classByName.equals(classByObject));

        System.out.println("示例2: 通过反射获取类名、包名");
        String name = classByName.getName();
        String simpleName = classByName.getSimpleName();
        System.out.println("全限制类名:"+name);
        System.out.println("类名:"+simpleName);

        System.out.println("示例3: 获取类属性");
        Field[] declaredFields = classByName.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("类属性:"+declaredField);
        }

        System.out.println("示例4:获取类属性具体值");
        person.setName("jianlin");
        person.setAge(18);
        for (Field declaredField : declaredFields) {
            //设置属性可见,从而可以得到一些私有属性的值
            declaredField.setAccessible(true);
            System.out.println("属性:"+declaredField+",值:"+declaredField.get(person));
        }

        System.out.println("示例5:反射实例化对象");
        Object object = classByName.newInstance();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            //判断属性名是否是name
            if(declaredField.getName().equals("name")){
                //设置name的属性值
                declaredField.set(object,"xiaohong");
            }
            if(declaredField.getName().equals("age")){
                //设置name的属性值
                declaredField.set(object,28);
            }
            System.out.println("属性:"+declaredField+",值:"+declaredField.get(object));
        }

        System.out.println("示例6:反射获取当前类的方法");
        Method[] methods = classByName.getMethods();
        for (Method method : methods) {
            System.out.print(method.getName()+",");
        }
        System.out.println();
        Method getToMethod = classByName.getMethod("toString");
        Object value = getToMethod.invoke(object);
        System.out.println("toString方法返回值:"+value);

    }
}

通过运行这个main方法,我们就可以了解如何在运行时动态获取类的所有属性和方法,结果如下:

示例1: 通过反射获取class元信息
两种方式得到的元信息:class com.hejianlin.model.Person,class com.hejianlin.model.Person;是否相同:true
示例2: 通过反射获取类名、包名
全限制类名:com.hejianlin.model.Person
类名:Person
示例3: 获取类属性
类属性:private java.lang.String com.hejianlin.model.Person.name
类属性:private int com.hejianlin.model.Person.age
示例4:获取类属性具体值
属性:private java.lang.String com.hejianlin.model.Person.name,值:jianlin
属性:private int com.hejianlin.model.Person.age,值:18
示例5:反射实例化对象
属性:private java.lang.String com.hejianlin.model.Person.name,值:xiaohong
属性:private int com.hejianlin.model.Person.age,值:28
示例6:反射获取当前类的方法
equals,toString,hashCode,getName,setName,setAge,getAge,wait,wait,wait,getClass,notify,notifyAll,
toString方法返回值:Person(name=xiaohong, age=28)

注解+反射的使用

首先,我们可以在运行时,通过对象的类类型,获取类上所带的注解,解析注解所携带的信息,还是以上面的ReflectionDemo类为例,代码片段如下:

        System.out.println("示例7: 获取注解");
        Study study = classByName.getAnnotation(Study.class);
        String[] mores = study.mores();
        String name1 = study.name();
        System.out.println("从注解获取的属性值:"+name1+","+mores);

同理,获取方法或属性上的注解,可通过反射得到的Method对象或Field对象,调用getAnnotation方法获取。

注解+反射案例:模拟类对象到sql语句的转化

假设我们现在有张订单表,Java与数据库表映射时,需要建立对应的实体类,由于数据库表名、字段名的语法格式与Java类名、类属性的语法格式不同。我们可以建立相应的注解进行映射。订单类Order如下:

@SqlState("t_order")
@Data
public class Order {

    /**
     * id
     */
    @Column //没有指定字段名,则默认和属性名相同
    private Long id;
    /**
     * 订单号
     */
    @Column("order_no")
    private Long orderNo;
    /**
     * 订单名称
     */
    @Column("order_name")
    private String orderName;
    /**
     * 商店id
     */
    @Column("shop_id")
    private Long shopId;
    /**
     * 用户id
     */
    @Column("user_id")
    private Long userId;

}

这里用到的@SqlState、@Column等注解,就是我们自定义的映射表名、字段名的注解,如下所示:

/**
 * 表名注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlState {

    String value();
}

/**
 * 字段注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {

    String value() default "";
}

试想一下,我们平常日常开发过程中,用的最多的就是单表查询,单表查询的sql语句不外乎

select 字段名 from 表名 where 条件

这种格式吧,而这里的字段名、表名、条件什么的,其实都是可以通过java对象的属性名、类名等反射出来的。所以通过反射,我们就可以动态地拼装单表查询sql语句。mybatis plus其实也是通过反射的机制来实现功能的。

假设我们现在需要根据订单名称和商店id查询对应的订单记录:

public class AppTest 
{
    @Test
    public void testSql() throws Exception {
        Order order = new Order();
        order.setShopId(101L);
        order.setOrderName("维他柠檬茶");
        String querySql = GenerateSqlUtil.query(order);
        System.out.println("查询sql:"+querySql);
    }
}

我们先实例化相应的订单对象,设置商店id和订单名称字段的值,然后调用GenerateSqlUtil.query方法,实现动态sql语句的拼装。GenerateSqlUtil.query方法方法实现如下:

public class GenerateSqlUtil {

    /**
     * sql语句生成器:select 字段名 from 表名 where 条件
     * @param tableObject
     * @return
     * @throws Exception
     */
    public static String query(Object tableObject) throws Exception{
        //select 语句
        StringBuffer sb = new StringBuffer();
        sb.append("select");

        StringBuffer whereSb = new StringBuffer();
        whereSb.append(" 1=1 ");

        //获取类类型
        Class<?> clazz = tableObject.getClass();
        //获取表名注解
        SqlState sqlState = clazz.getAnnotation(SqlState.class);
        if(sqlState == null){
            throw new RuntimeException("表名映射失败!");
        }
        //获取属性
        Field[] declaredFields = clazz.getDeclaredFields();

        //获取表名
        String tableName = sqlState.value();
        //获取select语句
        String selectSqlStr = generateSelectSql(declaredFields);
        //获取where语句
        String whereSqlStr = generateWhereSql(declaredFields, tableObject);
        return selectSqlStr+" from "+tableName+" "+whereSqlStr+" ;";
    }

    /**
     * 生成select语句
     * @param declaredFields
     * @return
     */
    private static String generateSelectSql(Field[] declaredFields){

        StringBuffer sb = new StringBuffer();
        sb.append("select");
        for (Field field : declaredFields) {
            //获取字段名注解
            Column column = field.getAnnotation(Column.class);
            if(column == null){
                throw new RuntimeException("字段名映射失败:"+field.getName());
            }
            String columnName = column.value();
            if(columnName.equals("")){
                //默认使用属性名
                sb.append(" "+field.getName()+",");
            }else{
                //使用字段名
                sb.append(" "+columnName+",");
            }
        }
        //去除末尾的逗号
        sb.deleteCharAt(sb.length()-1);
        return sb.toString();
    }

    /**
     * 生成where语句
     * @param declaredFields
     * @param tableObject
     * @return
     * @throws Exception
     */
    private static String generateWhereSql(Field[] declaredFields,Object tableObject) throws Exception {

        StringBuffer sb = new StringBuffer();
        sb.append("where 1=1");
        for (Field field : declaredFields) {
            //获取字段名注解
            Column column = field.getAnnotation(Column.class);
            if(column == null){
                throw new RuntimeException("字段名映射失败:"+field.getName());
            }

            String columnName = column.value();
            String name = field.getName();
            field.setAccessible(true);
            Object columnValue = field.get(tableObject);
            if(columnValue == null ){
                continue;
            }

            //如果是字符串类,则添加单引号
            if(columnValue instanceof String){
                columnValue="'" + columnValue + "'";
            }

            if(columnName.equals("")){
                //默认使用属性名
                sb.append(" and "+name+" = "+columnValue);
            }else{
                //使用字段名
                sb.append(" and "+columnName+" = "+columnValue);
            }
        }
        return sb.toString();
    }

}

sql语句主要由三部分组成,select语句、表名、where语句,因此各自封装成一个方法,最后拼接在一起就可以了。执行上面的测试类,输出如下的sql语句:

查询sql:select id, order_no, order_name, shop_id, user_id from t_order where 1=1 and order_name = '维他柠檬茶' and shop_id = 101 ;

至此,简单的sql语句通过反射的方式就成功生成了。当然这只是简单示例,如果有兴趣的话,可以阅读mybatis plus的源码,了解如何反射更加复杂的sql语句。

最后,源代码传送门,有需要的请点击,谢谢!

##

文章永久链接:https://tech.souyunku.com/26750

未经允许不得转载:搜云库技术团队 » Java注解实战

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们