超详细SpringMVC源码剖析

整体流程图在这里插入图片描述

1.自定义视图(63~66)

视图解析过程
1.先到DispatcherServlet中央控制器, 根据视图解析的 优先级 执行对应的 视图解析器
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
      Locale locale, HttpServletRequest request) throws Exception {

   if (this.viewResolvers != null) {
      for (ViewResolver viewResolver : this.viewResolvers) {//viewResolvers存放了两个视图解析器(1)InternalResourceViewResolver默认视图解析器   (2)BeanNameViewResolver自定义视图解析器
         View view = viewResolver.resolveViewName(viewName, locale);
         if (view != null) {//如果返回的视图为空,那么会继续遍历所有的视图解析器;
             //注意:如果 默认视图解析器 优先级在前,那么不管怎样,它都不会再执行 自定义视图解析器 
             //原因:默认视图解析器 一定会返回视图,因为它是通过传入的视图名build一个视图
            return view;
         }
      }
   }
   return null;
}
2.若默认视图解析器在前,那么用 视图解析器 解析
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
   InternalResourceView view = (InternalResourceView) super.buildView(viewName);//根据viewName创建视图
   if (this.alwaysInclude != null) {
      view.setAlwaysInclude(this.alwaysInclude);
   }
   view.setPreventDispatchLoop(true);
   return view;
}

注意:如果 默认视图解析器 优先级在前,那么不管怎样,它都不会再执行 自定义视图解析器 (因为:默认视图解析器 一定会返回视图,因为它是通过传入的视图名build一个视图)

3.若自定义视图解析器在前
public View resolveViewName(String viewName, Locale locale) throws BeansException {
   ApplicationContext context = obtainApplicationContext();//context实质上就类似于ioc容器
   if (!context.containsBean(viewName)) {//判断context(ioc)中是否有id为viewName的bean
      // Allow for ViewResolver chaining...
      return null;
   }
   if (!context.isTypeMatch(viewName, View.class)) {//判断context(ioc)中是否有id为viewName并实现View接口的bean
      if (logger.isDebugEnabled()) {
         logger.debug("Found bean named '" + viewName + "' but it does not implement View");
      }
      // Since we're looking into the general ApplicationContext here,
      // let's accept this as a non-match and allow for chaining as well...
      return null;
   }
   return context.getBean(viewName, View.class);
}

1.拿到context对象,实质上类似于ioc容器

2.//判断context(ioc)中是否有id为viewName的bean, 没有就返回null; 有就接着往下走

3.//判断context(ioc)中是否有id为viewName并实现View接口的bean 有就返回该视图 ,如果不存在传入的viewName对应的自定义视图,那么就返回null,接着走其他视图解析器

视图渲染和响应
  1. 拿到view对象后, SpringMVC 调用自定义视图的 renderMergedOutputModel 方法渲染视图

手动实现SpringMVC

自己实现 SpringMVC 底层机制 【核心分发控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器方法获取参数 + 视图解析 + 返回 JSON 格式数据 】

原生SpringMVC**一图胜千言

在这里插入图片描述

1 实现任务阶段 1- 开发 HspDispatcherServlet
1.1 说明**😗* 编写 HspDispatcherServlet 充当原生的 DispatcherServlet(即核心控制器)
1.2 分析**+**代码实现

2 实现任务阶段 2- 完成客户端**/**浏览器可以请求控制层

2.1 创建 自己的 Controller 和自定义注解

MonsterController控制器类

/**
 * Created by IntelliJ IDEA.
 * User: 加棉
 * Date: 2023/9/9
 * Time: 14:22
 */
package com.ljr.controller;

import com.ljr.ljrspringmvc.annotation.Controller;
import com.ljr.ljrspringmvc.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Controller
public class MonsterController {

