文章目录
引人:MVC架构:
- M:model(数据层)
- V:view(视图层)
- C:Controller 数据层和视图层通过控制层(接口)互相调用
一、依赖
spring-web和spring-webmvc里的web有点不一样,所以mvc必须导spring-webmvc
通过properties控制版本,方便改版本
<properties>
<spring-version>5.3.6</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-expression -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring-version}</version>
</dependency>
<!--切面-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
二、具体配置(原理实现)
1、具体实现流程
- 所有请求经过DispatcherServlet,交给他去调度
- 通过处理器映射器通过请求与Controller组件的映射关系(Bean或者Controller组件注解)定位到具体Controller
- 返回到前端控制器,然后前端控制器通过处理器适配器找到Controller
- Controller去调用业务层返回ModelAndView
- ModelAndView通过前端控制器交给视图解析器解析
- 通过解析返回相应的视图
2、web.xml配置前端控制器
<!--配置前端控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--绑定mvc配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--优先级-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都要经过前端控制器-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3、配置mvc
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器:DispatcherServlet给他的ModelAndView-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--注册controller-->
<bean id="/hello" class="com.chime.controller.HelloController"/>
4、Controller
public class HelloController implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mv=new ModelAndView();
//业务代码
String result="Hello MVC";
mv.addObject("msg",result);
mv.setViewName("hello");
return mv;
}
}
5、404异常
- Artifacts没有lib库,需要手动添加
- 视图解析器前缀后缀错误
三、mvc注解实现
1、web.xml配置前端控制器
<!--注册前端控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--绑定配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求经过springmvc-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2、配置mvc
开启注解扫描需要添加context命名空间
注册mvc需要添加mvc命名空间
<!--开启注解扫描-->
<context:component-scan base-package="com.chime"/>
<!--通过前缀注册mvc注解,自动生成HandlerMapping和HandlerAdapter-->
<mvc:annotation-driven/>
<!--过滤静态资源-->
<mvc:default-servlet-handler/>
<!--注册视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
annotation-driven用于注册注解驱动。
一般根据前缀来注册相关的注解类
<tx:annotation-driven/>:支持事务注解的(@Transactional)
<mvc:annotation-driven/>:支持MVC注解
3、Controller
@Controller
public class HelloController {
//映射地址
@RequestMapping("/hello")
public String hello(Model model){
// 封装数据
model.addAttribute("msg","Hello");
return "hello";//交给视图解析器解析
}
}
4、使用bean注册Controller的缺点
实现Controller的类只有一个实现方法,需要一个个配(一个Bean对应一个类)
而使用Controller注解,可以写多个方法,加上Mapping来映射
四、Restful风格
1、Restful和传统传参的区别
localhost:8080/login?username=qwe&password=123
localhost:8080/login/qwe/123
localhost:8080/add?a=3&b=4
localhost:8080/add/3/4
url的Restful风格传参,需要使用@PathVariable注解声明路径变量
@RequestMapping("/add/{a}/{b}")
public String test1(@PathVariable int a,@PathVariable String b, Model model){
String result=a+b;
model.addAttribute("msg","test1"+result);
return "hello";
}
2、Restful风格的优点
- 参数名称不会直接暴露在地址栏中
- 简洁高效
3、相同url怎么控制冲突
相同的url可以通过更改请求类型来访问,get请求只能访问Get,post请求只能访问post
- 使用RequestMapping的method参数设置请求类型
- 直接使用相应类型的Mapping
@RequestMapping(value= "/add/{a}/{b}",method = RequestMethod.GET)
@GetMapping("/add/{a}/{b}")
//@RequestMapping(value= "/add/{a}/{b}",method = RequestMethod.GET)
@GetMapping("/add/{a}/{b}")
public String test1(@PathVariable int a,@PathVariable String b, Model model){
String result=a+b;
model.addAttribute("msg","test1"+result);
return "hello";
}
//@RequestMapping(value= "/add/{a}/{b}",method = RequestMethod.POST)
@PostMapping("/add/{a}/{b}")
public String test2(@PathVariable int a,@PathVariable String b, Model model){
String result=a+b;
model.addAttribute("msg","test2 "+result);
return "hello";
}
五、转发和重定向
1、有视图解析器
- 视图解析器默认转发
- 转发写出forword后,就不会拼接了,需要直接写完整
- 重定向需要写出完整地址,而且WEB-INF访问不到,不能重定向进去
WEB-INF是WEB应用的安全目录,只允许服务端访问,不允许客户端访问(只能转发,不能重定向)
重定向就是服务端确定url后,将url交给客户端,客户端再直接访问,所有不能重定向到WEB-INF
- 携带数据用转发,重定向如果需要携带数据需要特殊jar包
@RequestMapping("/test3")
public String test3(Model model){
model.addAttribute("msg","转发");
//return "forward:hello.jsp";
return "hello";
}
@RequestMapping("/test4")
public String test4(Model model){
model.addAttribute("msg","重定向");
return "redirect:/test.jsp";
}
2、不用视图解析器
- 转发和重定向都要写出地址
- 转发可以访问WEB-INF
@RequestMapping("/test3")
public String test3(Model model){
model.addAttribute("msg","转发");
return "forward:/WEB-INF/jsp/hello.jsp";
}
@RequestMapping("/test4")
public String test4(Model model){
model.addAttribute("msg","重定向");
return "redirect:/index.jsp";
}
注意:具体页面可以直接return和转发重定向具体位置 但是非页面的请求必须得使用重定向和转发声明不经过视图解析器
六、Controller方法参数
1、普通类型参数
可以使用@RequestParam设置参数别名
localhost:8080/test5?username=qwe
qwe就被name接收了
@RequestMapping("/test5")
public String test5(@RequestParam("username") String name,Model model){
model.addAttribute("username",name);
return "hello";
}
2、对象参数
前端根据参数名自动匹配对象的属性
http://localhost:8080/anno/test6?id=5&sex=男
没有name字段,则person对象的name属性为空
@RequestMapping("/test6")
public String test6(Person person, Model model){
System.out.println(person);
return "hello";
}
七、前台乱码
web.xml里注册格式过滤器
<!--字符格式过滤器-->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
八、Json
1、前端
<script>
var user={
id : "101",
name: "张三",
sex : "男"
};
console.log("对象:"+user);//会输出Object,不能显示属性
console.log(user);
var json = JSON.stringify(user);
console.log("Json:"+json);
var obj = JSON.parse(json);
console.log("Obj:"+obj);
console.log(obj);
</script>
- Json是个有多个键值对的字符串
- 输出对象不能拼接字符串,如果拼接字符串会显示Object
2、后端
-
@RestController:在Controller上声明,类中所有方法不会返回页面,只会返回值
-
@ResponseBody:在Controller方法上上面,该方法不会返回页面,只会返回值
一般使用obj.toString()或者json转换包来返回json数据
@RequestMapping("json1")
// @ResponseBody
public String json1(){
return "json1";
}
@RequestMapping("/json2")
public String json2(){
Person person=new Person("101","张三","男");
return person.toString();
}
3、使用Jackson返回Json
(1)添加Jackson依赖,Spring4.0版本有点变化
(2)在Artifacts添加lib
(2)代码实现
@RequestMapping("/json3")
public String json3(){
ObjectMapper mapper=new ObjectMapper();
Person person=new Person("103","李四","男");
try {
String json=mapper.writeValueAsString(person);
return json;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@RequestMapping("/json4")
public String json4(){
ObjectMapper mapper = new ObjectMapper();
List<Person> persons=new ArrayList<>();
persons.add(new Person("101","张三","男"));
persons.add(new Person("102","李四","女"));
persons.add(new Person("103","王五","男"));
try {
String json = mapper.writeValueAsString(persons);
return json;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
(3)JsonUtils
public class JsonUtils {
//如果object是date类型,则格式化 如果是其他类型则返回普通Json
public static String getJson(Object object){
return getJson(object,"yyyy-MM-dd HH:mm:ss");
}
public static String getJson(Object object,String format){
try {
SimpleDateFormat dateFormat=new SimpleDateFormat(format);
ObjectMapper mapper=new ObjectMapper();
mapper.setDateFormat(dateFormat);//修改Json的时间戳格式
String json = mapper.writeValueAsString(object);
return json;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
@Test
public void test1(){
Date date=new Date();
String format="yyyy-MM-dd HH:mm:ss";
String json = JsonUtils.getJson(date, format);
System.out.println(json);
List<Person> persons=new ArrayList<>();
persons.add(new Person("101","张三","男"));
persons.add(new Person("102","李四","女"));
persons.add(new Person("103","王五","男"));
String json1 = JsonUtils.getJson(persons);
System.out.println(json1);
}
"2021-05-23 10:41:40"
[{"id":"101","name":"张三","sex":"男"},
{"id":"102","name":"李四","sex":"女"},
{"id":"103","name":"王五","sex":"男"}]
4、乱码:配置Json格式
(1)注解
@RequestMapping(value="/json2",produces = "application/json;character=utf-8")
(2)配置文件
可能会标签报错,因为从网上拷贝下来的代码,直接的空格,字符可能是中文的,需要删除空格然后重新对齐
<mvc:annotation-driven>
是根据mvc注册注解功能的标签,自动生成处理器映射器和处理器适配器
错误配置
spring4.0版本之前可以这么配置
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
正确配置
spring4.0之后后面那个bean配置会报错
<!--<mvc:annotation-driven/>-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<!--<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">-->
<!--<property name="objectMapper">-->
<!--<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">-->
<!--<property name="failOnEmptyBeans" value="false"/>-->
<!--</bean>-->
<!--</property>-->
<!--</bean>-->
</mvc:message-converters>
</mvc:annotation-driven>
九、Ajax
<html>
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/static/js/jquery-3.3.1.js"></script>
<script>
$(function () {
$("#in").click(function at123() {
$.ajax({
url: "/ajax",
success: function (data) {
//alert(data)
var html="";
//遍历插入行
for(let i=0;i<data.length;i++) {
html+="<tr>" +
"<td>" + data[i].id + "</td>" +
"<td>" + data[i].name + "</td>" +
"<td>" + data[i].sex + "</td>" +
"</tr>";
}
$("#bod").html(html)
}
})
})
})
</script>
</head>
<body>
<button id="in">按钮</button>
<table>
<thead>
<th>学号</th>
<th>姓名</th>
<th>性别</th>
</thead>
<tbody id="bod">
</tbody>
</table>
</body>
</html>
RequestMapping("/ajax")
public List<Person> ajax(Person person, HttpServletResponse response){
// response.setHeader("Access-Control-Allow-Origin", "*");
List<Person> persons=new ArrayList<>();
persons.add(new Person(1,"张三","男"));
persons.add(new Person(2,"李四","女"));
persons.add(new Person(3,"王五","男"));
response.setHeader("Access-Control-Allow-Origin-Patterns", "*");
return persons;
}
十、前后端分离
- 后端统一使用RestController返回Json
- 尽量不要使用Controller默认的Json,使用专门Jar包更好(JackSon、FastJson等)
- 页面跳转交给前端路由
- 前端通过Ajax拿数据
- 前后端分离跨域问题需要解决
十一、拦截器(Intercepter)
拦截器是AOP的具体实现
拦截器是MVC自带的,只拦截controller请求
过滤器(Filter)是Web层的,url-pattern中配置了/*之后可以过滤所有
1、具体实现
- 通过实现HandlerInterceptor配置拦截器
public class MyIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行前");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("执行后");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("清理回收");
}
}
一般情况只写preHandle进行拦截,其他两个一般是做日志
- spring-mvc.xml配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--包括请求下的所有请求-->
<mvc:mapping path="/**"/>
<bean class="com.chime.intercepter.MyIntercepter"/>
</mvc:interceptor>
</mvc:interceptors>
执行前
执行了ControllerTest1
执行后
清理回收
2、登录拦截
public class LoginIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
if(requestURI.contains("login")){//路径含有login的放行
return true;
}
if(request.getSession().getAttribute("token")!=null){//有登录令牌的放行
return true;
}
//转发到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
return false;
}
}
@Controller
public class UserController {
@RequestMapping("/tologin")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, HttpSession session, Model model){
//账户密码正确session添加登录令牌,没有令牌跳到toMain会被拦截
if(username.equals("qwe")&&password.equals("123")){
session.setAttribute("token","1");
}
// 不要直接return视图,拦截器只会拦截controller
return "redirect:/toMain";
}
@RequestMapping("/toMain")
public String toMain(){
return "main";
}
@RequestMapping("/logout")
public String logout(HttpSession session){
//删除令牌
session.removeAttribute("token");
return "redirect:/toLogin";
}
}