今天我们来实现一个简单的springMVC框架,可以理解为 springMVC1.0这个版本,只是功能比较简单而已;
废话不多说,进入正题;先看看springMVC的简单流程;
我们请求的地址会被dispatchservlet这个springMVC的核心拦截器拦截,然后dispatchservlet会找到我们想要的那个controller里的那个方法,并调用。但是dispatchServlet不是人,它没那么智能,看到url就知道是谁了,但是我们可以让它变得智能起来,我们可以利用handlerMapping来告诉
dispatchServlet,它应该调用哪个方法;
为了让这个框架不那么笨,我借用了spring的IOC 思想, 实现一个容器来管理我的bean; 这个框架和你印象中使用springmvc 很相似,你应该能回想起
springMVC的零零点点,然后看看这个简单的框架是如何实现这些功能的;
首先看下项目工程:
首先,我们先看maven 依赖,没错,自己实现的框架当然不用spring的jar包了;
并为了方便大家理解,我的取名和spring原来的风格多少有些类似;
首先看到annotation包,@Autowired、@Controller、@RequestMapping、@service这些注解大家应该再熟悉不过了吧!
然后是 servlet包,模仿的springMVC的核心拦截器 dispatchServlet;
demo包的 controller service 不必解释啦~
这里我们模仿springMVC的调用规则,MyDispatcherServlet负责处理框架的逻辑,调用Controller,Controller调用service;
先看看自定义注解是如何定义的,这里挑选了几个代表;
开始编写核心拦截器 dispatchServilet
我们第一步模仿spring 的思想,先找到我们要扫描哪些类,下面是 spring的做法,
这是我的做法:
为什么我通过 String scanPackage = config.getInitParameter("scanPackage"); 就能找到xml中的配置呢?请参考这里 的初始化细节;
servlet 对象在初始化的时候,容器会给它提供一个 ServletConfig 对象 去读取 web.xml中的配置;
我们得到要扫描的路径后,可以就需要实现spring的IOC了;
我们为了得到所有bean;在拿到项目的包路径后,可以转换为文件路径,然后再从文件路径中得到所有的类名;
得到类名后,就可以通过反射进行实例化了,然后将这些需要管理的东西放到一个容器中管理,要用的时候从容器里拿就可以了;
我这里使用的容器是 Hashmap<String, Object> 类的简称(SimpleName)为key ,类的实例对象为value。
得到了所有的类名后,开始实例化的工作
- private void instance(){
-
- if(classNames.size() == 0){ return; }
- try{
- for (String className : classNames) {
-
- Class<?> clazz = Class.forName(className);
-
- if(clazz.isAnnotationPresent(LANController.class)){
-
- String beanName = lowerFirstChar(clazz.getSimpleName());
- instanceMapping.put(beanName, clazz.newInstance());
- }else if(clazz.isAnnotationPresent(LANService.class)){
- LANService service = clazz.getAnnotation(LANService.class);
- String beanName = service.value();
- if(!"".equals(beanName.trim())){
-
- instanceMapping.put(beanName, clazz.newInstance());
- continue;
- }
-
- Class<?> [] interfaces = clazz.getInterfaces();
- for (Class<?> i : interfaces) {
- instanceMapping.put(i.getName(), clazz.newInstance());
- }
- }else{
- continue;
- }
- }
- }catch(Exception e){
- e.getStackTrace();
- }
- }
实例化以后,就要准备注入了;
- private void autowired(){
- if(instanceMapping.isEmpty()){ return; }
- for (Entry<String, Object> entry : instanceMapping.entrySet()) {
-
- Field [] fields = entry.getValue().getClass().getDeclaredFields();
- for (Field field : fields) {
- if(!field.isAnnotationPresent(LANAutowired.class)){ continue; }
- LANAutowired autowired = field.getAnnotation(LANAutowired.class);
-
- field.setAccessible(true);
-
- String beanName = autowired.value().trim();
- System.out.println("beanName=="+beanName);
-
- if("".equals(beanName)){
-
- beanName = field.getType().getName();
- }
- try {
- System.out.println("field.getName()***"+field.getName());
-
- System.out.println("entry.getValue()======"+entry.getValue());
- System.out.println("instanceMapping.get(beanName)---------"+instanceMapping.get(beanName));
-
- field.set(entry.getValue(),instanceMapping.get(beanName));
- } catch (Exception e) {
- e.printStackTrace();
- continue;
- }
- }
- }
- }
- field.set(entry.getValue(),instanceMapping.get(beanName));
这一行代码是关键,这就是为什么我们在注入接口,就能找到实现类的根本所在。
这里的field,就是我们注入的那个接口, entry.getValue() 得到的是接口所在的类,instanceMapping.get(beanName)是这个接口对应的
那个实现类, 意思就是:在 运行阶段, 将 controller中 的某个service接口字段 替换成 这个service的实现类;
这样我们在编写代码的时候是用使用接口调用方法,但实际运行时,就是它的实现类在调用这个方法了;
不得不感叹,反射的强大。
完成注入后,开始处理handlermapping上的value与之对应的method 的映射关系了
- public void handlerMapping(){
- if(instanceMapping.isEmpty()){ return; }
- for (Entry<String, Object> entry : instanceMapping.entrySet()) {
- Class<?> clazz = entry.getValue().getClass();
-
- if(!clazz.isAnnotationPresent(LANController.class)){ continue; }
- String url = "";
- if(clazz.isAnnotationPresent(LANRequestMapping.class)){
- LANRequestMapping requstMapping = clazz.getAnnotation(LANRequestMapping.class);
-
- url = requstMapping.value();
- }
- Method [] methods = clazz.getMethods();
- for (Method method : methods) {
- if(!method.isAnnotationPresent(LANRequestMapping.class)){ continue; }
- LANRequestMapping requstMapping = method.getAnnotation(LANRequestMapping.class);
- String regex = url + requstMapping.value();
-
- regex = regex.replaceAll("/+", "/").replaceAll("\\*", ".*");
- System.out.println("regex: "+regex);
- Map<String,Integer> pm = new HashMap<String,Integer>();
-
- Annotation [] [] pa = method.getParameterAnnotations();
- for(int i = 0; i < pa.length; i ++){
- for (Annotation a : pa[i]){
- if(a instanceof LANRequestParam){
- String paramName = ((LANRequestParam) a).value();
- if(!"".equals(paramName.trim())){
-
- pm.put(paramName, i);
- }
- }
- }
- }
-
- Class<?> [] paramsTypes = method.getParameterTypes();
- for(int i = 0 ; i < paramsTypes.length; i ++){
- Class<?> type = paramsTypes[i];
- if(type == HttpServletRequest.class || type == HttpServletResponse.class){
-
- pm.put(type.getName(), i);
- }
- }
- handlerMapping.add(new Handler(Pattern.compile(regex),entry.getValue(),method, pm));
- }
- }
- }
完成所有的初始化工作后,当然就等着用户发过来的请求咯。
那自然是调用servlet的 doPost 和 doGet方法了,
为了简单点,我在doGet中调用doPost
- @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{
-
-
- boolean isMatcher = pattern(req,resp);
- if(!isMatcher){
- resp.getWriter().write("对不起,你遇到了 404 Not Found");
- }
- }catch(Exception e){
- resp.getWriter().write("500 Exception,Details:\r\n" +
- e.getMessage() + "\r\n" +
- Arrays.toString(e.getStackTrace()).replaceAll("\\[\\]", "")
- .replaceAll(",\\s", "\r\n"));
- }
- }
如果如果没有匹配成功就返回404,说明用户的路径输错了,
发送异常就报500;
如果匹配成功怎么办? 当然是调用controller里的方法咯;
怎么调用?还是通过反射~通过方法的反射~
- public boolean pattern(HttpServletRequest req, HttpServletResponse resp) throws Exception{
- if(handlerMapping.isEmpty()){ return false; }
-
- String url = req.getRequestURI();
-
- String contextPath = req.getContextPath();
- url = url.replace(contextPath, "").replaceAll("/+", "/");
- for (Handler handler : handlerMapping) {
- try{
- Matcher matcher = handler.pattern.matcher(url);
-
- if(!matcher.matches()){ continue ;}
-
- Class<?> [] paramTypes = handler.method.getParameterTypes();
-
- Object [] paramValues = new Object[paramTypes.length];
-
- Map<String,String[]> params = req.getParameterMap();
- for (Entry<String, String []> param : params.entrySet()) {
- String value = Arrays.toString(param.getValue()).replaceAll("\\]|\\[", "").replaceAll(",\\s", ",");
- if(!handler.paramMapping.containsKey(param.getKey())){ continue;}
-
- int index = handler.paramMapping.get(param.getKey());
-
- paramValues[index] = castStringValue(value,paramTypes[index]);
- }
-
- int reqIndex = handler.paramMapping.get(HttpServletRequest.class.getName());
- paramValues[reqIndex] = req;
- int respIndex = handler.paramMapping.get(HttpServletResponse.class.getName());
- paramValues[respIndex] = resp;
-
- handler.method.invoke(handler.controller, paramValues);
- return true;
- }catch(Exception e){
- throw e;
- }
- }
- return false;
- }
我们来看看controller的代码,准备测试:
- public class MyController {
-
- public MyController() {
- System.out.println("初始化了"+this);
- }
-
- @LANAutowired
- private searchService searchService;
-
- @LANAutowired("aa")
- private modifyService modifyService;
-
- @LANRequestMapping("/search/*.json")
- public void search(HttpServletRequest request,HttpServletResponse response,
- @LANRequestParam("name") String name){
- String result = searchService.search(name);
- System.out.println("查询成功");
- out(response,result);
- }
-
-
- @LANRequestMapping("/add.json")
- public void add(HttpServletRequest request,HttpServletResponse response,
- @LANRequestParam("name") String name,
- @LANRequestParam("addr") String addr){
- System.out.println("添加成功");
- String result = modifyService.add(name,addr);
- out(response,result);
- }
-
-
- @LANRequestMapping("/delete.json")
- public void remove(HttpServletRequest request,HttpServletResponse response,
- @LANRequestParam("id") Integer id){
- System.out.println("删除成功");
- String result = modifyService.remove(id);
- out(response,result);
- }
是不是和你刚学springMVC时的一致?
赶紧启动项目在地址栏输入:http://localhost:8080/LanSpringMVC/web/add.json?name=witt&addr=shenzhen
出现:
- add name = witt,addr=shenzhen
说明大工告成~
想要源码的小伙伴点这里:源码