SpringMVC学习(具体实现+底层原理)

8 篇文章 0 订阅
6 篇文章 0 订阅

引人: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、具体实现流程

在这里插入图片描述

  1. 所有请求经过DispatcherServlet,交给他去调度
  2. 通过处理器映射器通过请求与Controller组件的映射关系(Bean或者Controller组件注解)定位到具体Controller
  3. 返回到前端控制器,然后前端控制器通过处理器适配器找到Controller
  4. Controller去调用业务层返回ModelAndView
  5. ModelAndView通过前端控制器交给视图解析器解析
  6. 通过解析返回相应的视图

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异常

  1. Artifacts没有lib库,需要手动添加
  2. 视图解析器前缀后缀错误

三、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风格的优点

  1. 参数名称不会直接暴露在地址栏中
  2. 简洁高效

3、相同url怎么控制冲突

相同的url可以通过更改请求类型来访问,get请求只能访问Get,post请求只能访问post

  1. 使用RequestMapping的method参数设置请求类型
  2. 直接使用相应类型的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、有视图解析器

  1. 视图解析器默认转发
  2. 转发写出forword后,就不会拼接了,需要直接写完整
  3. 重定向需要写出完整地址,而且WEB-INF访问不到,不能重定向进去
WEB-INF是WEB应用的安全目录,只允许服务端访问,不允许客户端访问(只能转发,不能重定向)
重定向就是服务端确定url后,将url交给客户端,客户端再直接访问,所有不能重定向到WEB-INF
  1. 携带数据用转发,重定向如果需要携带数据需要特殊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、不用视图解析器

  1. 转发和重定向都要写出地址
  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>

在这里插入图片描述

  1. Json是个有多个键值对的字符串
  2. 输出对象不能拼接字符串,如果拼接字符串会显示Object

2、后端

  1. @RestController:在Controller上声明,类中所有方法不会返回页面,只会返回值

  2. @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;
    }

十、前后端分离

  1. 后端统一使用RestController返回Json
  2. 尽量不要使用Controller默认的Json,使用专门Jar包更好(JackSon、FastJson等)
  3. 页面跳转交给前端路由
  4. 前端通过Ajax拿数据
  5. 前后端分离跨域问题需要解决

十一、拦截器(Intercepter)

拦截器是AOP的具体实现

拦截器是MVC自带的,只拦截controller请求
过滤器(Filter)是Web层的,url-pattern中配置了/*之后可以过滤所有

1、具体实现

  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进行拦截,其他两个一般是做日志

  1. 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";
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值