zoukankan      html  css  js  c++  java
  • 通过项目了解JAVA注解

    java自定义注解实践

     

     

    ² 背景

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

     

     

    ² 概念

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

     

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

     

    我们知道,java除了内置的注解(@Override@Deprecated等)以外,还支持自定义注解(StrutsHibernate等很多框架甚至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虚拟机》

    为了分享给大家

  • 相关阅读:
    新浪微盘又是一个给力的产品啊,
    InfoQ: 百度数据库架构演变与设计
    列式数据库——Sybase IQ
    MapR初体验 淘宝共享数据平台 tbdata.org
    IBM正式发布新一代zEnterprise大型机(组图) 大型机,IBM,BladeCenter,美国,纽约 TechWeb News
    1TB is equal to the number of how many GB? 1PB equal to is equal to the number of TB? 1EB PB? | PCfault.com
    Cassandra vs HBase | WhyNosql
    The Hadoop Community Effect
    雅虎剥离开源软件平台 Hadoop ,与风投新建 Hortonworks 公司 品味雅虎
    RowOriented Database 、ColumnOriented Database 、KeyValue Store Database 、DocumentOriented Database
  • 原文地址:https://www.cnblogs.com/qwop/p/6637324.html
Copyright © 2011-2022 走看看