• 通过项目了解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虚拟机》

    为了分享给大家

  • 相关阅读:
    phpexcel导出带生成图片完美案例
    让Asp.Net WebAPI支持OData查询,排序,过滤。(转)
    Workflow笔记2——状态机工作流(转)
    WebAPI请求(转)
    WebApi参数传递总结(转)
    30分钟搞定后台登录界面(103个后台PSD源文件、素材网站)(转)
    .net 分布式架构之分布式锁实现(转)
    C#分布式事务解决方案-TransactionScope(转)
    Windows 环境下分布式跨域Session共享(转)
    Session分布式共享 = Session + Redis + Nginx(转)
  • 原文地址:https://www.cnblogs.com/qwop/p/6637324.html
Copyright © 2020-2023  润新知