通过项目了解JAVA注解
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;
}
² 参考
1. 《java编程思想》
2. 《深入理解java虚拟机》
为了分享给大家
通过项目了解JAVA注解的更多相关文章
- 框架基础——全面解析Java注解
为什么学习注解? 学习注解有什么好处? 学完能做什么? 答:1. 能够读懂别人写的代码,特别是框架相关的代码: 2. 让编程更加简洁,代码更加清晰: 3. 让别人高看一眼. spring.mybati ...
- Spring3 + Spring MVC+ Mybatis 3+Mysql 项目整合(注解及源码)
Spring3 + Spring MVC+ Mybatis 3+Mysql 项目整合(注解及源码) 备注: 之前在Spring3 + Spring MVC+ Mybatis 3+Mysql 项目整合中 ...
- 使用Java注解来简化你的代码
注解(Annotation)就是一种标签,可以插入到源代码中,我们的编译器可以对他们进行逻辑判断,或者我们可以自己写一个工具方法来读取我们源代码中的注解信息,从而实现某种操作.需要申明一点, ...
- Java注解(二):实战 - 直接使用对象列表生成报表
通过对Java注解(一):介绍,思想及优点学习了解,相信大家对Java注解有一定程度的了解,本篇文章将实战项目中的应用来加深对Java注解的了解. 本实例实现根据指定字段的JavaBean,生成对应列 ...
- java基础---->java注解的使用(一)
注解是众多引入到Java SE5中的重要的语言变化之一.它为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据.今天我们就开始学习一下java中注解的知识. j ...
- JAVA注解@Interface基础知识
java注解是在JDK5时引入的新特性,大多数框架(SpringBoot.MyBatis.Quartz)背后都在大量使用注解开发. 一.先进行一个小试验,了解注解开发流程 建立maven项目annot ...
- Java 注解的概念与种类
Java 注解的概念与种类 一,什么是注解 注解和XML文件都是常用的,对web项目进行配置性描述的方式. 举个最简单的例子,对于一个Servlet,比如LoginServlet,采用如下方式: @W ...
- Java注解与自己定义注解处理器
动机 近期在看ButterKnife源代码的时候.竟然发现有一个类叫做AbstractProcessor,并且ButterKnife的View绑定不是依靠反射来实现的,而是使用了编译时的注解,自己主动 ...
- Java 注解指导手册 – 终极向导
原文链接 原文作者:Dani Buiza 译者:Toien Liu 校对:深海 编者的话:注解是java的一个主要特性且每个java开发者都应该知道如何使用它. 我们已经在Java Code Gee ...
随机推荐
- Java基础语法实例(1)——实习第一天
来到广州实习的第一天,我选择的是JavaEE,因为以后的方向是Java,所以就选择了它.感觉有一段时间没有接触Java了.趁此机会好好努力,将基础巩固好. Java输入及循环,判断,字符转换,数组定义 ...
- jQuery UI-Draggable 参数集合
·概述 在任何DOM元素启用拖动功能.通过单击鼠标并拖动对象在窗口内的任何地方移动. 官方示例地址:http://jqueryui.com/demos/draggable/ 所有 ...
- 浅谈_IDEA导入Eclipse的Web项目
相信很多同学在工作中都会遇到将一个Eclipse的Web项目导入IDEA的情景,这里浅谈一下具体的操作流程 一:Import Project,选择要导入的项目 二:选择以Eclipse模型的方式导入 ...
- FPGA 日积月累
Nios II 13.1中,使用Qsys生成的cpu中断号默认为-1,因此中断无法注册.解决方法:手动修改中断模块的tcl文件如下: add_interface interrupt_sender in ...
- 汕头市队赛 SRM14 T2 最长上升子序列
最长上升子序列 (tree.pas/c/cpp) 128MB 1s 有一个长度为n的序列a[i],其中1到n的整数各自在a[i]中出现恰好一次. 现在已知另一个等长的序列f[i],表示a[i]中以第i ...
- JS操作checkBox
代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w ...
- 150.Evaluate Reverse Polish Notation---逆波兰式求值
题目链接 题目大意:计算逆波兰表达式的值. 法一:stack,用stack存数,遇到操作符,则运算.代码如下(耗时12ms): public int evalRPN(String[] tokens) ...
- Windows基础-实时录音程序(WaveXXX)
写在前面 一开始是打算用这个老接口做讯飞语音识别的程序,在转移到UWP时发现,这玩意在Windows Runtime中屏蔽(弃用)了,将来会更新使用WASAPI的程序 WaveRecorder类代码下 ...
- Centos的APK解包打包签名
http://www.v5b7.com/other/apk.html vi /etc/profile PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:/u ...
- 鸭子-策略模式(Strategy)
前言 万事开头难,最近对这句话体会深刻!这篇文章是这个系列正式开始介绍设计模式的第一篇,所以肩负着确定这个系列风格的历史重任,它在我脑袋里默默地酝酿了好多天,却只搜刮出了一点儿不太清晰的轮廓,可是时间 ...