Spring的IOC是要加载所有的类,并实例化对象。我们要在什么时候进行加载呢?
想了想,MySpringMvc有一个MyDispatcherServlet,Servlet可以设置为在tomcat服务器启动时进行实例化,而Servlet实例化会调用Servlet的init方法。我们不妨将加载类的起点放在init方法中,这样tomcat服务器启动时,所有的类都已加载并且实例化。很是方便。
一、定义注解
因为我们只有SpringMvc,所以我们只需要定义MyController和MyRequestMapping两个注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ""; }
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value() default ""; }
因为MyController只能放在类上,所以定义为Type,而MyRequestMapping可以放在类上也可以放在方法上,所以定义如上。
二、实现MyDispatcherServlet
1、实现MyDispatcherServlet之前先进行配置,为了方便,我将basePackage定义在web.xml中。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>MySpringMvc</servlet-name> <servlet-class>com.MySpringMvc.Servlet.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MySpringMvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
其实application.properties只有basePackage一项,你也可以直接写在web.xml。无所谓
2、MyDispatcherServlet的属性如下
//读取配置文件 private Properties properties = new Properties(); //存放类的全路径名 private List<String> classNames = new ArrayList<>(); //类名与实例的映射 private Map<String,Object> ioc = new HashMap<>(); //url和方法的映射,表明是哪个方法 private Map<String,Method> handlerMapping = new HashMap<>(); //url和实例映射,表明是哪个Conyroller private Map<String,Object> controllerMap = new HashMap<>();
3、编写init方法
刚才也说了,init方法要完成类的扫描,类的实例化
@Override public void init(ServletConfig config) throws ServletException { //读取web.xml的参数,加载配置文件 doLoadConfig(config.getInitParameter("contextConfigLocation")); //初始化所有相关联的类,扫描用户设定的包下面所有类 doScanner(properties.getProperty("scanPackage")); //拿到扫描的类,通过反射实例化,放到ioc的map中 doInstance(); //初始化HandlerMapping(将url和method对应) initHandlerMapping(); }
4、doLoadConfig方法
这个方法主要用于加载配置文件,获取basePackage。实现如下
private void doLoadConfig(String location){ //把web.xml中的contextConfigLocation对应value值的文件加载到流里面 InputStream is = this.getClass().getClassLoader() .getResourceAsStream(location); try { //使用Properties文件加载文件里的内容 properties.load(is); }catch (Exception e){ e.printStackTrace(); }finally { if (is != null){ try { is.close(); }catch (Exception e){ e.printStackTrace(); } } } }
5、doScanner方法
这个方法便是扫描包下的类,获取每个类的全路径名
public void doScanner(String packageName){ //将url所有的.替换为/ URL url = this.getClass().getClassLoader() .getResource("/"+packageName.replaceAll("\\.","/")); File dir = new File(url.getFile()); for (File file : dir.listFiles()){ //如果是文件夹,递归扫描 if (file.isDirectory()){ doScanner(packageName+"."+file.getName()); }else { String className = packageName+"."+file.getName().replace(".class",""); this.classNames.add(className); } } }
6、doInstance方法
这个方法就是实例化所有带有MyController注解的类
private void doInstance(){ if (classNames.isEmpty()){ return; } for (String name : classNames){ try { //实例化注解为@MyController System.out.println(name); Class<?> clazz = Class.forName(name); if (clazz.isAnnotationPresent(MyController.class)){ //首字母小写存入 ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance()); }else { continue; } } catch (Exception e) { e.printStackTrace(); } } }
private String toLowerFirstWord(String name){ char[] chars = name.toCharArray(); chars[0]+=32; return String.valueOf(chars); }
7、initHandlerMapping方法
这个方法用来完成url与方法的映射以及url和Controller的映射
private void initHandlerMapping(){ if (ioc.isEmpty()){ return; } try { for (Map.Entry<String,Object> entry : ioc.entrySet()){ Class<? extends Object> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(MyController.class)){ continue; } String baseUrl = ""; //如果Controller类也有路径,将此路径加上 if (clazz.isAnnotationPresent(MyRequestMapping.class)){ MyRequestMapping annocation = clazz.getAnnotation(MyRequestMapping.class); baseUrl = annocation.value(); } Method[] methods = clazz.getMethods(); for (Method method : methods){ if (!method.isAnnotationPresent(MyRequestMapping.class)){ continue; } MyRequestMapping annocation = method.getAnnotation(MyRequestMapping.class); String url = annocation.value(); url = (baseUrl+"/"+url).replaceAll("/+","/"); handlerMapping.put(url,method); controllerMap.put(url,entry.getValue()); System.out.println(url+","+method); } } }catch (Exception e){ e.printStackTrace(); } }
8、进行请求分发
MyDispatcherServlet拦截所有的请求,并为每一个请求进行分发相应的handler
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { //处理请求 doDispatch(req,resp); }catch (Exception e){ resp.getWriter().write("500!!!Server Exception"); } } private void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception{ if (handlerMapping.isEmpty()){ return; } String uri = request.getRequestURI(); String contextPath = request.getContextPath(); uri = uri.replace(contextPath,"").replaceAll("/+","/"); if (!this.handlerMapping.containsKey(uri)){ response.getWriter().write("404 Not Found"); return; } Method method = this.handlerMapping.get(uri); //获取方法参数列表 Class<?>[] parameterTypes = method.getParameterTypes(); //获取请求的参数 Map<String, String[]> parameterMap = request.getParameterMap(); //保存参数值 Object[] paramsValues = new Object[parameterTypes.length]; //方法的参数列表 for (int i=0;i<parameterTypes.length;i++){ //根据参数名,进行处理 String requestParam = parameterTypes[i].getSimpleName(); if (requestParam.equals("HttpServletRequest")){ //参数类型已明确 paramsValues[i] = request; continue; } if (requestParam.equals("HttpServletResponse")){ paramsValues[i] = response; } if (requestParam.equals("String")){ for (Map.Entry<String, String[]> param : parameterMap.entrySet()){ String value = Arrays.toString(param.getValue()) .replaceAll("\\[|\\]","") .replaceAll(",\\s",","); paramsValues[i] = value; } } } //利用反射调用方法 try { method.invoke(this.controllerMap.get(uri),paramsValues); }catch (Exception e){ e.printStackTrace(); } }
好了,上述实现之后,我们的MySpringMvc就已经实现完成了。下面我们测试一下吧
定义TestController
@MyController @MyRequestMapping("/test") public class TestController { @MyRequestMapping("/doTest") public void test1(HttpServletRequest request, HttpServletResponse response, @MyRequestParam("param") String param){ System.out.println(param); try { response.getWriter().write("doTest method success : param:"+param); }catch (Exception e){ e.printStackTrace(); } } @MyRequestMapping("/doTest2") public void test2(HttpServletRequest request,HttpServletResponse response){ try { response.getWriter().println("doTest2 success"); }catch (Exception e){ e.printStackTrace(); } } }
启动tomcat,在地址栏输入http://localhost:8080/MySpringMvc/test/doTest?param=123。结果如下
再次输入http://localhost:8080/MySpringMvc/test/doTest2。结果如下
输入一个不存在的url:http://localhost:8080/MySpringMvc/test/doTest3。结果如下
好了,大功告成