java 注解

背景

最近在为公司的技术改造做准备,我设计了一个提高 Web 开发效率的技术框架,为了增加框架的友好性和易用性,决定采用注解来代替配置文件,于是我查询了很多的资料,进行整理和学习。

 

 

²  概念

注解是 JDK5 引入的新特性,最初衍生自代码注释,但现在早已经超出了注释的范畴,以至于我很惶恐,不敢使用注释这个词汇来描述他,尽管现有的很多资料里仍然称其为注释。如果说反射使得很多技术实现(动态代理、依赖注入等)有了基础,那么注解就是使这些技术实现变得平民化的基础。

 

从 class 文件规范中可以看出, JDK5 开始, class 文件已经引入了注解描述片段。站在 java 虚拟机的角度来看, class 保留和运行时保留的注解已经和 java 二进制码放在了同等的地位。虚拟机在加载 class 文件时,会为注解内容分配空间并进行解析,最终还会为注解和对应的二进制码建立关联。尽管这些注解不会被运行,但其对代码的说明能力,结合反射技术已经足够我们做太多的事情。

 

我们知道, java 除了内置的注解( @Override 、 @Deprecated 等)以外,还支持自定义注解( Struts 、Hibernate 等很多框架甚至 java 自身都实现了很多自定义注解)。当然,更为厉害的是元注解,元注解是用来描述注解的注解(光听着就觉得厉害了吧)。

 

要实现一个自定义注解,必须通过 @interface 关键字来定义。且在 @interface 之前,需要通过元注解来描述该注解的使用范围( @Target )、生命周期( @Retention )及其他(其他的不重要,所以领盒饭了)。

 

@Target 用于描述注解的使用范围(即:被描述的注解可以用在什么地方),其取值有:

取值

描述

CONSTRUCTOR

用于描述构造器(领盒饭)。

FIELD

用于描述域(领盒饭)。

LOCAL_VARIABLE

用于描述局部变量(领盒饭)。

METHOD

用于描述方法。

PACKAGE

用于描述包(领盒饭)。

PARAMETER

用于描述参数。

TYPE

用于描述类或接口(甚至 enum )。

 

@Retention 用于描述注解的生命周期(即:被描述的注解在什么范围内有效),其取值有:

取值

描述

SOURCE

在源文件中有效(即源文件保留,领盒饭)。

CLASS

在 class 文件中有效(即 class 保留,领盒饭)。

RUNTIME

在运行时有效(即运行时保留)。

 

根据上述介绍,如果我需要定义一个用于对方法进行描述,且能在运行时可以读取到的自定义注解(假定我希望这个注解的名字是 Sample )。那么,我就应该这样:

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD})

public @interface Sample {

         public String value() default "";

}

 

OK ,自定义注解已经写好了,那我们就可以在代码中使用这个注解了,如:

@Sample(value="I'm here.")

public void anyName() {

         ... ...

}

 

值得一提的是,在网上能搜索到的资料(中文的)几乎都是到此为止了。给人的感觉就像看美国大片,每到结束的时候总会给你一种未完待续的意味。事实上,我能容忍电影给我这样的感觉,因为这样会让我充满期待。而从技术的角度来说,我很厌恶这种感觉。

 

事实上,事情远没有结束。如果自定义注解以这样的形式存在,那么这种存在是没有任何实际意义的。

 

那么,我们接下来该做什么呢?

接下来我们应该编写自己的注解处理器。

 

嗯,再啰嗦一下,提到注解处理器,我又被 N 多资料误导了。很多资料都提到 APT ,或者AbstractProcessor 。但事实上,我的理解是 APT 或者 AbstractProcessor 更多的用于:在非运行时进行增强处理(如:分析逻辑 BUG ,分析代码结构等等)。

 

回到注解处理器,注释处理器其实就是一段用于解释或处理自定义注解的代码而已,没有太多复杂的概念或者技术(嗯,先卖个关子,后面的实例会细说注解处理器的)。

 

²  实践

通过前文对自定义注解的了解,我猜想我应该这样做:

1.       结合实际需求规划注解的功能,以及定义如何解析注解

先说说我的需求吧:框架会把页面划分成 N 个分块,而每个分块都需要不同的类来处理输出内容,处理到不同的分块是,框架会自动创建对应的类实例(目前为止,没有任何问题)。接下来的问题就来了,每个分块处理类处理分块内容时,所需要的参数是不一样的(参数类型以及参数个数都不一样);因此,也不好定义一个固定的接口。当然,肯定有人会说可以把参数改成 map ,或 Object 数组。是的,这是一种解决办法,但是如果我用自定义注解,会不会能更好的完成这项工作呢?是的,答案在你我心中。

 

