您的位置:首页 > 移动开发

由SpringMVC中的Controller注解@RequestMapping引发的思考

2016-03-14 18:11 477 查看

1、前言

在Spring MVC的设计中,Controller用于接收客户端发来的Request请求,进行相应的处理后,或者返回RequstBody(Restful设计风格用来返回Json数据),或者返回JSP的名称等。实现这种设计,使用了注解@RequestMapping等。由此,引发了一些思考,为什么只需要在方法前添加一个注解@RequestMapping便可以实现该种设计。

Spring MVC中的用法如下:

<span style="font-family:Times New Roman;font-size:14px;">@Controller
public class MainController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
return "index";
}
}</span>


2、注解

注解也称为元数据,为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻方便的使用这些数据。其作用有三个。

编译时检查,通过某些注解如@override实现在编译时期对方法进行检查,如没有复写父类的方法,则在编译时期报错;
程序运行时,通过反射机制实现,对代码进行分析,实现特定的功能,Spring中的@RequestMapping就是这种作用;
生成文档,通过注解,编写外部文档。

Java中内置了三种标准注解以及四种元注解。标准注解为:

@Override,表示当前的方法定义将覆盖父类中的方法,如没覆盖,编译时会报错;
@Deprecated,若使用了该注解,表示该方法已经被弃用了,编译器会报警告;
@SuppressWarnings,关闭不当的编译器警告信息。

四种元注解(用于自定义注解)为:

@Target 表示该注解用于什么地方。可能的ElementType参数包括:CONSTRUCTOR(构造器的声明)、FIELD(域声明)、LOCAL_VARIABLE(局部变量声明)、METHOD(方法声明)、PACKAGE(包声明)、TYPE(类、接口或枚举)
@Retention表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:SOURCE(注解将被编译器丢弃)、CLASS(注解在class字节码文件中可用,但会被JVM丢弃)、RUNTIME(JVM在运行期也会保留该注解,因此可利用反射机制读取该注解的信息,用于代码分析)
@Documented讲该注解包含在Javadoc中
@Inherited允许子类继承父类中的注解

3、反射

使用javac编译完java类文件后会生成一个.class字节码文件,每个字节码文件都对应一个Class类。Class类和java.lang.reflect类库一起对反射做出了支持,该类库包括Filed、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()方法读取和修改Field对象关联的字段,用invoke()方法调用与Method对象关联的方法,还可以使用getFiled()、getMethod()和getContructors()等方法。

4、例子

首先,使用元注解自定义一个注解,用它来跟踪一个项目中的用例,如果一个方法或者一组方法实现了某个用例的需求,则程序员为该方法加上该注解。项目经理通过计算已经实现的用例就可以很好的掌握项目的进展。若运维人员需要修改业务,也可以很方便的在代码中找到该用例。
用例注解代码如下:
<span style="font-family:Times New Roman;font-size:14px;">package thinkinginjava;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}</span>


下面的类中有三个方法被注解为用例:
<span style="font-family:Times New Roman;">package thinkinginjava;

public class PasswordUtils {
@UseCase(id = 47, description = "Password must contain at least one numeric")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}

@UseCase(id = 48)
public String encrypPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
}</span><span style="font-family:Microsoft YaHei;">
</span>
如果没有注解读取的工具,那么注解也就没有意义了,外部工具apt可以帮助程序员解析带有注解的java源代码。下面为一个简单的注解处理器,用它来读取PasswordUtils类,并利用反射机制查找@UseCase标记。其中提供了一组id值,然后它会列出在PasswordUtils里中找到的用例以及缺失的用例。
<span style="font-family:Times New Roman;">package thinkinginjava;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class UseCaseTracker {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for (Method m : cl.getDeclaredMethods()) {
System.out.println(m.toString());
UseCase uc = m.getAnnotation(UseCase.class);
if (uc != null) {
System.out.println("Found Use Case : " + uc.id() + " " + uc.description());
if(uc.id() == 48){
try {
Object pwdu = cl.newInstance();
System.out.println(cl.isInstance(pwdu));
System.out.println(m.invoke(pwdu, "jichenxiao"));
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
useCases.remove(new Integer(uc.id()));
}
}
for(int i : useCases){
System.out.println("Warning: Missing Use Case - " + i);
}
}

public static void main(String[] args){
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47,48,49);
trackUseCases(useCases, PasswordUtils.class);
}
}</span><span style="font-family:Microsoft YaHei;">
</span>


上述程序中使用了反射,getDeclaredMethods()和getAnnotation()均属于AnnotationElement接口(Class、Method和Filed等类都实现了该接口)。通过getDeclaredMethod()返回类中的方法,类型为Method,然后再使用getAnnotation()返回UseCase注解对象。如果被注解的方法中没有该类型的注解,则返回null。通过id()和description()方法从UseCase中提取元素的值。通过注解命中一个方法,并使用invoke()去调用该方法。最终的输出结果为:
public boolean thinkinginjava.PasswordUtils.validatePassword(java.lang.String)
Found Use Case : 47 Password must contain at least one numeric
public java.lang.String thinkinginjava.PasswordUtils.encrypPassword(java.lang.String)
Found Use Case : 48 no description
true
oaixnehcij
Warning: Missing Use Case - 49


5、思考

Sping MVC中的注解,通过@RequstMapping去执行其命中的方法,并经过相应的处理返回RequstBody或者JSP页面的名称。该设计中,猜测是同样的实现方式,通过注解处理器和Java的反射机制去执行相应的处理。接下来会研读一下Spring的源码,如有不对,届时再做更正。
注:本文中例子来源于《Java编程思想》。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: