在Spring MVC中,将一个普通的java类标注上Controller注解之后,再将类中的方法使用RequestMapping注解标注,那么这个普通的java类就够处理Web请求,示例代码如下:
/**
* 使用Controller注解标注LoginUI类
*/
@Controller
public class LoginUI {
//使用RequestMapping注解指明forward1方法的访问路径
@RequestMapping("LoginUI/Login2")
public View forward1(){
//执行完forward1方法之后返回的视图
return new View("/login2.jsp");
}
//使用RequestMapping注解指明forward2方法的访问路径
@RequestMapping("LoginUI/Login3")
public View forward2(){
//执行完forward2方法之后返回的视图
return new View("/login3.jsp");
}
}
spring通过java annotation就可以注释一个类为action ,在方法上添加上一个java annotation 就可以配置请求的路径了,那么这种机制是如何实现的呢,今天我们使用"自定义注解+Servlet"来简单模拟一下Spring MVC中的这种注解配置方式。
一、编写注解
1.1、Controller注解
开发Controller注解,这个注解只有一个value属性,默认值为空字符串,代码如下:
package me.gacl.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName: Controller
* @Description: 自定义Controller注解
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:16:40
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
public String value() default "";
}
1.2、RequestMapping注解
开发RequestMapping注解,用于定义请求路径,这个注解只有一个value属性,默认值为空字符串,代码如下:
package me.gacl.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义请求路径的java annotation
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
public String value() default "";
}
二、编写核心的注解处理器
2.1、开发AnnotationHandleServlet
这里使用一个Servlet来作为注解处理器,编写一个AnnotationHandleServlet,代码如下:
package me.gacl.web.controller;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import me.gacl.annotation.Controller;
import me.gacl.annotation.RequestMapping;
import me.gacl.util.BeanUtils;
import me.gacl.util.RequestMapingMap;
import me.gacl.util.ScanClassUtil;
import me.gacl.web.context.WebContext;
import me.gacl.web.view.DispatchActionConstant;
import me.gacl.web.view.View;
/**
* ClassName: AnnotationHandleServlet
*
Description: AnnotationHandleServlet作为自定义注解的核心处理器以及负责调用目标业务方法处理用户请求
* @author xudp
* @version 1.0 V
*/
public class AnnotationHandleServlet extends HttpServlet {
private String pareRequestURI(HttpServletRequest request){
String path = request.getContextPath()+"/";
String requestUri = request.getRequestURI();
String midUrl = requestUri.replaceFirst(path, "");
String lasturl = midUrl.substring(0, midUrl.lastIndexOf("."));
return lasturl;
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.excute(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.excute(request, response);
}
private void excute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//将当前线程中HttpServletRequest对象存储到ThreadLocal中,以便在Controller类中使用
WebContext.requestHodler.set(request);
//将当前线程中HttpServletResponse对象存储到ThreadLocal中,以便在Controller类中使用
WebContext.responseHodler.set(response);
//解析url
String lasturl = pareRequestURI(request);
//获取要使用的类
Class> clazz = RequestMapingMap.getRequesetMap().get(lasturl);
//创建类的实例
Object classInstance = BeanUtils.instanceClass(clazz);
//获取类中定义的方法
Method [] methods = BeanUtils.findDeclaredMethods(clazz);
Method method = null;
for(Method m:methods){//循环方法,找匹配的方法进行执行
if(m.isAnnotationPresent(RequestMapping.class)){
String anoPath = m.getAnnotation(RequestMapping.class).value();
if(anoPath!=null && !"".equals(anoPath.trim()) && lasturl.equals(anoPath.trim())){
//找到要执行的目标方法
method = m;
break;
}
}
}
try {
if(method!=null){
//执行目标方法处理用户请求
Object retObject = method.invoke(classInstance);
//如果方法有返回值,那么就表示用户需要返回视图
if (retObject!=null) {
View view = (View)retObject;
//判断要使用的跳转方式
if(view.getDispathAction().equals(DispatchActionConstant.FORWARD)){
//使用服务器端跳转方式
request.getRequestDispatcher(view.getUrl()).forward(request, response);
}else if(view.getDispathAction().equals(DispatchActionConstant.REDIRECT)){
//使用客户端跳转方式
response.sendRedirect(request.getContextPath()+view.getUrl());
}else{
request.getRequestDispatcher(view.getUrl()).forward(request, response);
}
}
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void init(ServletConfig config) throws ServletException {
/**
* 重写了Servlet的init方法后一定要记得调用父类的init方法,
* 否则在service/doGet/doPost方法中使用getServletContext()方法获取ServletContext对象时
* 就会出现java.lang.NullPointerException异常
*/
super.init(config);
System.out.println("---初始化开始---");
//获取web.xml中配置的要扫描的包
String basePackage = config.getInitParameter("basePackage");
//如果配置了多个包,例如:me.gacl.web.controller,me.gacl.web.UI
if (basePackage.indexOf(",")>0) {
//按逗号进行分隔
String[] packageNameArr = basePackage.split(",");
for (String packageName : packageNameArr) {
initRequestMapingMap(packageName);
}
}else {
initRequestMapingMap(basePackage);
}
System.out.println("----初始化结束---");
}
/**
* @Method: initRequestMapingMap
* @Description:添加使用了Controller注解的Class到RequestMapingMap中
* @Anthor:孤傲苍狼
* @param packageName
*/
private void initRequestMapingMap(String packageName){
Set> setClasses = ScanClassUtil.getClasses(packageName);
for (Class> clazz :setClasses) {
if (clazz.isAnnotationPresent(Controller.class)) {
Method [] methods = BeanUtils.findDeclaredMethods(clazz);
for(Method m:methods){//循环方法,找匹配的方法进行执行
if(m.isAnnotationPresent(RequestMapping.class)){
String anoPath = m.getAnnotation(RequestMapping.class).value();
if(anoPath!=null && !"".equals(anoPath.trim())){
if (RequestMapingMap.getRequesetMap().containsKey(anoPath)) {
throw new RuntimeException("RequestMapping映射的地址不允许重复!");
}
RequestMapingMap.put(anoPath, clazz);
}
}
}
}
}
}
}
这里说一下AnnotationHandleServlet的实现思路
1、AnnotationHandleServlet初始化(init)时扫描指定的包下面使用了Controller注解的类,如下图所示:
2、遍历类中的方法,找到类中使用了RequestMapping注解标注的那些方法,获取RequestMapping注解的value属性值,value属性值指明了该方法的访问路径,以RequestMapping注解的value属性值作为key,Class类作为value将存储到一个静态Map集合中。如下图所示:
2.2 在Web.xml文件中注册AnnotationHandleServlet
在web.xml文件中配置AnnotationHandleServlet和需要扫描的包
AnnotationHandleServlet
me.gacl.web.controller.AnnotationHandleServlet
配置要扫描包及其子包, 如果有多个包,以逗号分隔
basePackage
me.gacl.web.controller,me.gacl.web.UI
1
AnnotationHandleServlet
*.do
三、相关代码讲解
3.1、BeanUtils
BeanUtils工具类主要是用来处理一些反射的操作
package me.gacl.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 对java反射中操作的一些封装
*/
public class BeanUtils {
/**
* 实例化一个class
* @param
* @param clazz Person.class
* @return
*/
public static T instanceClass(Class clazz){
if(!clazz.isInterface()){
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 通过构造函数实例化
* @param
* @param ctor
* @param args
* @return
* @throws IllegalArgumentException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public static T instanceClass(Constructor ctor, Object... args)
throws IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException{
makeAccessible(ctor);
return ctor.newInstance(args);//调用构造方法实例化
}
/**
* 查找某个class的方法
* @param clazz
* @param methodName
* @param paramTypes
* @return
* @throws SecurityException
* @throws NoSuchMethodException
*/
public static Method findMethod(Class> clazz, String methodName, Class>... paramTypes){
try {
return clazz.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
return findDeclaredMethod(clazz, methodName, paramTypes);//返回共有的方法
}
}
public static Method findDeclaredMethod(Class> clazz, String methodName, Class>[] paramTypes){
try {
return clazz.getDeclaredMethod(methodName, paramTypes);
}
catch (NoSuchMethodException ex) {
if (clazz.getSuperclass() != null) {
return findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes);
}
return null;
}
}
public static Method [] findDeclaredMethods(Class> clazz){
return clazz.getDeclaredMethods();
}
public static void makeAccessible(Constructor> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers())
|| !Modifier.isPublic(ctor.getDeclaringClass().getModifiers()))
&& !ctor.isAccessible()) {
ctor.setAccessible(true);//如果是私有的 设置为true 使其可以访问
}
}
public static Field[] findDeclaredFields(Class> clazz){
return clazz.getDeclaredFields();
}
}
3.2 RequestMapingMap
该类是用于存储方法的访问路径,AnnotationHandleServlet初始化时会将类(使用Controller注解标注的那些类)中使用了RequestMapping注解标注的那些方法的访问路径存储到RequestMapingMap中。
package me.gacl.util;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: RequestMapingMap
* @Description: 存储方法的访问路径
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:31:43
*
*/
public class RequestMapingMap {
/**
* @Field: requesetMap
* 用于存储方法的访问路径
*/
private static Map> requesetMap = new HashMap>();
public static Class> getClassName(String path) {
return requesetMap.get(path);
}
public static void put(String path, Class> className) {
requesetMap.put(path, className);
}
public static Map> getRequesetMap() {
return requesetMap;
}
}
3.3、ScanClassUtil
package me.gacl.util;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @ClassName: ScanClassUtil
* @Description: 扫描指定包或者jar包下面的class
* @author: 孤傲苍狼
* @date: 2014-11-16 下午6:34:10
*
*/
public class ScanClassUtil {
/**
* 从包package中获取所有的Class
*
* @param pack
* @return
*/
public static Set> getClasses(String pack) {
// 第一个class类的集合
Set> classes = new LinkedHashSet>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
System.err.println("file类型的扫描");
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.err.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + '.'
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
public static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + '.' + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
3.4 WebContext
WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse,当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取,通过WebContext.java这个类 ,我们可以在作为Controller的普通java类中获取当前请求的request、response或者session相关请求类的实例变量,并且线程间互不干扰的,因为用到了ThreadLocal这个类。
package me.gacl.web.context;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* WebContext主要是用来存储当前线程中的HttpServletRequest和HttpServletResponse
* 当别的地方需要使用HttpServletRequest和HttpServletResponse,就可以通过requestHodler和responseHodler获取
**/
public class WebContext {
public static ThreadLocal requestHodler = new ThreadLocal();
public static ThreadLocal responseHodler = new ThreadLocal();
public HttpServletRequest getRequest(){
return requestHodler.get();
}
public HttpSession getSession(){
return requestHodler.get().getSession();
}
public ServletContext getServletContext(){
return requestHodler.get().getSession().getServletContext();
}
public HttpServletResponse getResponse(){
return responseHodler.get();
}
}
3.5、View
视图类,对一些客户端响应操作进行封装,其中包括跳转的路径 、展现到页面的数据、跳转方式。
package me.gacl.web.view;
/**
* 视图模型
**/
public class View {
private String url;//跳转路径
private String dispathAction = DispatchActionConstant.FORWARD;//跳转方式
public View(String url) {
this.url = url;
}
public View(String url,String name,Object value) {
this.url = url;
ViewData view = new ViewData();
view.put(name, value);
}
public View(String url,String name,String dispathAction ,Object value) {
this.dispathAction = dispathAction;
this.url = url;
ViewData view = new ViewData();//请看后面的代码
view.put(name, value);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDispathAction() {
return dispathAction;
}
public void setDispathAction(String dispathAction) {
this.dispathAction = dispathAction;
}
}
3.6、ViewData
request范围的数据存储类,当需要发送数据到客户端显示时,就可以将要显示的数据存储到ViewData类中。使用ViewData.put(String name,Object value)方法往request对象中存数据。
package me.gacl.web.view;
import javax.servlet.http.HttpServletRequest;
import me.gacl.web.context.WebContext;
/**
* 需要发送到客户端显示的数据模型
*/
public class ViewData {
private HttpServletRequest request;
public ViewData() {
initRequest();
}
private void initRequest(){
//从requestHodler中获取request对象
this.request = WebContext.requestHodler.get();
}
public void put(String name,Object value){
this.request.setAttribute(name, value);
}
}
3.7、DispatchActionConstant
package me.gacl.web.view;
/**
* 跳转常量
*/
public class DispatchActionConstant {
public static String FORWARD = "forward";//服务器跳转
public static String REDIRECT = "redirect";//客户端跳转
}