我们不妨设想一下:

如果处理类需要获取参数,那么这个处理类就给我注解某个方法(方法名任意,前文提到过:虚拟机会做好二者之间的关联),以说明该方法需要被框架预先调用一次(类似初始化方法)。同样的道理,在注解这个方法时,加入所需要的参数注解。

然后,在框架的处理程序中,我们先根据注解查找方法,如果该方法存在,则再次根据注解把对应的参数准备好,然后反射调用 invoke 方法。

OK ,这样的设想应该是行得通的。

 

2.       定义并构造自定义注解

前文提到了我们需要对方法进行注解,而且注解中还需要包含参数信息。好吧,我的设想是定义两个注解:

@RenderParameter 用于描述方法的参数,包括参数类型、参数来源等。

@RenderMethod 用于描述方法(主要描述方法的参数列表)。

 

这里要提到一个小技巧:即注解可以使用数组(嗯嗯,待会会看到的)。

 

先来定义一下 @RenderParameter 吧:

… …

 

@Retention(RetentionPolicy.RUNTIME) // 运行时保留

@Target({ElementType.METHOD})           // 注解对象为方法

public @interface RenderParameter {

 

// 参数类型

public enum ParameterType { STRING, SHORT, INT, BOOL, LONG, OBJECT };

// 参数值的来源

public enum ScopeType { NORMAL, SESSION, COOKIE, ATTRIBUTE, CUSTOM };

 

public String name();        // 参数名

public boolean ignoreCase() default false;      // 匹配时是否忽略大小写

public ParameterType type() default ParameterType.STRING;       // 参数类型

public ScopeType scope() default ScopeType.NORMAL;          // 参数值来源

 

}

 

再看看 @RenderMethod 的定义:

… …

 

@Retention(RetentionPolicy.RUNTIME) // 运行时保留

@Target({ElementType.METHOD})           // 注解对象为方法

public @interface RenderMethod {

 

public enum MethodType { INQUIRE };

 

public MethodType method() default MethodType.INQUIRE;

public RenderParameter[] parameters();        // 参数列表

 

}

 

至此,两个自定义注解已经完成,看看我应该如何使用他们:

@RenderMethod(parameters={@RenderParameter(name="logined", scope=ScopeType.SESSION),@RenderParameter(name="loginedUser", scope=ScopeType.SESSION)})

public void inquire(String logined, String loginedUser) {

if("true".equals(logined)) {

         write(loginedUser + " is logined.");

} else {

         write("No user logined.");

}

}

 

3.       构造自定义注解的处理方法(即注解处理器)

终于又说到注解处理器了,其实很简单:

… …

// 此处的 renderer 就是采用了自定义注解的类实例

for(Method method : renderer.getClass().getDeclaredMethods()) {

RenderMethod rm = (RenderMethod)method.getAnnotation(RenderMethod.class);

 

if(rm != null) {

           int length = rm.parameters().length;

           Object[] parameters = length > 0 ? buildParameters(rm.parameters()) : null;

 

           try {

                    method.invoke(renderer, parameters);

           } catch (IllegalArgumentException e) {

                    log.error(e.getMessage());

           } catch (IllegalAccessException e) {

                    log.error(e.getMessage());

           } catch (InvocationTargetException e) {

                    log.error(e.getMessage());

           }

 

           break;

}

}

… …

 

// 根据注解数组创建参数对象列表,供 invoke 使用

private Object[] buildParameters(RenderParameter[] parameters) {

Object[] objs = new Object[parameters.length];

int i = 0;

 

for(RenderParameter parameter : parameters) {

           ScopeType scope = parameter.scope();

 

           // 参数值来自 request.getParameter

           if(scope == ScopeType.NORMAL) {

                    String temp = request.getParameter(parameter.name());

                    String value = null;

 

                    if(temp != null && !"".equals(temp)) {

                             try {

                                       byte[] bytes = temp.getBytes("iso-8859-1");

                                       value = new String(bytes, "UTF-8");

                             } catch (UnsupportedEncodingException e) {

                                       log.error(e.getMessage());

                             }

                    }

 

                    objs[i ++] = value;

 

           // 参数值来自 Session

           } else if(scope == ScopeType.SESSION) {

                    objs[i ++] = request.getSession().getAttribute(parameter.name());

 

           // 参数值来自 Cookie

           } else if(scope == ScopeType.COOKIE) {

                    for(Cookie cookie : request.getCookies()) {

                             if(cookie.getName().equals(parameter.name())) {

                                       objs[i ++] = cookie.getValue();

                                       break;

                             }

                    }

 

           // 参数值来自 request. getAttribute

           } else if(scope == ScopeType.ATTRIBUTE) {

                    objs[i ++] = request.getAttribute(parameter.name());

           }

}

 

return objs;

}

 