    @RequestMapping("/listMonster")
    public void listMonster(HttpServletRequest request, HttpServletResponse response) {
        //设置编码和返回类型
        response.setContentType("text/html;charset=utf-8");
        StringBuilder stringBuilder = new StringBuilder("<h1>所有的monster<h1>");
        try {
            PrintWriter writer = response.getWriter();
            writer.write(stringBuilder.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

@Controller 注解

package com.ljr.ljrspringmvc.annotation;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    String value() default "";
}

@RequestMapping 注解

package com.ljr.ljrspringmvc.annotation;

import java.lang.annotation.*;

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}
2.2 配置 ljrspringmvc.xml
<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <!--指定要扫描的基础包及子包的路径-->
    <component-scan base-package="com.ljr.controller"/>
</beans>
2.3 编写 XMLParser 工具类,可以解析 ljrspringmvc.xml

0.完成功能说明

编写 XMLParser 工具类,可以解析hspringmvc.xml, 得到要扫描的包

/**
 * Created by IntelliJ IDEA.
 * User: 加棉
 * Date: 2023/9/9
 * Time: 14:48
 */
package com.ljr.ljrspringmvc.xml;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;

public class XMLParser {
    public static ArrayList<String> getBasePackage(String xmlFile){
        //定义一个集合,用于存放 指定扫描包的路径
        ArrayList<String> packageList = new ArrayList<>();

        SAXReader saxReader = new SAXReader();
        ClassLoader classLoader = XMLParser.class.getClassLoader();
        URL url = classLoader.getResource(xmlFile);
        try {
            Document read = saxReader.read(url);
            Element rootElement = read.getRootElement();
            //遍历所有的component-scan结点,将指定的包路径 放入packageList
            Iterator iterator = rootElement.elementIterator("component-scan");
            while (iterator.hasNext()) {
                Element nextComponentScanElement = (Element)iterator.next();
                String basePackage = nextComponentScanElement.attributeValue("base-package");
                packageList.add(basePackage);
            }
            return packageList;
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return packageList;
    }
}

2.4 开发 HspWebApplicationContext,充当 Spring 容器得到扫描类的全路径列表(扫描包)

简单说明:将要扫描的包下所有的文件的路径添加到一个集合中,方便下一阶段 实例化单例池

/**
 * Created by IntelliJ IDEA.
 * User: 加棉
 * Date: 2023/9/9
 * Time: 16:16
 */
package com.ljr.ljrspringmvc.context;

import com.ljr.ljrspringmvc.xml.XMLParser;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class LjrWebApplicationContext {
    //定义属性classFullPathList, 保存扫描包/子包的类的全路径
    public static List<String> classFullPathList = new ArrayList<>();


    public void init() {
        //获取要扫描包的路径(可能在ljrspringmvc.xml配置了多个<component-scan>,就会有多个)
        ArrayList<String> packageList = XMLParser.getBasePackage("ljrspringmvc.xml");
        if (packageList.size() > 0) {
            for (String basePackage : packageList) {
                //扫描包
                scanPackage(basePackage);
            }
        }
        System.out.println("classFullPathList = " + classFullPathList);
    }

    public void scanPackage(String basePackage) {
        ClassLoader classLoader = this.getClass().getClassLoader();
        URL url = classLoader.getResource("/" + basePackage.replaceAll("\\.", "/"));
        System.out.println("url = " + url);
        File file = new File(url.getFile());
        System.out.println("file = " + file);
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.isDirectory()) {//如果是一个目录
                //递归
                scanPackage(basePackage + "." + file1.getName());
            } else {//是文件
                classFullPathList.add(basePackage + "." + file1.getName().replaceAll("\\.class", ""));
            }
        }
    }
}

初始化HspWebApplicationContext, 因为LjrDispatcherServlet是tomcat一启动就创建的,所以可以在其init()初始化方法中调用,HspWebApplicationContext的初始化方法

package com.ljr.ljrspringmvc.servlet;

import com.ljr.ljrspringmvc.context.LjrWebApplicationContext;
import com.ljr.ljrspringmvc.xml.XMLParser;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LjrDispatcherServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
         LjrWebApplicationContext ljrWebApplicationContext = new LjrWebApplicationContext();
        ljrWebApplicationContext.init();
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("LjrDispatcherServlet doPost被调用...");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("LjrDispatcherServlet doGet被调用...");
    }
}
2.5 完善 HspWebApplicationContext,充当 Spring 容器-实例化对象到容器中(实例化单例池)

0 完成功能说明

  • 将扫描到的类**,** 在满足条件的情况下(即有相应的注解@Controller @Service…), 反射注入到 ioc 容器

实现:在LjrWebApplicationContex类中添加executeInstance()方法, 再在init方法中调用

