就目前来说,CDI 基本还是和 JSF 结合使用的比较多,CDI 的扩展能力非常的出色,Seam3 就是完全基于 weld 的。当然我们也可以扩展 CDI 实现和 Spring MVC, .Net MVC 差不多的功能结合 JSP 一起使用。这里我是看到了老外的这篇文章,然后对其已经实现的MVC功能做了一些扩展,添加了页面像 Controller 传值的功能,后面我还准备尝试添加表单实体的提交,还有 Controller 返回 JSon,Freemarker 等一些功能,这些功能都是借鉴于 RestEasy 。
对于MVC,我就不去介绍了,网上有很多。
JDK5 以后新增了自定义注解的功能,我们先为MVC添加好需要的一些注解,在 CDI 中的限定词都是通过扩展注解来做的。基本取消了配置文件用注解来代替,Spring 3 MVC 也是。下面是目前我们使用到的几个注解。
添加 @Controller 注解,这个注解表示这个类为 MVC 中的 Controller。
@Target({ ElementType.TYPE,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
添加 @RequestMapping 注解,它有两个参数,第一个表示映射路径,第二个表示其对应的Http不同的提交 GET POST DELETE。
@Target({ ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String[] value() default {}; RequestMethod[] method() default {}; }
定义一下 RequestMethod 的枚举
public enum RequestMethod { POST,GET,DELETE }
添加 @Param 注解,这个注解只能在 Controller 的参数中使用,他的值表示你请求该 Controller 值的名称。如 controller?xxx=123 ,当你为一个参数添加了 @Param("xxx") 注解,那调用这个这个Controller的时候就这个参数就会被传入 123 值。
@Qualifier @Target({ElementType.FIELD,ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Param { String value(); }
当我们定义一个 Controller的时候,就可以这样去写:
@Controller @RequestMapping("/person/") public class PersonController { public Person createPerson() { return new Person(null, "new", "person"); } @RequestMapping("list") public String doListPeople() { return "listPeople"; } @RequestMapping("view") public String doViewPerson() { return "viewPerson"; } @RequestMapping(value = "edit", method = RequestMethod.GET) public String doEditPerson() { return "editPerson"; } @RequestMapping(value = "edit", method = RequestMethod.POST) public String doPostUpdatePerson(@Param("first") String first,@Param("last") int last) { return "updatedPerson"; }
在容器启动的时候,我们这里需要讲工程路径下面所有的 Controller 的基本信息初始化好,然后存在内存中。这里每一个Controller 包括其类的映射路径,方法的路径,Http请求的方式,所执行的方法,已经方法的参数信息。我们这里作为 MVC 的元数据初始化好放在一个 List 里面。然后我们定义一个 Servlet 来拦截所有请求,然后遍历这个List找出匹配的 Controller 然后执行其方法以后,根据返回的 View 的路径然后开打这个页面。我没有去研究过 Spring MVC 是怎么做的,有知道的朋友可以在评论中介绍一下。
定义 MVC 的基本信息对象
public class ControllerMethod { private final String prefix; private final String suffix; private final RequestMethod[] requestMethod; private final Method method; private final List<Map<String,Class<?>>> args; // gets , sets }
我们通过添加一个 CDI 的 ApplicationScoped 的 Bean 来完成我们的初始化工作,我们添加一个@PostConstruct的方法,这个方法会在工程第一次被访问的时候执行该方法。
@ApplicationScoped public class ControllerInfo { private final List<ControllerMethod> controllerMethods = new ArrayList<ControllerMethod>(); private static final String[] DEFAULT_MAPPING_PATHS = new String[] { "" }; public static final AnnotationLiteral<Controller> CONTROLLER_LITERAL = new AnnotationLiteral<Controller>() { private static final long serialVersionUID = -3226395594698453241L; }; @Inject private BeanManager beanManager; @PostConstruct public void initialize() { logger.debug("Initializing controller info"); Set<Bean<?>> controllers = beanManager.getBeans(Object.class, CONTROLLER_LITERAL); for (Bean<?> bean : controllers) { add(bean.getBeanClass()); } sortControllerMethods(); listMethods(); } ... ... }
在这个方法中,先通过 CDI 的 BeanManager 拿到所有具有 @Controller 注解的类,然后调用 add 方法,解析这个类里面的方法 具有@RequestMapping 的方法。初始化 ControllerMethod 对象放到 List 中。
private void add(Class<?> clazz) { logger.debug("Adding class {}", clazz); if(clazz == null){ throw new NullPointerException(); } if (clazz.isAnnotationPresent(Controller.class)) { logger.debug("Found controller on class {}", clazz); RequestMapping rm = clazz.getAnnotation(RequestMapping.class); Method[] methods = clazz.getMethods(); String[] controllerPaths = null; if (rm != null) { controllerPaths = rm.value(); } // if no paths are specified, then default to one blank path if (controllerPaths == null || controllerPaths.length == 0) { controllerPaths = DEFAULT_MAPPING_PATHS; } // add methods for each controller level paths for (String prefix : controllerPaths) { for (Method m : methods) { addMethod(prefix, m); } } } } private void addMethod(String prefix, Method javaMethod) { if(javaMethod == null){ throw new NullPointerException(); } RequestMapping mapping = javaMethod.getAnnotation(RequestMapping.class); Annotation[][] annotations = javaMethod.getParameterAnnotations(); Class<?>[] types = javaMethod.getParameterTypes(); // 这里对方法的参数处理,形成 名称:类型 的键值对 List<Map<String,Class<?>>> args = new ArrayList<Map<String,Class<?>>>(); for(int i = 0 , size = types.length; i<size; i ++){ Map<String,Class<?>> m = new HashMap<String,Class<?>>(); for(Annotation a : annotations[i]){ if(a instanceof Param){ m.put(((Param)a).value(),types[i]); } } args.add(m); } if (mapping != null) { logger.debug("Found request mapping on method {}", javaMethod .getName()); String[] paths = mapping.value(); // if these are blank, fill with defaults if (paths.length == 0) { paths = DEFAULT_MAPPING_PATHS; } for (String path : paths) { controllerMethods.add(new ControllerMethod(javaMethod, prefix, path, mapping.method(),args)); } } // check for other annotations in the future }这样,我们就完成对MVC 的Controller 的基本信息的初始化,目前功能还比较简单。下面会给出处理请求的功能实现。不过,有知道Spring 3 MVC 是怎么处理的朋友,希望不吝赐教。