下面例子为注解生成sql 语言例子

 

@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Tractate {
    /*
     * 语言国家化表后缀
     * <p>在当前表名的基础上加上此后缀</p>
     * <p>例如:在产品表为PRODUCT 那么语言表为PRODUCT_ML</p>
     */
    public String value() default "ML";
}

 

 

 

/**
 * 定义国际化查询字段
 * @author 张森
 */
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface TractateField {

}

 

 

/**
 * 定义国际化查询语言
 * @author 张森
 */
@Documented
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface TractateLocale {
}

 

 

    /**
     * 欲查询表别名
     */
    private String tableAliax = "ATN";
    /**
     * 语言表别名
     */
    private String textAliax = "ETN";
    private SessionFactory sessionFactory;

    private String createMutiQuery(Object entity) {
        //查询SQL语句
        StringBuilder sql = new StringBuilder();
        try {
            boolean isFristColumn = true;
            sql.append("SELECT ");
            //当前实体对应表名
            String tableName = null;
            //定义国际化表名前缀
            String suffix = null;
            Locale locale = null;
            Table t = (Table) entity.getClass().getAnnotation(Table.class);
            tableName = t.name();

            Tractate tp = (Tractate) entity.getClass().getAnnotation(Tractate.class);
            if (tp != null) {
                suffix = tp.value();
            }
            for (Method method : entity.getClass().getDeclaredMethods()) {
                //如果有语言国际化
                if (suffix != null) {
                    //获取语言值
                    TractateLocale tl = (TractateLocale) method.getAnnotation(TractateLocale.class);
                    if (tl != null) {
                        locale = (Locale) method.invoke(entity);
                    }
                }
                //获取字段
                Column c = (Column) method.getAnnotation(Column.class);
                if (c != null) {

                    if (!isFristColumn) {
                        sql.append(",");
                    } else {
                        isFristColumn = false;
                    }
                    //如果有语言国际化
                    if (suffix != null) {
                        //获取文本
                        TractateField tf = (TractateField) method.getAnnotation(TractateField.class);
                        if (tf != null) {
                            sql.append(textAliax);
                        } else {
                            sql.append(tableAliax);
                        }
                    }
                    sql.append(".");
                    sql.append(c.name());
                }
            }
            sql.append(" ");
            sql.append(tableName);
            sql.append(" AS ");
            sql.append(tableAliax);
            //如果有国际化
            if (suffix != null) {
                sql.append(" JOIN ");
                sql.append(" ");
                sql.append(tableName);
                sql.append("_");
                sql.append(suffix);
                sql.append(" AS ");
                sql.append(tableAliax);
                sql.append(" ON ");
                sql.append(tableAliax);
                sql.append(".ID = ");
                sql.append(textAliax);
                sql.append(".NO");
                sql.append(" AND ");
                sql.append(textAliax);
                sql.append(".LANG = '");
                sql.append(locale.toString());
                sql.append("'");
            }
        } catch (IllegalAccessException e) {
            sql = null;
        } catch (IllegalArgumentException e) {
            sql = null;
        } catch (InvocationTargetException e) {
            sql = null;
        }
        return sql == null ? null : sql.toString();
    }

SAP FICO CK40N批量发布标准价:常见报错排查与实战解决指南 本文详细解析了SAP FICO模块中CK40N批量发布标准价的常见报错及解决方案。从物料选择、成本核算到最终发布的全流程,提供了针对'已有标准成本估算记号'等典型报错的实战处理技巧,并分享了预防性维护与批量处理的高效方法,帮助财务人员提升工作效率。 阅读详情

相关推荐

Mind+掌控板实战:5分钟搞定Easy IoT的MQTT通讯(附完整代码)

