http://anole1982.iteye.com/blog/1450421

http://www.open-open.com/doc/view/51fe76de67214563b20b3853203982df

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();
    }

java自定义注解注解方法、类、属性等等【转】的更多相关文章

  1. 阶段3 1.Mybatis_12.Mybatis注解开发_5 mybatis注解建立实体类属性和数据库表中列的对应关系

    创建新项目,一对多 复制刚才关闭的项目的文件 复制到们的新项目里面 复制包的依赖 删减相关代码.只保留这三个查询的方法 模糊查询改成传统的占位符的方式 之前是可以自定义实体类的属性字段,和数据库的字典 ...

  2. java 自定义的注解有什么作用

    转自https://zhidao.baidu.com/question/1668622526729638507.html 自定义注解,可以应用到反射中,比如自己写个小框架. 如实现实体类某些属性不自动 ...

  3. mybatis注解开发实体类属性和数据库字段不对应问题

    /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap",va ...

  4. Java自定义 sort 排序方法

    Sort用法 •结构 1 package Test; 2 3 import java.util.Arrays; 4 import java.util.Random; 5 import java.uti ...

  5. 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 SpringBoot是如何实现自动配置的?--SpringBoot源码(四) 温故而知新,我们来简单回顾一下上 ...

  6. python面向对象学习(六)类属性、类方法、静态方法

    目录 1. 类的结构 1.1 术语 -- 实例 1.2 类是一个特殊的对象 2. 类属性和实例属性 2.1 概念和使用 2.2 属性的获取机制 3. 类方法和静态方法 3.1 类方法 3.2 静态方法 ...

  7. python类属性和类方法(类的结构、实例属性、静态方法)

    类属性和类方法 目标 类的结构 类属性和实例属性 类方法和静态方法 01. 类的结构 1.1 术语 —— 实例 使用面相对象开发,第 1 步 是设计 类 使用 类名() 创建对象,创建对象 的动作有两 ...

  8. python-面向对象-09_类属性和类方法

    类属性和类方法 目标 类的结构 类属性和实例属性 类方法和静态方法 01. 类的结构 1.1 术语 —— 实例 使用面相对象开发,第 1 步 是设计 类 使用 类名() 创建对象,创建对象 的动作有两 ...

  9. Python类属性和类方法

    01. 类的结构 1.1 术语 —— 实例 使用面相对象开发,第 1 步 是设计 类 使用 类名() 创建对象,创建对象 的动作有两步: 1) 在内存中为对象 分配空间 2) 调用初始化方法 __in ...

随机推荐

  1. RMAN备份与恢复之数据文件

    备份数据文件,模拟磁盘损坏时,还原恢复数据文件. 首先,查询数据文件序号,备份数据文件,可根据数据文件序号指定备份的数据文件. SQL SQL> select file_name,file_id ...

  2. rm命令

    rm是一个危险的命令,使用的时候要特别当心,尤其对于新手,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * -rf).所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西 ...

  3. C++资料大全

    本文内容源自GitHub<Awesome C/C++>. 关于 C++ 框架.库和资源的一些汇总列表,由 fffaraz 发起和维护. 内容包括:标准库.Web应用框架.人工智能.数据库. ...

  4. 一些C#预处理器指令

    像C语言一样,C#有一些预处理器指令的命令.例如,#if#end if,#define等,所谓这些命令是指不会转化为可执行代码中的一些命令,只是在编译的过程中起作用.下面简要介绍一下:1 .#defi ...

  5. SSH_框架整合1

    1 WEB环境下配置Spring   因为是在WEB环境中应用Spring,所以要先配置web.xml: (1)WebContent-WEB-INF-lib包中,加入Spring包下的required ...

  6. Windows2012 cannot access netapp CIFS share

    NAS1> options cifs.smb2.signing.requiredcifs.smb2.signing.required off NAS1> options cifs.smb2 ...

  7. SVN中的Branches分支以及Merge 应用举例

    come from: http://www.360doc.com/content/12/0816/19/1317564_230547958.shtml 创建Branch分支或者Tag标签 当按照推荐的 ...

  8. 如何获取客户端IP、操作系统、浏览器

    request.getRemoteAddr();//获取IP request.getHeader("User-Agent");//获取操作系统信息.浏览器信息. protected ...

  9. HBASE解析

    Hbase是运行在Hadoop上的NoSQL数据库,它是一个分布式的和可扩展的大数据仓库,也就是说HBase能够利用HDFS的分布式处理模式,并从Hadoop的MapReduce程序模型中获益.这意味 ...

  10. PLSQL_数据泵导入进度查看Impdp/Expdp Status(案例)

    20150701 Created By BaoXinjian