SpringMVC框架——初学知识点篇1
注:作者本人也是初学者,所以本文有些总结性见解可能存在问题,但是多数问题都是在上网查询过资料后总结的,如果有逻辑或者原理上的错误,或者见解不同,欢迎在评论区讨论!!!
目录
3.SpringMVC:一种基于java实现MVC设计模型的请求驱动类型的轻量级WEB框架
4.使用注解配置Controller类中业务方法的映射地址(在第三步的@RequestMapping上)
3.不返回modelandview,如何将处理后的值传送到前台页面
2.使用ModelAndView向request中添加共享数据
SpringMVC其实就是一个Servlet,他是基于原生servlet,并且封装了一个功能十分强大的前端控制器DispatcherServlet,以此对请求和相应做出统一处理。
1.MVC模型
2.SpringMVC在三层架构的位置
Mybatis负责处理数据访问层
Spring负责处理业务逻辑层
SpringMVC主要负责处理表现层
3.SpringMVC:一种基于java实现MVC设计模型的请求驱动类型的轻量级WEB框架
回顾(重点):
刚刚学习完javaweb时的项目案例流程,我们都是基于三层架构实现,前端页面布局设置好后我们需要创建servlet按照指定路径去接收客户端发送过来的信息,我们一般单独创建一个类去继承HTTPServlet,重写doPost和doGet方法,单独处理某个功能。以单纯的登录案例来看,我们就需要处理登录信息的LoginServlet类,处理登陆完后的SuccessServlet类,以及登录失败的FailServelet类。
如此布局代码臃肿,Servlet类众多。当时我们提出了以下解决方案:将我们所写的为了完成某个总体业务的一堆Servlet类都转换成一个Servlet类中的多个方法,这样就大大减少了代码的冗余。
为了实现这样的目的,我们查看HTTPServlet内部实现。由于每个类都约束于HttpServlet的doGet和doPost两个方法,我们发现HttpServlet里面的service方法读取了request请求信息,获取了访问方法类别,并且封装为了doGet方法和doPost方法。当浏览器访问我们所写类时,我们的Servlet类中只有doGet和doPost两种方法,那么没有重写的service方法就会继承父类HttpServlet类中的service方法,该方法内部通过request获取请求类型,并且根据不同的类型调用了不同的方法,如此实现了根据访问类型调用doGet方法和doPost方法。
如此一来我们就可以仿造HttpServlet中的设计思想:通过request信息获取我们要调用的方法名,然后再重写service方法,调用方法即可。
我们设计了包含各种各样方法的AllServlet类,以及继承HttpServlet重写service方法的BaseServlet类。以登录案例为例,AllServlet里面有login方法,success方法和fail方法,分别处理登录,登录成功以及登录失败三个问题。而AllServlet继承BaseServlet类,BaseServlet类则负责重写HttpServlet方法,当用户带着请求信息访问AllServlet类时,由于该类没有service方法,因此会调用继承自父类BaseServlet的service方法,此时的service方法已经被我们重写过了,他会通过request的请求信息,获取需要调用的方法名,然后通过java反射机制,通过方法名获取method对象。从而在service中完成对应方法的调用。
如此,我们就可以在同一个Servlet类中书写专门用于处理某一个模块问题的多个方法,相比于之前多个Servlet,代码冗余大幅减少,同时项目结构也更加清晰了。
但是这么写依旧相当麻烦,那有没有什么东西能帮我们实现好了呢?
1)开发步骤
首先我们按照规格打一下整体流程,感受一下springMVC开发与之前使用Servlet开发的不同点
注意:以下代码时基于maven开发,jsp作为前端页面的代码(由于Thymeleaf我们还未学习)
1.导入SpringMVC相关坐标(pom.xml)
<properties> <!--定义统一版本--> <spring.version>5.2.9.RELEASE</spring.version> </properties> <dependencies> <dependency> <!--spring核心jar包--> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <!--springmvc核心jar包--> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <!--servlet-api包--> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <!--可能会用到的jsp-api包--> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> </dependency> </dependencies>
2.在web.xml中配置SpringMVC核心控制器
<web-app> <display-name>Archetype Created Web Application</display-name> <!--配置springmvc的核心控制器--> <servlet> <servlet-name>SpringMVCDispatherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置springMVC需要的配置文件--> <init-param> <!--contextConfigLocation是SpringMVC中已经定义好的参数名,不能写别的--> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--配置Servlet的对象创建时间,服务器启动应用就创建对象--> <!-- 正常的Servlet初始化实在客服端第一次访问服务器的时候初始化,但是我们的前端控制器DispatcherServlet负责诸多领域, 内容庞大所以如果在第一次访问的时候初始化,势必会影响用户第一次使用的体验,所以再次我们将其设置为服务器启动时创建对象。 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVCDispatherServlet</servlet-name> <!-- 设置访问路径,该访问路径只对.html .css或者.js等静态资源生效,对.jsp动态资源是不生效的, 前端控制其实就是DispatcherServlet,是一个Servlet,他是用来处理前端页面的 而jsp文件他本身其实就是一个Servlet,不需要别的Servlet对其进行处理,jsp文件由org.apache.jasper.servlet.JspServlet这个Servlet来处理jsp资源。 --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
注意:
1.springmvc.xml是自己在resource包下书写的配置文件,当然可以定义成别的名字
2.jsp文件需要特殊Servlet去处理而不是这里的DispatcherServlet。DispatcherServlet使用"/"去拦截,他的拦截优先级小于处理jsp的拦截器,所以jsp文件会先被他的专属Servlet拦截并且处理,而类似于html这样的静态资源就会被后面的DispatchServlet拦截。如果使用"/*"去拦截,那么他的拦截优先级就会高于jsp专属拦截器,此时jsp会被DispatcherServlet拦截下来当作一个普通的静态前端页面去处理,一般会出错。
3.创建Controller类(请求控制器)和实体页面
@Controller public class HelloController { //括号内的hello是访问该方法的请求路径名称 @RequestMapping(value = "/hello") public String hellomethod() { System.out.println("hello..."); //返回值的类型为String,内容为想要跳转到的前端页面的名称(不加厚嘴) return "index"; } }
4.使用注解配置Controller类中业务方法的映射地址(在第三步的@RequestMapping上)
5.配置SpringMVC核心文件
在第三步中定位的springmvc.xml中书写以下代码
如果使用的是jsp页面:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.tencent"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--定义前后缀--> <property name="prefix" value="/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
如果使用的是html页面(如果学习过Thymeleaf就使用,当然需要导入Thymeleaf核心jar包)
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.ling"/> <!-- 配置Thymeleaf视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="order" value="1"/> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀 --> <property name="prefix" value="/WEB-INF/pages/"/> <!-- 视图后缀 --> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML5"/> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean> </beans>
6.测试请求
我在WEB-INF下定义pages包,包下创建index.jsp,通过服务器地址+ /hello进行初次访问。
2)初次运用感受
回顾一下第一次使用springmvc的感受吧
1.配置的文件还挺多的,同时有点迷迷糊糊,尤其是第二步在web.xml中配置核心控制器,核心控制器是用来干什么的呢?
2.其次就是表现层,表现层由我们原来的一个一个Servlet变成了一个一个方法。而原本放在类上用于配置访问路径的注解@WebServlet(value = "/路径")变成了放在方法上的注解@RequestMapping(value = "/路径")
3.方法的返回值是一个字符串名字,而且恰好是我们想要跳转到的页面的名字。
4.第五步配置SpringMVC核心文件的时候配置了InternalResourceViewResolver,里面我们配置了前缀和后缀,前缀和后缀的名称分别对应了我们要访问的页面前面的路径以及后面的后缀。真实路径 = prefix中配置的内容 + 实体类方法的返回值 + suffix中配置的内容
3)访问步骤
-
第一步,浏览器发送请求给服务器,在服务器中前端控制器DispatcherServlet获取了请求,但是实际上他只是获取了前端传来的信息,实际上他不知道在传来时该请求是否被监听被拦截,如何拦截,拦截几次等。
-
第二步,前端控制器DispatcherServlet向处理映射器HandlerMapping发送请求,请求查询Handler,实际上就是查询整个请求在服务器接收后到底经历了什么样的一个过程。
-
第三步,处理映射器HandlerMapping将查询好的整个请求执行过程封装好,也就是处理器执行链HandlerExecutionChain,并将其返回给前端控制器DispatcherServlet。
-
第四步,获取了整体执行流程的前端控制器开始准备处理请求业务,他需要访问处理器Handler(可以理解为我们所写的Servlet,@RequestMapping注解下的方法),但是前端控制器所带有的一些变量可能不符合处理器的要求,所以前端控制器向处理器适配器HandlerAdaptor(主要负责将前端控制器的数据规范成处理器能够接受的格式)发送请求,请求执行Handler。
-
第五步,处理器适配器HandlerAdaptor将前端控制器发送的数据规范好,并且拿着规范好的数据向处理器发送请求,请求处理数据。
-
第六步,处理器Handler拿到数据,处理请求,把处理好的请求返回给处理器适配器HandlerAdaptor,处理器适配器将处理好的ModelAndView返回给前端控制器,这里Model包含了Request域,里面有着一系列准备响应给前端页面的数据键值对。View里包含了需要访问的路径资源(也就是ModelAndView里面setViewName设置的值。当我们在编写处理器方法时,没有把ModelAndView作为返回值,而是将String路径作为返回值,这个String其实就是View的名字,SpringMVC底层会将其封装为View对象的名字)。
-
第七步,处理器是配置将处理好的数据ModelAndView返回给前端控制器。
-
第八步,前端控制器将View发送给视图解析器(如果没有这一步,那我们所有存入ModelAndView的name值,或者以String作为返回值的路径名都需要写全称。当我们在开发时,处理非常多的业务都需要访问写在同一个目录下的页面,那么每一处都需要写同样的包名,造成代码冗余。使用视图解析器就可以避免冗余,我们设置文件姓名时只需要将不同的部分设置好,相同的部分经过视图解析器去处理,就可以大片减少代码冗余,提升开发效率)(另一种方式去理解,如果我们每一次设置的ModelAndView里面View都是全路径,以String为返回值的方法返回的是全路径,那经过这一步的时候压根就不需要操作)。
-
第九步,视图解析器ViewResolver将解析好的View返回给前端控制器。
-
第十步,前端控制器将处理好的信息,以及处理好的路径发送给视图页面,并且进行视图渲染。
下面我们来一一解决初用SpringMVC中遇到的问题吧
1)@RequestMapping注解的运用
1.注解源码
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { String name() default ""; @AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {}; }
阅读注解源码(抓重点):
1.Target注解中内容表示他可以作用在类上,也可以作用在方法上
2.Retention注解中内容表述他只在运行时生效
3.注解的path属性和value属性一致,所以path是关键属性,当只输入一个值时可以省略value
4.method属性顾名思义,就是定义可以访问的请求方法属性
5.此外我们还需要了解一下params属性,该属性是一个String类型的数组,用于获取只拥有数组内的请求key值。如果请求中没有这个key值,那么该请求就不会被接收处理。
总结:该注解可以作用于方法上也可以作用于类上,注解常用的有三个值,一个是最重要的path属性,用于设置访问路径,第二个method用于获取指定访问方法的请求,第三个params用于获取含有指定键的请求。
2.注解的使用
@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。
-
path/value:用于指定请求路径。
-
method:用于指定当前请求方式
@RequestMapping(value="/hello",method = RequestMethod.POST)
-
params:用于指定限定请求参数的条件。
@RequestMapping(value="/hello",method = {RequestMethod.GET,RequestMethod.POST},params = {"username"})
-
支持简单的表达式
@RequestMapping(value="/hello",method = {RequestMethod.GET,RequestMethod.POST},params = {"username","age!=20"})
- 如果只处理get请求,那么注解可是使用其衍生注解@GetMapping,同样的,如果只处理post请求,可以使用@PostMapping
2)SpringMVC的数据响应
1.页面的跳转
直接返回字符串:通过将返回的字符串与视图解析器的前缀后缀进行拼接后跳转。
2.请求转发和重定向
@RequestMapping("/m1")
public String method1(){
System.out.println("method1...");
return "redirect:/pages/index.jsp";
}
@RequestMapping("/m2")
public String method2(){
System.out.println("method2...");
return "forward:/pages/index.jsp";
}
转发:可以访问WEB-INF下面的文件资源
重定向:无法访问
3)获取请求参数
1.通过HttpServletRequest获取请求参数
HttpServletRequest作为形参,当DispatcherServlet前端控制器通过IOC容器获取Controller对象调用该方法时,会将request实参传入该方法参数。
@RequestMapping("/test1") public String testParam(HttpServletRequest request){ String username = request.getParameter("username"); String password = request.getParameter("password"); System.out.println("username:"+username+",password:"+password); return "target"; }
2.直接通过参数接收
在DispatcherServlet调用该方法时,通过请求request获取请求的键值对,并且根据该方法参数去创建变量。通过变量的名称去匹配请求参数,如果遇到匹配的就赋值,并且将其作为实参传入该方法。
@RequestMapping("/test2") public String testParam(String username, String password){ System.out.println("username:"+username+",password:"+password); return "target"; }
注意1:如果请求参数为多个同名key的键值对,比如爱好=篮球,爱好=摄影,那么如果参数使用String类型去接收,那么接收到的数据就会以字符串的形式拼接:“篮球,摄影”。如果参数使用String[]数组类型去接收,那么数组中每个值就是请求参数中所对应的值。
注意2:如果方法参数名与实际请求键值对名称不相同不匹配,那么可以对形参使用@RequestParam注解:
value值为该形参用于接收的请求参数名
required设置是否必须传输此请求参数,默认值为true。(如果为true的话那么发送请求时必须有注解中value配置的请求参数,否者会报错)
defaultValue当value所指定的请求参数没有传输或者传输的值为""时,则使用defaultValue配置的值为形参赋值
3.通过实体类参数直接获取请求对象。
@RequestMapping("/method3") public void method14(User user){ System.out.println(user); System.out.println(user.getPassword()); }
public class User { private Integer id; private String username; private String password; @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public User() { } public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } }
注意1:pojo实体类对象中的属性值必须与请求参数中的键值对对应,否则就无法成功赋值
4)ModelAndView对象
1.介绍
ModelAndView对象顾名思义就是Model和View两个对象的结合,既可以携带数据信息,也可以携带视图信息。Model和View两个对象分别对应SpringMVC中的M和V,而在表现层操作的类正好需要使用@Controller标签配置到bean容器中,又对应了C。所以ModelAndView十分的重要。
在开篇的是我我们提到:
Model(模型):通常指的就是我们的数据模型,主要作用用于封装数据,Model一种概念,主要用于向请求域共享数据。
View(视图):通知指的的是.html或者.jsp 应用,作用一般是进行数据展示的。
Controller(控制):通常指应用程序中处理用户交互的部分,作用就是处理程序的逻辑。
所以这里的Model基本就是存储客户端传送过来的信息,并且将其分装到JavaBean对象中,而Model就是一个存储信息的模型。
而View视图,一般就是前端页面应用。他的名字一般就是html或者jsp文件的名称(不加后缀)。此前以String作为返回值返回的内容,SpringMVC底层都将其封装为View对象的名称。
2.基本使用
@RequestMapping("/m4") public ModelAndView method4() { System.out.println("视图解析器"); //创建ModelAndView对象 ModelAndView modelAndView = new ModelAndView(); //设置View的名称(也就是之前String类型的返回值) modelAndView.setViewName("m4"); //设置请求键值对,类似于request.setAttribute(key,value); //这里model起作用,将键值对添加到request域中 modelAndView.addObject("username", "zhangsan"); //将带有信息的ModelAndView返回 return modelAndView; }
此时model将键值对username=zhangsan添加到了request域中,而前台是可以通过request获取到该数据。
3.不返回modelandview,如何将处理后的值传送到前台页面
第一种,使用Model参数存值。
方法的参数中设置Model对象,当服务器调用该方法时会从容器中拿到springmvc创建好的该类型对象,用于存储该值。此时request中就存有了键值对。
@RequestMapping("/m7") public String method7(Model model){ System.out.println("m7"); model.addAttribute("username","zhangsan"); return "index"; }
第二种,使用HttpServletRequest参数存值
@RequestMapping("/m8") public String method8(HttpServletRequest request){ System.out.println("m8"); request.setAttribute("username","zhangsan"); return "index"; }
5)域对象共享数据
1.使用HttpServletRequest域对象共享数据
@RequestMapping("/m16") public String method16(HttpServletRequest request){ request.setAttribute("username","zhangsan"); request.setAttribute("id",1); return "target"; }
2.使用ModelAndView向request中添加共享数据
此处与上文我们所举的ModelAndView基本使用时的例子是基本一样的。
@RequestMapping("/userModelAndView") public ModelAndView userModelAndView(ModelAndView modelAndView){ System.out.println("userModelAndView"); modelAndView.setViewName("index"); modelAndView.addObject("username","zhangsan"); return modelAndView; }
3.使用Model向request域对象共享数据
@RequestMapping("/userModel") public String userModel(Model model){ System.out.println("userModel"); model.addAttribute("username","zhangsan"); return "index"; }
4.使用map向request域对象共享数据
@RequestMapping("/userMap") public String userMap(Map<String,Object> map){ map.put("username","zhangsan"); return "target"; }
5.使用ModelMap向request域对象共享数据
@RequestMapping("/m16") public String method16(HttpServletRequest request){ request.setAttribute("username","zhangsan"); request.setAttribute("id",1); return "target"; }
Model、ModelMap、Map三个形参其实在DispatcherServlet调用时都传入了BindingAwareModelMap类型的对象,BindingAwareModelMap与这三个类有着继承和实现的关系,最终的传参也是根据多态的原理进行参数传递。
6.向session域共享数据
@RequestMapping("/useSession") public String useSession(HttpSession session){ session.setAttribute("username","zhangsan"); return "target"; }
7.向Application域共享数据
@RequestMapping("/useApplication") public String useApplication(HttpSession session){ ServletContext servletContext = session.getServletContext(); servletContext.setAttribute("username","zhangsan"); return "target"; }