本文详细介绍了如何利用掌控板和Mind+图形化编程软件,快速实现基于Easy IoT平台的MQTT通讯。通过清晰的步骤讲解和完整的代码示例,指导用户完成从云端配置、Wi-Fi连接到MQTT消息发布与订阅的全过程,帮助初学者轻松构建物联网设备间的双向对话系统。

weixin_29300241的博客 449

java自定义注解实践

  ²  背景 最近在为公司的技术改造做准备,我设计了一个提高Web开发效率的技术框架,为了增加框架的友好性和易用性,决定采用注解来代替配置文件,于是我查询了很多的资料,进行整理和学习。     ²  概念 注解是JDK5引入的新特性,最初衍生自代码注释,但现在早已经超出了注释的范畴,以至于我很惶恐,不敢使用注释这个词汇来描述他,尽管现有的很多资料里仍然称其为注释。如果说反射使得很多技...

iteye_16204的博客 211

避开PSRR仿真三大坑:用Cadence psspxf分析分频器时,这些设置错了白忙活

本文深入探讨了使用Cadence psspxf工具进行分频器PSRR仿真时常见的三大技术陷阱,包括扰动信号参数设置、收敛性优化和后处理信号提取。通过详细的参数推荐和实战技巧,帮助工程师避免仿真误差,提升PSRR分析效率与准确性,特别适用于16分频器等数字-模拟混合电路设计。

weixin_29258297的博客 301

java注解 自定义注解类型

java注解 自定义注解类型

weixuan_的博客 931

STM32CubeMX实战:TIM定时器中断配置避坑指南(附100ms精准定时代码)

本文深入解析STM32CubeMX配置TIM定时器中断的常见误区与避坑技巧,重点阐述时钟源、预分频器与自动重装载值的精准计算方法。通过实战案例,提供一份可直接复用的100ms精准定时中断代码模板,并分享调试验证方法,帮助开发者构建稳定可靠的嵌入式系统心跳。

grafana8visual的博客 500

Netron显示自定义属性,按属性设置不同节点的颜色

本文介绍了一种让Netron显示自定义颜色的方法,可根据某些属性,调整节点的颜色 需求背景: 在分析AI模型精度误差时,用不同等级的红色,表示误差的大小.可以直观地看到误差的传递关系

Hi20240217的博客 1513

【Java 注解】自定义注解(注解属性与使用)

Java注解是一种元数据(metadata)机制,它提供了在Java代码中添加附加信息的方式。注解可以应用于类、方法、字段和其他程序元素,以提供关于这些元素的额外信息。注解以符号开头,后面跟着注解的名称。注解可以包含属性,属性可以带有默认值。使用注解时,可以为属性提供值,也可以使用默认值。提供编译时的静态检查和验证。在运行时通过反射获取元数据信息。自动生成代码或配置文件。在框架和库中进行配置和扩展。Java提供了一些内置的注解,如@Override和。

m0_71621983的博客 5956

【Cadence 17.4实战】----- 基于MySQL与ODBC的企业级CIS数据库配置全攻略

本文详细介绍了如何在Cadence 17.4中配置基于MySQL与ODBC的企业级CIS数据库,提升硬件设计团队的元器件管理效率。通过实战案例和避坑指南,帮助读者掌握数据库部署、ODBC驱动配置及CIS参数映射等关键技巧,实现设计数据的实时同步与高效管理。

weixin_30591551的博客 327

android架构师之路——面向对象数据库

简介 BaseDaoFactory 数据库创建,初始化 IBaseDao 增删改查接口 BaseDao 增删改查具体的实现 User 增删改查的对象 DbField 数据库字段名字标注 DbTable 数据库表名注解 我们这里只要是学习对象型数据库实现原理,如果要直接使用,可以查看GreenDao直接使用 BaseDaoFactory public class BaseD...

zkq49099的专栏 249

BMS控制板硬件设计核心要点与工程避坑指南

电池管理系统(BMS)硬件设计是保障锂电安全与寿命的关键环节,其本质是模拟前端精度、数字控制可靠性、电源完整性及电磁兼容性四大要素的系统协同。理解AFE芯片(如BQ76920)的物理层约束、MCU与专用监控IC的异构分工逻辑,是构建高鲁棒BMS的基础;而晶振布局、I²C总线匹配、MOSFET体二极管方向、开尔文采样等细节,直接决定量产良率与EMC合规性。在电动工具、储能系统、两轮电动车等中低功耗BMS应用中,成本敏感型方案常采用STM32F0+AFE架构,此时电源滤波电容选型、复位电路ESR控制、TVS/齐