//根据classFullPathList(需要扫描的包/子包的类的全路径),
//如果是.class文件并且被@Controller注解标识,就实例化到单例池
public void executeInstance() {
    if (classFullPathList.size() == 0) {
        return;
    }
    try {
        for (String classFullPath : classFullPathList) {
            ClassLoader classLoader = this.getClass().getClassLoader();
            System.out.println("classFullPath = " + classFullPath);
            Class<?> aClass = classLoader.loadClass(classFullPath);
            //判断aClass是否被@controller直接标识
            if (aClass.isAnnotationPresent(Controller.class)) {//被标识
                //实例化对象
                Object o = aClass.newInstance();
                String id;
                //判断是否设置了value
                String value = aClass.getDeclaredAnnotation(Controller.class).value();
                if (!(value == null || "".equals(value))) {
                    id = value;
                } else {//没有设置value,将类名首字母小写作为id
                    String simpleName = aClass.getSimpleName();
                    //将首字母小写
                    id = StringUtils.uncapitalize(simpleName);
                }
                //将实例对象放入ioc容器中
                ioc.put(id, o);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
2.6 完成请求 URL 和控制器方法的映射关系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现思路:
处理器映射器的作用:
1.根据ioc容器(即已经初始化的单例池singtonObject),遍历所有的controller的所有被LjrRequestMapping标识的方法
2.取出url,与controller实例和Method方法组成一个LjrHandler对象
3.将步骤2创建的LjrHandler对象放入一个List集合中

处理器映射器的代码实现:

/**
 * Created by IntelliJ IDEA.
 * User: 加棉
 * Date: 2023/9/10
 * Time: 15:53
 */
package com.ljr.ljrspringmvc.handle;

import com.ljr.ljrspringmvc.annotation.Controller;
import com.ljr.ljrspringmvc.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * LjrHandlerMapping 充当处理器映射器,初始化controller中定义的方法的映射
 */
public class LjrHandlerMapping {
    //定义属性handlerList, 保存LjrHandler
    public List<LjrHandler> handlerList = new ArrayList<>();

    private ConcurrentHashMap<String, Object> ioc;

    public LjrHandlerMapping(ConcurrentHashMap<String, Object> ioc) {
        this.ioc = ioc;
    }

    //初始化 URL与控制器方法的映射关系
    public void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entrySet = ioc.entrySet();
        for (Map.Entry<String, Object> entry : entrySet) {
            Class<?> aClass = entry.getValue().getClass();
            System.out.println("aClass = "+aClass);
            //定义一个属性,保存@RequestMapping标识在类上的value值
            String classURL = "";
            if(aClass.isAnnotationPresent(Controller.class)){
                if (aClass.isAnnotationPresent(RequestMapping.class)) {
                    //获取@RequestMapping标识在类上的value值
                    RequestMapping requestMappingAnnotation = aClass.getDeclaredAnnotation(RequestMapping.class);
                    classURL = requestMappingAnnotation.value();
                }
                //遍历所有的Method,看是否有被@RequestMapping标识
                //Method[] methods = aClass.getMethods();
                Method[] methods = aClass.getDeclaredMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {//说明该Method上标识了@RequestMapping注解
                        //拿出value值
                        RequestMapping requestMappingMethodAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
                        String methodURL = requestMappingMethodAnnotation.value();

                        //映射url = 类url + 方法url
                        String url = classURL + methodURL;
                        LjrHandler ljrHandler = new LjrHandler(url, entry.getValue(), method);
                        //将创建的 LjrHandler 对象放入handlerList集合中
                        handlerList.add(ljrHandler);
                    }
                }
            }
        }
    }
}
2.7 完成 HspDispatcherServlet 分发请求到对应控制器方法

在这里插入图片描述

实现思路:
增加executeDispatch()方法,用于请求分发,调用相应的处理器
1.根据request(使用request.getRequestURI()拿到用户请求的uri)返回对应的LjrHandler --在LjrMapping中添加 getLjrHandler()方法
2.有就调用执行相应的处理器,没有就报404错误

代码实现:

LjrHandlerMapping中的 getHandler()方法

//根据request返回对应的LjrHandler
public LjrHandler getLjrHandler(HttpServletRequest request) {
    //1.先获取的用户请求的uri 比如http://localhost:8080/springmvc/monster/list
    //  uri = /springmvc/monster/list
    String uri = request.getRequestURI();
    for (LjrHandler ljrHandler : handlerList) {
        if (uri.equals(ljrHandler.getUrl())) {//存在该url
            return ljrHandler;
        }
    }
    //不存在该url
    return null;
}

LjrDispatcherServlet中的 executeDispatch()方法

public void executeDispatch(HttpServletRequest request, HttpServletResponse response){
    LjrHandler ljrHandler = ljrHandlerMapping.getLjrHandler(request);
    if (ljrHandler!=null) {//找到匹配的LjrHandler
        //使用反射执行LjrHandler对应的方法
        Method method = ljrHandler.getMethod();
        try {
            method.invoke(ljrHandler.getController(),request,response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }else {//匹配不到LjrHandler,报404
        System.out.println("匹配不到LjrHandler");
        try {
            response.getWriter().write("<h1>404 NOT FOUNT</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3 实现任务阶段 3- web.xml 动态获取 hspspringmvc.xml

说明:前面我们加载 hspspringmvc.xml 是硬编码**,** 现在做活**,** 从 web.xml 动态获取

在这里插入图片描述

代码实现:
LjrDispatcherServlet类中的 init()方法:
(获取web.xml配置的初始化参数"contextConfigLocation",构建我们自己的spring容器LjrWebApplicationContext时,传入配置文件路径,动态创建)

public void init(ServletConfig config) throws ServletException {
    super.init(config);
    String contextConfigLocation = config.getInitParameter("contextConfigLocation");
    String ljrspringmvcPath = contextConfigLocation.substring(contextConfigLocation.indexOf(":") + 1);
    LjrWebApplicationContext ljrWebApplicationContext = new LjrWebApplicationContext(ljrspringmvcPath);
    //初始化映射关系
    ljrHandlerMapping = new LjrHandlerMapping(ljrWebApplicationContext.ioc);
    ljrHandlerMapping.initHandlerMapping(config);
    System.out.println("handlerList = "+ljrHandlerMapping.handlerList);
}

LjrWebApplicationContext类中的构造方法:

public LjrWebApplicationContext(String ljrspringmvcPath) {
    this.ljrspringmvcPath = ljrspringmvcPath;
    //初始化单例池
    init(ljrspringmvcPath);
}
4 实现任务阶段 4- 完成自定义**@Service** 注解功能

4.1 功能说明:如果给某个类加上@Service, 则可以将其注入到我们的 Spring 容器

  • 给 Service 类标注**@Service,** 可以将对象注入到 Spring 容器中

  • 并可以通过 id 来获取到 ServiceBean

  • 并可以通过接口名支持多级, 类名来获取到 ServiceBean

    getBean(String id)方法:

    //通过id获取bean
    public Object getBean(String id) {
        Object o = ioc.get(id);
        if (o == null) {
            throw new RuntimeException("getBean时没有该bean");
        }
        return o;
    }
    

getBean(Class aClass)方法:

//通过类名/接口名获取bean
    public Object getBean(Class aClass) {
        Object result = null;
        int count = 0;//统计有多少个对象符合要求

        //遍历ioc中所有的value,判断是否有aClass类型的对象
        Set<Map.Entry<String, Object>> entrySet = ioc.entrySet();
        for (Map.Entry<String, Object> entry : entrySet) {
            Object o = entry.getValue();
            //1.判断某个对象是否是aClass类型的实例
            if (!(aClass.isInterface())
                    && (aClass.isInstance(o))) {//aClass不是接口,并且对象o是aClass的实例
                result = o;
                count++;
            }
            //2.判断某个对象对应的类是否实现了aClass
            if (aClass.isInterface()) {
                Class<?>[] interfaces = o.getClass().getInterfaces();
                for (Class<?> anInterface : interfaces) {
                    if (aClass.equals(interfaces)) {
                        result = o;
                        count++;
                    }
                }
            }
        }
        if (result != null && count == 1) {//有且只有一个
            return result;
        }
        if (count==0){
            throw new RuntimeException("使用byType策略getBean时没有该类型的bean");
        }else{
            throw new RuntimeException("使用byType策略getBean时该类型的bean含有多个");
        }
    }
5- 完成 Spring 容器对象的自动装配 -@Autowried

说明: 完成 Spring 容器中对象的注入/自动装配
1.添加一个方法executeAutoWired(), 在executeInstance()后调用,
2.该方法遍历ioc,取出里面的object,遍历所有的字段
3.判断字段是否被 @AutoWired 标识,若是 ,进行下面4,5
4.获取待装配对象, 先根据byType策略,再根据byName策略(如果装配失败,给出相应的报错信息)
5.使用反射设置属性值(需爆破)

executeAutoWired()方法:

//完成spring容器中对象的自动装配
    private void executeAutoWired(){
        //遍历ioc
        for(ConcurrentHashMap.Entry<String,Object> entry : ioc.entrySet()){
            Object o = entry.getValue();
            Class<?> aClass = o.getClass();
            //遍历所有的字段,查看是否需要自动装配
            Field[] fields = aClass.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(AutoWired.class)) {
                    //根据字段类型,返回对象
                    Object bean = null;
                    try {
                        Class<?> fieldType = field.getType();
                        //通过属性类型获取bean
                        bean = getBean(fieldType);
                    } catch (Exception e) {
                        new PrinterException("AutoWired自动装配时, "+o+" 对象无法通过byType策略注入,将进行byName策略").printStackTrace();
                    }
                    if (bean==null){
                        //不是只有唯一一个该类型的对象, 需要根据id装配
                        String id = "";
                        //判断是否设置了value
                        AutoWired autoWiredAnnotation = field.getDeclaredAnnotation(AutoWired.class);
                        String value = autoWiredAnnotation.value();
                        if (value!=null&&value!=""){//设置了value
                            id = value;
                        }else{//未设置value
                            //根据属性名匹配
                            id = field.getName();
                        }
                        try {//如果通过byName策略还无法匹配注入bean,那就等着报错吧
                            bean = getBean(id);
                        } catch (Exception e) {
                            throw new RuntimeException("自动装配【"+field.getName()+"】属性失败");
                        }
                    }
                    //爆破
                    field.setAccessible(true);
                    try {
                        field.set(o,bean);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
6 实现任务阶段 6- 完成控制器方法获取参数**-@RequestParam**

功能说明:自定义**@RequestParam** 和 方法参数名获取参数

6.1 功能说明:自定义**@RequestParam** 和 方法参数名获取参数
6.2* 完成**😗* 将 方法的 HttpServletRequest HttpServletResponse 参数封装到参数数组,进行反射调用
6.3 完成**😗* 在方法参数 指定 @RequestParam 的参数封装到参数数组,进行反射调用
6.4 完成**😗* 在方法参数 没有指定 @RequestParam ,按照默认参数名获取值**,** 进行反射调用

代码:(修改了LjrDispatcherServlet类的executeDispatch()方法)

public void executeDispatch(HttpServletRequest request, HttpServletResponse response){
    LjrHandler ljrHandler = ljrHandlerMapping.getLjrHandler(request);
    if (ljrHandler!=null) {//找到匹配的LjrHandler
        //使用反射执行LjrHandler对应的方法

        //目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
        //1. 得到目标方法的所有形参参数信息[对应的数组]
        Class<?>[] parameterTypes = ljrHandler.getMethod().getParameterTypes();
        //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
        Object[] objects = new Object[parameterTypes.length];

        //3遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
        for (int i=0;i<parameterTypes.length;i++){
            //取出每一个形参类型
            //如果这个形参的类型是HttpServletRequest, 将request填充到params
            if ((HttpServletRequest.class).equals(parameterTypes[i])){
                objects[i]=request;
            }else if ((HttpServletResponse.class).equals(parameterTypes[i])){
                objects[i]=response;
            }
        }

        //将http请求参数封装到params数组中, 老韩提示,要注意填充实参的时候,顺序问题

        //1. 获取http请求的参数集合
        //解读
        //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
        //2. 返回的Map<String,String[]> String:表示http请求的参数名
        //   String[]:表示http请求的参数值,为什么是数组
        //
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String name = entry.getKey();//请求实参参数名
            String value = entry.getValue()[0];//请求实参参数值

            //根据name返回该参数的位置
            int indexRequestParameterIndex = getIndexRequestParameterIndex(ljrHandler.getMethod(), name);
            if (indexRequestParameterIndex!=-1){
                objects[indexRequestParameterIndex]=value;
            }
        }
        /*至此,请求的参数 已经按 方法参数的顺序 放入objects数组*/

        Method method = ljrHandler.getMethod();
        try {
            method.invoke(ljrHandler.getController(),objects);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }else {//匹配不到LjrHandler,报404
        try {
            response.getWriter().write("<h1>404 NOT FOUNT</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//根据参数名和目标方法返回该参数的位置
private int getIndexRequestParameterIndex(Method method,String name){
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (int i=0;i<parameterTypes.length;i++){
        //判断该参数字段是否被@RequestParam标识
        boolean annotationPresent = parameterTypes[i].isAnnotationPresent(RequestParam.class);
        if (annotationPresent){
            RequestParam declaredAnnotation = parameterTypes[i].getDeclaredAnnotation(RequestParam.class);
            String value = declaredAnnotation.value();
            if (name.equals(value)){
                return i;
            }
        }

    }
    //普通字段,方法参数名 和 请求参数名一致
    Parameter[] parameters = method.getParameters();
    for (int i=0;i<parameters.length;i++) {
        if (name.equals(parameters[i].getName())){
            return i;
        }
    }
    return -1;
}
7 实现任务阶段 7- 完成简单视图解析
7.1 功能说明:通过方法返回的 String, 转发或者重定向到指定页面
7.2分析和代码实现

定义一个视图解析器LjrViewResolveing类, 解析 handler处理器返回的结果

/**
 * Created by IntelliJ IDEA.
 * User: 加棉
 * Date: 2023/9/14
 * Time: 19:54
 */
package com.ljr.ljrspringmvc.viewResolving;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LjrViewResolveing {
    //编写一个方法,解析LjrHandler处理器的返回结果
    public void viewResolving(HttpServletRequest request, HttpServletResponse response,
                              Object object) throws ServletException, IOException {
        System.out.println("开始视图解析,解析内容:" + object);
        if (object instanceof String) {
            String viewName = (String) object;
            if (viewName.contains(":")) {//说明你返回的String 结果forward:/login_ok.jsp 或者 redirect:/xxx/xx/xx.xx
                String viewType = viewName.split(":")[0];//forward | redirect
                String viewPage = viewName.split(":")[1];//是你要跳转的页面名
                if ("forward".equals(viewType)) {
                    //请求转发
                    request.getRequestDispatcher(viewPage).forward(request, response);
                } else if ("redirect".equals(viewType)) {
                    //重定向
                    //这里的"/"是被服务器解析的
                    viewPage = request.getContextPath() + viewPage;
                    response.sendRedirect(viewPage);
                }
            } else {//默认是请求转发
                request.getRequestDispatcher(viewName).forward(request, response);
            }
        }
    }
}
8 实现任务阶段 8- 完成返回 JSON 格式数据**-@ResponseBody**
8.1 功能说明:通自定义**@ResponseBody** 返回 JSON 格式数据
8.2 分析**+**代码实现

1.定义注解@ResponseBody,用在方法上,说明该方法要放回 json 格式的数据
2.handler处理器的方法返回ArrayList集合
3.在视图解析器中,判断handler目标方法是否被@ResponseBody标识,如果时,将方法返回的数据转化成 json 格式,再相应给客户端.

@ResponseBody:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
整体流程图

在这里插入图片描述

项目源码
额外功能:(尚未实现的)
1.验证以及国际化
用法:

方法上标识 @Valid 注解

public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {

javabean上标识 标准的校验注解

public class Monster {
    private Integer id;

    //email是string,使用@NotEmpty
    @NotEmpty
    private String email;

    //@Range(min = 1,max = 100)
    //表示接收的age值,在 1-100之间

    @NotNull(message = "age不能为空")
    @Range(min = 1,max = 100)
    private Integer age;
底层实现思路:
/* 校验发生的时机: 在springmvc底层,反射调用目标方法时,会接收到http请求的数据,然后根据注解来进行验证, 
 * 在验证过程中,如果出现了错误,就把错误信息填充errors 和 map
2.数据转换机制(比如使用@RequestBody,将客户端发送的json数据转换成javabean)

底层实现思路:

在这里插入图片描述

当控制器处理方法使用到@RequestBody/@ResponseBody或HttpEntity/ResponseEntity时, Spring首先根据请求头或响应头的 Accept属性选择匹配 的HttpMessageConverter, 进而 根据 参数类型 或泛型 类型的 过滤得 到匹配 的HttpMessageConverter, 若找不到可用的 HttpMessageConverter将报错
在这里插入图片描述

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
弃用了struts,用spring mvc框架做了几个项目,感觉都不错,而且使用了注解方式,可以省掉一大堆配置文件。本文主要介绍使用注解方式配置的spring mvc,之前写的spring3.0 mvc和rest小例子没有介绍到数据层的内容,现在这一篇补上。下面开始贴代码。 文中用的框架版本:spring 3,hibernate 3,没有的,自己上网下。 先说web.xml配置: [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 03. <display-name>s3h3</display-name> 04. <context-param> 05. <param-name>contextConfigLocation</param-name> 06. <param-value>classpath:applicationContext*.xml</param-value> 07. </context-param> 08. <listener> 09. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10. </listener> 11. 12. <servlet> 13. <servlet-name>spring</servlet-name> 14. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15. <load-on-startup>1</load-on-startup> 16. </servlet> 17. <servlet-mapping> 18. <servlet-name>spring</servlet-name> <!-- 这里在配成spring,下边也要写一个名为spring-servlet.xml的文件,主要用来配置它的controller --> 19. <url-pattern>*.do</url-pattern> 20. </servlet-mapping> 21. <welcome-file-list> 22. <welcome-file>index.jsp</welcome-file> 23. </welcome-file-list> 24.</web-app> spring-servlet,主要配置controller的信息 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02. <beans 03. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 04. xmlns:context="http://www.springframework.org/schema/context" 05. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 06. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 07. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 09. 10. <context:annotation-config /> 11. <!-- 把标记了@Controller注解的类转换为bean --> 12. <context:component-scan base-package="com.mvc.controller" /> 13. <!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 --> 14. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> 15. 16. <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --> 17. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 18. p:prefix="/WEB-INF/view/" p:suffix=".jsp" /> 19. 20. <bean id="multipartResolver" 21. class="org.springframework.web.multipart.commons.CommonsMultipartResolver" 22. p:defaultEncoding="utf-8" /> 23. </beans> applicationContext.xml代码 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<beans 03. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 04. xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" 05. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 06. xsi:schemaLocation=" 07. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 09. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 11. 12. <context:annotation-config /> 13. <context:component-scan base-package="com.mvc" /> <!-- 自动扫描所有注解该路径 --> 14. 15. <context:property-placeholder location="classpath:/hibernate.properties" /> 16. 17. <bean id="sessionFactory" 18. class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 19. <property name="dataSource" ref="dataSource" /> 20. <property name="hibernateProperties"> 21. <props> 22. <prop key="hibernate.dialect">${dataSource.dialect}</prop> 23. <prop key="hibernate.hbm2ddl.auto">${dataSource.hbm2ddl.auto}</prop> 24. <prop key="hibernate.hbm2ddl.auto">update</prop> 25. </props> 26. </property> 27. <property name="packagesToScan"> 28. <list> 29. <value>com.mvc.entity</value><!-- 扫描实体类,也就是平时所说的model --> 30. </list> 31. </property> 32. </bean> 33. 34. <bean id="transactionManager" 35. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 36. <property name="sessionFactory" ref="sessionFactory" /> 37. <property name="dataSource" ref="dataSource" /> 38. </bean> 39. 40. <bean id="dataSource" 41. class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 42. <property name="driverClassName" value="${dataSource.driverClassName}" /> 43. <property name="url" value="${dataSource.url}" /> 44. <property name="username" value="${dataSource.username}" /> 45. <property name="password" value="${dataSource.password}" /> 46. </bean> 47. <!-- Dao的实现 --> 48. <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> 49. <property name="sessionFactory" ref="sessionFactory" /> 50. </bean> 51. <tx:annotation-driven transaction-manager="transactionManager" /> 52. <tx:annotation-driven mode="aspectj"/> 53. 54. <aop:aspectj-autoproxy/> 55.</beans> hibernate.properties数据库连接配置 [java] view plaincopy 01.dataSource.password=123 02.dataSource.username=root 03.dataSource.databaseName=test 04.dataSource.driverClassName=com.mysql.jdbc.Driver 05.dataSource.dialect=org.hibernate.dialect.MySQL5Dialect 06.dataSource.serverName=localhost:3306 07.dataSource.url=jdbc:mysql://localhost:3306/test 08.dataSource.properties=user=${dataSource.username};databaseName=${dataSource.databaseName};serverName=${dataSource.serverName};password=${dataSource.password} 09.dataSource.hbm2ddl.auto=update 配置已经完成,下面开始例子 先在数据库建表,例子用的是mysql数据库 [java] view plaincopy 01.CREATE TABLE `test`.`student` ( 02. `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 03. `name` varchar(45) NOT NULL, 04. `psw` varchar(45) NOT NULL, 05. PRIMARY KEY (`id`) 06.) 建好表后,生成实体类 [java] view plaincopy 01.package com.mvc.entity; 02. 03.import java.io.Serializable; 04. 05.import javax.persistence.Basic; 06.import javax.persistence.Column; 07.import javax.persistence.Entity; 08.import javax.persistence.GeneratedValue; 09.import javax.persistence.GenerationType; 10.import javax.persistence.Id; 11.import javax.persistence.Table; 12. 13.@Entity 14.@Table(name = "student") 15.public class Student implements Serializable { 16. private static final long serialVersionUID = 1L; 17. @Id 18. @Basic(optional = false) 19. @GeneratedValue(strategy = GenerationType.IDENTITY) 20. @Column(name = "id", nullable = false) 21. private Integer id; 22. @Column(name = "name") 23. private String user; 24. @Column(name = "psw") 25. private String psw; 26. public Integer getId() { 27. return id; 28. } 29. public void setId(Integer id) { 30. this.id = id; 31. } 32. 33. public String getUser() { 34. return user; 35. } 36. public void setUser(String user) { 37. this.user = user; 38. } 39. public String getPsw() { 40. return psw; 41. } 42. public void setPsw(String psw) { 43. this.psw = psw; 44. } 45.} Dao层实现 [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.public interface EntityDao { 06. public List<Object> createQuery(final String queryString); 07. public Object save(final Object model); 08. public void update(final Object model); 09. public void delete(final Object model); 10.} [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.import org.hibernate.Query; 06.import org.springframework.orm.hibernate3.HibernateCallback; 07.import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 08. 09.public class EntityDaoImpl extends HibernateDaoSupport implements EntityDao{ 10. public List<Object> createQuery(final String queryString) { 11. return (List<Object>) getHibernateTemplate().execute( 12. new HibernateCallback<Object>() { 13. public Object doInHibernate(org.hibernate.Session session) 14. throws org.hibernate.HibernateException { 15. Query query = session.createQuery(queryString); 16. List<Object> rows = query.list(); 17. return rows; 18. } 19. }); 20. } 21. public Object save(final Object model) { 22. return getHibernateTemplate().execute( 23. new HibernateCallback<Object>() { 24. public Object doInHibernate(org.hibernate.Session session) 25. throws org.hibernate.HibernateException { 26. session.save(model); 27. return null; 28. } 29. }); 30. } 31. public void update(final Object model) { 32. getHibernateTemplate().execute(new HibernateCallback<Object>() { 33. public Object doInHibernate(org.hibernate.Session session) 34. throws org.hibernate.HibernateException { 35. session.update(model); 36. return null; 37. } 38. }); 39. } 40. public void delete(final Object model) { 41. getHibernateTemplate().execute(new HibernateCallback<Object>() { 42. public Object doInHibernate(org.hibernate.Session session) 43. throws org.hibernate.HibernateException { 44. session.delete(model); 45. return null; 46. } 47. }); 48. } 49.} Dao在applicationContext.xml注入 <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> Dao只有一个类的实现,直接供其它service层调用,如果你想更换为其它的Dao实现,也只需修改这里的配置就行了。 开始写view页面,WEB-INF/view下新建页面student.jsp,WEB-INF/view这路径是在spring-servlet.xml文件配置的,你可以配置成其它,也可以多个路径。student.jsp代码 [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>添加</title> 09.<mce:script language="javascript" src="<%=request.getContextPath()%><!-- 10./script/jquery.min.js"> 11.// --></mce:script> 12.<mce:style><!-- 13.table{ border-collapse:collapse; } 14.td{ border:1px solid #f00; } 15.--></mce:style><style mce_bogus="1">table{ border-collapse:collapse; } 16.td{ border:1px solid #f00; }</style> 17.<mce:script type="text/javascript"><!-- 18.function add(){ 19. [removed].href="<%=request.getContextPath() %>/student.do?method=add"; 20.} 21. 22.function del(id){ 23.$.ajax( { 24. type : "POST", 25. url : "<%=request.getContextPath()%>/student.do?method=del&id;=" + id, 26. dataType: "json", 27. success : function(data) { 28. if(data.del == "true"){ 29. alert("删除成功!"); 30. $("#" + id).remove(); 31. } 32. else{ 33. alert("删除失败!"); 34. } 35. }, 36. error :function(){ 37. alert("网络连接出错!"); 38. } 39.}); 40.} 41.// --></mce:script> 42.</head> 43.<body> 44. 45.<input id="add" type="button" value="添加"/> 46.<table > 47. <tr> 48. <td>序号</td> 49. <td>姓名</td> 50. <td>密码</td> 51. <td>操作</td> 52. </tr> 53. <c:forEach items="${list}" var="student"> 54. <tr id="<c:out value="${student.id}"/>"> 55. <td><c:out value="${student.id}"/></td> 56. <td><c:out value="${student.user}"/></td> 57. <td><c:out value="${student.psw}"/></td> 58. <td> 59. <input type="button" value="编辑"/> 60. <input type="button" value="${student.id}"/>')" value="删除"/> 61. </td> 62. </tr> 63. </c:forEach> 64. 65.</table> 66.</body> 67.</html> student_add.jsp [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>学生添加</title> 09.<mce:script type="text/javascript"><!-- 10.function turnback(){ 11. [removed].href="<%=request.getContextPath() %>/student.do"; 12.} 13.// --></mce:script> 14.</head> 15.<body> 16.<form method="post" action="<%=request.getContextPath() %>/student.do?method=save"> 17.<div><c:out value="${addstate}"></c:out></div> 18.<table> 19. <tr><td>姓名</td><td><input id="user" name="user" type="text" /></td></tr> 20. <tr><td>密码</td><td><input id="psw" name="psw" type="text" /></td></tr> 21. <tr><td colSpan="2" align="center"><input type="submit" value="提交"/><input type="button" value="返回" /> </td></tr> 22.</table> 23. 24.</form> 25.</body> 26.</html> controller类实现,只需把注解写上,spring就会自动帮你找到相应的bean,相应的注解标记意义,不明白的,可以自己查下@Service,@Controller,@Entity等等的内容。 [java] view plaincopy 01.package com.mvc.controller; 02. 03.import java.util.List; 04. 05.import javax.servlet.http.HttpServletRequest; 06.import javax.servlet.http.HttpServletResponse; 07. 08.import org.apache.commons.logging.Log; 09.import org.apache.commons.logging.LogFactory; 10.import org.springframework.beans.factory.annotation.Autowired; 11.import org.springframework.stereotype.Controller; 12.import org.springframework.ui.ModelMap; 13.import org.springframework.web.bind.annotation.RequestMapping; 14.import org.springframework.web.bind.annotation.RequestMethod; 15.import org.springframework.web.bind.annotation.RequestParam; 16.import org.springframework.web.servlet.ModelAndView; 17. 18.import com.mvc.entity.Student; 19.import com.mvc.service.StudentService; 20. 21.@Controller 22.@RequestMapping("/student.do") 23.public class StudentController { 24. protected final transient Log log = LogFactory 25. .getLog(StudentController.class); 26. @Autowired 27. private StudentService studentService; 28. public StudentController(){ 29. 30. } 31. 32. @RequestMapping 33. public String load(ModelMap modelMap){ 34. List<Object> list = studentService.getStudentList(); 35. modelMap.put("list", list); 36. return "student"; 37. } 38. 39. @RequestMapping(params = "method=add") 40. public String add(HttpServletRequest request, ModelMap modelMap) throws Exception{ 41. return "student_add"; 42. } 43. 44. @RequestMapping(params = "method=save") 45. public String save(HttpServletRequest request, ModelMap modelMap){ 46. String user = request.getParameter("user"); 47. String psw = request.getParameter("psw"); 48. Student st = new Student(); 49. st.setUser(user); 50. st.setPsw(psw); 51. try{ 52. studentService.save(st); 53. modelMap.put("addstate", "添加成功"); 54. } 55. catch(Exception e){ 56. log.error(e.getMessage()); 57. modelMap.put("addstate", "添加失败"); 58. } 59. 60. return "student_add"; 61. } 62. 63. @RequestMapping(params = "method=del") 64. public void del(@RequestParam("id") String id, HttpServletResponse response){ 65. try{ 66. Student st = new Student(); 67. st.setId(Integer.valueOf(id)); 68. studentService.delete(st); 69. response.getWriter().print("{/"del/":/"true/"}"); 70. } 71. catch(Exception e){ 72. log.error(e.getMessage()); 73. e.printStackTrace(); 74. } 75. } 76.} service类实现 [java] view plaincopy 01.package com.mvc.service; 02. 03.import java.util.List; 04. 05.import org.springframework.beans.factory.annotation.Autowired; 06.import org.springframework.stereotype.Service; 07.import org.springframework.transaction.annotation.Transactional; 08. 09.import com.mvc.dao.EntityDao; 10.import com.mvc.entity.Student; 11. 12.@Service 13.public class StudentService { 14. @Autowired 15. private EntityDao entityDao; 16. 17. @Transactional 18. public List<Object> getStudentList(){ 19. StringBuffer sff = new StringBuffer(); 20. sff.append("select a from ").append(Student.class.getSimpleName()).append(" a "); 21. List<Object> list = entityDao.createQuery(sff.toString()); 22. return list; 23. } 24. 25. public void save(Student st){ 26. entityDao.save(st); 27. } 28. public void delete(Object obj){ 29. entityDao.delete(obj); 30. } 31.} OK,例子写完。有其它业务内容,只需直接新建view,并实现相应comtroller和service就行了,配置和dao层的内容基本不变,也就是每次只需写jsp(view),controller和service调用dao就行了。 怎样,看了这个,spring mvc是不是比ssh实现更方便灵活。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值