1 引言
Servlet是SUN公司为JAVA企业级开发提供的一套编程规范,使用它可以大大简化基于B/S结构的应用系统开发。它是一种独立于平台和协议的服务器端的JAVA应用程序,可以生成动态的Web页面。Servlet在服务端处理Web浏览器或其他HTTP客户程序发出的请求并作出响应,它是JAVA企业级开发平台(JAVA EE)最为核心的技术之一。
JAVA EE规范中规定,在使用Servlet开发应用系统时,需要在web.xml文件中为每一个Servlet配置相关信息,如下:
<servlet>
<servlet-name>someServlet</servlet-name>
<servlet-class>edu.xju.web.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>someServlet</servlet-name>
<url-pattern>/servlet/Login</url-pattern>
</servlet-mapping>
一个大型应用系统中,Servlet的数量是很庞大的,如果要为每一个Servlet都配置上述信息,这无疑是开发人员的噩梦。现在主流的MVC框架(如Struts、WebWork),它们在一定程度上解决了这样繁琐的配置,但这些框架本身就引入了大量的XML文件配置。JDK1.5版本之后,JAVA语言提供了一种叫做Annotation(JSR250)的新数据类型,中文译为注解或标注,它的出现为铺天盖地的XML配置文件提供了一个完美的解决方案,让JAVA EE开发更加方便快速,也更加干净了。
2 Annotation的概念和语法简介
JAVA语言中的Annotation类型,它提供了一条与程序元素关联任何信息或者任何元数据(metadata)的途径。Annotation就像修饰符一样被使用,可应用在包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。Annotation是一种类似于接口的类型,能够通过JAVA反射API的方式提供对其信息的访问。需要注意的是,Annotaion本身不能影响程序代码的执行,JAVA语言解释器在工作时将忽略这些Annotation。
Annotation是与一个程序元素相关联信息或者元数据的标注。它从不影响JAVA程序的执行,但是对例如编译器警告或者像文档生成器等辅助工具产生影响。虽然Annotation 本身不影响程序代码的执行,但可以通过反射API获取到这些标注信息,可以在程序运行期间根据标识信息,对程序的执行作出相应的改变。
Annotaion类型的定义使用@interface,下面是一个定义Annotation类型的例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface NotNull {
String value() default “”;
}
观察上面这个注解类型定义例子,其中@Retention和@Target都为注解类型,注解类型以@符号开头。 定义一个注解类型时,在这个注解声明上使用了@Retention和@Target两个注解,注解后的括号里是注解的属性。@Retention注解的属性是一个叫RetentionPolicy的枚举类型,其可取值CLASS、SOURCE、RUNTIME。它表示了这个注解的生存保留时间,RUNTME表示此注解将在程序运行期一直被保留,意味着这个注解在运行期可以通过反射API获取到。@Target注解的属性是一个叫ElementType的枚举类型,可以接收多个这样的枚举值。此枚举表示定义的注解类型可以放置在哪些位置上,根据枚举值,可以将此定义的注解类型放置在类、接口、注解、构造器、注解、字段、方法、参数等前面。
上面例子中,定义了一个名为@NotNull的注解,有一个属性value,此属性拥有默认值空字符串。它在程序运行期将保留,它只能被用于方法参数前。此注解使用方式:public void test(@NotNull String str) { ... }。当参数为空(null)时,我们希望程序在此调用处抛出空异常(NullPointerException)。
因为注解本身并不会影响代码的执行,所以我们调用test(null)方法会发现程序仍然正常运行。想让上面的注解按期望那样起作用,则必须为此注解类型编写相应的处理器,主要需要使用到JAVA反射包(java.lang.reflect)及Class类。
3 @Servlet注解的实现
上面简要地介绍了注解的定义声明与使用方式,了解到了注解在后台需要一个处理器才能起作用。下面我们定义一个@Servlet注解,将Servlet在web.xml中的配置信息使用注解来表示,使用注解后,只需要在相应Servlet类的前面使用类似@Servlet(“/servlet/Login”)注解就可以达到和上述web.xml文件中配置信息一样的目的。注解@Servlet中的属性值” /servlet/Login”表示了web.xml配置文件中<servlet-mapping>元素的子元素<url-pattern>里的值。通过这样的注解能简化在XML文件中配置Servlet信息,整个配置文件将会非常简洁干净,开发人员的工作也将大大减少。
3.1 注解的定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Servlet {
String value();
}
@Target注解的属性值表明了@Servlet注解只能用于类或接口定义声明的前面,@Servlet注解有一个必填的属性value。调用方式为:@Servlet(value=“/xxxx”),因语法规定如果属性名为value且只填value属性值时,可以省略value属性名,即也可以写作:@Servlet(“/xxxx”)。
3.2 使用注解后程序流程
clip_image002
图1 服务器启动
在web.xml文件中配置:
<listener>
<listener-class>edu.xju.AnnotationListener</listener-class>
</listener>
<context-param>
<!-- 扫描包及其子包, 如果有多个参数,以逗号分隔 -->
<param-name>basePackage</param-name>
<param-value>edu.xju.web</param-value>
</context-param>
<servlet>
<servlet-name>ActionServlet</servlet-name>
<servlet-class>edu.xju.action.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ActionServlet</servlet-name>
<url-pattern>*.svt</url-pattern>
</servlet-mapping>
观察web.xml文件,会发现系统实现了一个名为AnnotationListener的监听器和一个名为ActionServlet的Servlet,二者为@Servlet注解提供了后台处理器。应用服务器(如Tomcat)启动时加载web.xml文件,并会立即执行AnnotationListener监听器的contextInitialized方法,在此方法中,根据<context-param>元素指定的basepackage参数,读取这些包(package)及其子包下的所有类,使用JAVA中的反射API对这些类进行分析。如果类前使用了@Servlet注解,如@Servlet(“/servlet/LoginServlet”),则以Key为”/servlet/LoginServlet”,Value为此Servlet类的一个实例对象,放入一个Map结构中。解析这些包下的所有的类之后,将得到的此Map结构对象置入ServletContext对象中。到此,完成了注解的解析,服务器成功运行起来了。
33 服务器将以下图的方式与客户端进行交互
clip_image004
图2 请求与响应
根据web.xml文件的配置,所有后缀名为 .svt 请求,都交由ActionServlet处理。ActionServlet本身也是一个Servlet,它也可以使用@Servlet注解方法进行配置,但将其配置在web.xml文件中具有更好的可读性与可维护性。ActionServlet继承于HttpServlet,重写了HttpServlet类中protected修饰的service方法。
在此service方法的实现代码中,从HttpServletRequest请求对象中得到请求的方式类型(GET/POST)和请求的URI。如有请求,http://localhost/testProject/servlet/loginServlet.svt,此时请求方法类型为POST,URI值为/testProject/servlet/loginServlet.svt。从ServletConext对象中获取到在监听器中保存的Map结构,根据URI获得一个Key=” /servlet/loginServlet”,从Map结构中根据此Key得到Value,此时Value就是要请求调用的那个Servlet对象实例。再根据前面得到的请求方法类型,能决定调用此Servlet对象实例的doGet或doPost方法。最终客户端发生的后缀为 .svt请求,经由ActionServlet对请求对象(HttpServletRequest)的分析,从而调用相应某Servlet的doGet或doPost方法,完成了一次客户端请求到服务器响应的过程。
34 注解的扩展
通过@Servlet注解,Servlet不用再在web.xml文件中进行繁冗的注册。考虑性能因素,使用单例设计模式,让每个Servlet类在内存中只有一个实例,这些类的实例对象在服务器启动时在监听器中就已经产生了。
通过@Servlet注解,我们还可以减少Servlet类文件的数量,并且此时不要求类继承或实现Servlet任何的类或接口,一个普通的类即可。不过此时注解@Servlet不应放在类前面,而应该让它能放在方法前面,需要将@Servlet定义时的@Target(ElementType.TYPE)改成@Target(ElementType.METHOD)。加@Servlet注解的方法,名称可以是任意的,但参数必须有HttpServletRequest 类型及HttpServletResponse类型。这是因为我们的方法要对请求和响应做相应的处理。
4 结语
JAVA提供的注解,为简化开发人员的工作带来了无限的可能。在JAVA EE 5版本中,提供了一些注解的使用来解化开发,而像Struts2.x、Hibernate、Spring等框架中,也开始加了大量的注解,使用这些注解可以极大地简化了配置文件的编写,基本上实现了“零配置”。注解还在面向切面编程(AOP)等领域都有很好的应用。在JAVA企业级应用开发中,注解技术将越来越广泛地被使用。