weixin_30591519的博客 260

VS2019编译配置Easy3D

VS2019编译配置Easy3D

dayuhaitang1的博客 2598

Java注解

为什么要引入注解? 使用【注解】之前(甚至在使用之后),【XML】被广泛的应用于描述元数据,得到各大框架的青睐,它以松耦合的方式完成了框架中几乎所有的配置,但是随着项目越来越庞大,【XML】的内容也越来越复杂,一些应用开发人员和架构师发现维护成本变高。他们希望使用一些和代码紧耦合的东西,于是就有人提出来一种标记式高耦合的配置方式【注解】。方法上可以进行注解,类上也可以注解,字段属性上也可以注解,...

ThinkWon的博客 2万+

QGis软件 —— 9、QGis - 由点绘制热力图来模拟人流量(绘制点方式、excel导入数据方式)

QGis软件 —— 9、QGis - 由点绘制热力图来模拟人流量(绘制点方式、excel导入数据方式)

1470

JAVA注解

本文将向你介绍J2SE5.0中的新特性之一:注解。本文将从什么是注解;J2SE5.0中预定义的注解;如何自定义注解;如何对注解进行注解以及如何在程序中读取注解5个方面进行讨论。     一、什么是注解     说起注解,得先提一提什么是元数据(metadata)。所谓元数据就是数据的数据。也就是说,元数据是描述数据的。就象数据表中的字段一样,每个字段描述了这个字段下的数据的含义。而J2SE

rj042的专栏 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

java注解怎么写_java注解

写在前面现在的java主流框架中,注解无处不在,因此我们完全有必要搞明白注解到底是什么。如何理解注解,它到底是怎么起作用的。注解的定义java是这样定义注解的:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。什么又是元数据的呢?说白了就是描述数据的数据,既然注解被称为元数据,具有描述作用,那么我们是不是可以把注解理...

weixin_42476912的博客 2245

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

java 注解 entity_Java 注解

一. 简介注解是java5的新特性。注解可以看做一种注释或者元数据(MetaData),可以把它插入到我们的java代码中,用来描述我们的java类,从而影响java类的行为。二. 基本语法1. 注解的使用形式:一个java注解由一个@符后面跟一个字符串构成,类似于这样:@Entityjava注解中一般包含一些元素,这些元素类似于属性或者参数,可以用来设置值,比如我们有一个包含两个元素的@Enti...

weixin_42384743的博客 2349

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Java注解与元注解

使用@interface关键字来定义注解,注解中的方法称为注解元素,注解元素可以有默认值。// 定义一个简单的注解元注解是用于注解其他注解的注解,它们可以控制注解的使用范围、生命周期等。Java 提供了几个元注解,常用的有@Retention@Target和@Inherited。

dsq_MaDing的博客 683

java 注解 数组_Java注解,就是那么简单

深入理解注解1、基本介绍2 内置注解2.1@Deprecated注解2.2@Override注解2.3@SuppressWarnings注解3、元注解简介3.1@Target3.2@Rentention3.3@Documented3.4@Inherited4、自定义注解5、获取注解中的参数值5.1参数值是基本类型5.2参数值是对象1、基本介绍基本概念:注解,顾名思义,就是对某一事物进行添...

weixin_34017915的博客 4110

java注解的使用及常用注解

java注解的使用及常用注解

qq_42003527的博客 2342

java 注解 id_java注解

java注解:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。注解理解的前提:我认为注解就是标签它是在 Java SE 5.0 版本中开始引入的概念,就如同java中的 class和interface说到这个就不得不说注解中的基础元注解元注解: 元注解是可以...

weixin_42547335的博客 1297

Java注解以及自定义注解

Java注解以及自定义注解

YIYIYI 1968

java 注解 default_java注解

元注解:元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:1.@Target,2.@Retention,3.@Documented,4.@Inherited这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和...

weixin_39889329的博客 1859

java 注解和 重复注解

1、什么是java注解 Annotation(注解)是JDK5.0及以后版本引入的一个特性。注解是java的一个新的类型(与接口很相似),它与类、接口、枚举是在同一个层次,它们都称作为java的一个类型(TYPE)。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。它的作用非常的多,例如:进行编译检查、生成说明文档、代码分析等。 2.jdk提供的3个基本注解

u012964143的专栏 3503
上一篇: 解决jquery load 缓存数据 .
下一篇: IBM MQ 实现方式java
蓝影138
博客等级 码龄19年 2粉丝 109原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值