`
gaiay
  • 浏览: 656 次
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

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虚拟机》

1
2
分享到:
评论

相关推荐

    java 自定义注解验证

    在本案例中,作者创建了三个自定义注解,具体细节虽未详述,但我们可以深入探讨一下Java自定义注解的基本概念、创建过程以及如何进行验证。 首先,我们需要理解Java注解的基本原理。注解是一种元数据,它提供了一种...

    java自定义注解和通过反射获取注解

    Java自定义注解和通过反射获取注解是Java编程中重要的高级特性,它们极大地增强了代码的可读性和可维护性。注解(Annotation)是一种元数据,提供了在编译时和运行时对代码进行标记的方法,而反射(Reflection)则是...

    Java自定义注解与spring BeanPostProcessor详解

    Java自定义注解和Spring的BeanPostProcessor是Java企业级开发中的两个重要概念,它们在构建灵活、可扩展的应用程序中发挥着关键作用。本文将深入探讨这两个话题,并结合源码分析,帮助开发者更好地理解和应用。 ...

    excel导入动态校验,自定义注解动态校验

    本文将详细探讨"Excel导入动态校验,自定义注解动态校验"这一主题,旨在帮助你理解和掌握如何在处理Excel数据时进行有效的验证和校验。 首先,Excel导入动态校验是指在将Excel数据导入到系统或数据库时,对数据进行...

    Java自定义注解md,学习代码

    总之,这个压缩包的内容涵盖了Java自定义注解的基础知识,以及与Java Web相关的Tomcat服务器、用户会话管理等内容,对于理解和实践Java后端开发非常有帮助。通过深入学习这些材料,可以提高对Java编程和Web应用开发...

    使用Java自定义注解模拟实现SpringBoot相关注解.zip

    本教程将探讨如何使用Java自定义注解来模拟实现这些SpringBoot相关的注解功能。 首先,我们来看`@Autowired`注解。`@Autowired`是Spring框架中的一个关键注解,用于自动装配bean。当我们想在类中注入某个依赖时,而...

    java 自定义注解 经典学习文档

    ### Java自定义注解经典学习文档 #### 一、引言 Java自定义注解是Java 5.0及后续版本引入的一项重要功能。通过使用注解,开发人员能够在不改变程序逻辑的情况下向代码中添加元数据。这些元数据可以被编译器或者运行...

    java(spring boot)自定义注解

    总结来说,这个示例展示了如何在Spring Boot项目中自定义注解,将其应用于Java Bean的方法,以及如何利用AOP来实现全局扫描和自动执行。这样的实践在系统监控、性能分析和故障排查中非常有用,能够帮助开发者更好地...

    SpringBoot AOP各种注解、自定义注解、鉴权使用案例(免费下载)

    例如,创建一个名为`@Cacheable`的自定义注解,用于缓存方法的返回结果: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Cacheable { String cacheName() default ...

    Android自定义注解实现View绑定Demo

    你可以下载这个压缩包,通过阅读和实践代码,加深对Android自定义注解实现View绑定的理解。同时,这也将帮助你掌握如何编写和使用注解处理器,以及如何在Android项目中有效地利用自定义注解来优化代码结构。

    Java 使用注解拼接SQL语句

    综上所述,使用Java自定义注解来拼接SQL语句是一种高效且灵活的编程方式,它可以提高代码的可读性和可维护性,同时减少错误的可能性。然而,也需要权衡其带来的性能影响,并遵循最佳实践来确保代码的质量和安全。

    自定义注解案例

    在Java编程语言中,自定义注解是一种强大的工具,它允许程序员定义自己的元数据,以提供额外的信息给编译器、运行时环境或其他工具。这个案例"自定义注解"着重于如何创建、使用和解析自定义注解,以及它们在实际开发...

    java token验证和注解方式放行

    总的来说,这个主题涵盖了身份验证、权限控制和Spring Security的使用,这些都是Java Web开发中的核心安全实践。理解并正确实施这些机制对于构建安全的Web服务至关重要。如果你在使用过程中遇到任何问题,社区的支持...

    自定义注解MVC

    在这个主题“自定义注解MVC”中,我们将深入探讨如何在自定义框架中利用注解来实现控制翻转,增强代码的可读性和可维护性。 1. **自定义注解** - 注解是一种在代码中添加元信息的方式,它允许开发者在不改变程序...

    SpringBoot 、Shiro、 自定义注解权限控制源码下载

    例如,通过Shiro的自定义注解,可以在方法级别进行权限判断,确保只有具有特定权限的用户才能执行特定的操作。这在描述中提到的"自定义注解权限控制"就是指这一功能。 MyBatis Plus则是在MyBatis的基础上扩展的一套...

    0006-自定义注解与设计模式.zip

    在Java编程语言中,自定义注解和设计模式是两个重要的概念,它们分别代表了元编程能力和软件架构的成熟度。下面将详细讲解这两个主题。 首先,我们来探讨自定义注解。Java注解(Annotation)是元数据的一种形式,它...

    Java注解(Annotation)全面解析:作用、应用与自定义实践

    本文将详细介绍Java注解的作用、应用场景以及如何自定义注解。 Java注解是一种强大的工具,它们为代码提供了额外的元数据,可以在编译时和运行时发挥多种作用。通过自定义注解,开发者可以创建灵活的框架和库,提高...

    Spring IOC 原理 ,Java 反射实例,自定义注解

    自定义注解是Java提供的一种元数据机制,可以用于向编译器、开发工具或者运行时系统提供信息。在Spring中,自定义注解可以用来扩展框架的功能,例如声明特定的行为或者定制化的配置。创建自定义注解需要定义一个@...

    common(自定义注解校验数据有效性)

    自定义注解是Java等编程语言中的一种元编程特性,允许我们创建自己的标记来指定代码的行为或者进行特定的处理。 首先,我们来理解自定义注解的工作原理。自定义注解允许程序员添加额外的信息到代码中,如类、方法或...

Global site tag (gtag.js) - Google Analytics