Java0基础_day24_SpringMVC框架_PART1 - Cache One

标签:SpringMVC、SSM、拦截器、代理对象

  1.SpringMVC概述

  • 是什么?

为了让web开发更简单、更高效、便于维护升级,引入了SpringMVC。

从名字的角度看,SpringMVC是Spring框架的一个子模块,可以理解为Spring+MVC,这里的MVC是Model、View、Controller是SpringMVC三个核心概念对象的缩写。

从底层实现来看,SpringMVC可以看成是Servlet的高级版本,是加上一些其他功能的框架而已。

 从容器的角度看,Spring是一个容器,通过ioc控制管理对象,因此SpringMVC也是一个容器,不过存放的是控制器对象@Controller

  • 核心概念?

在Springmvc中,有三个核心概念,如上面的图所示:

第一个:Controller控制器

该控制器分为用于调度的中央(前端)控制器和用于响应的后台@Controller对象

第二个:Model数据

Model数据是后台数据调用Service方法得到的数据,将其放在Springmvc容器中。

第三个:View视图

View是视图,用于页面转发等功能。


2.SpringMVC配置初探

第一步:创建maven-web项目,加入Spring-mvc依赖,servlet依赖,jsp依赖

第二步:web.xml注册中央控制器,设置为初始时加载设置;

中央控制器控制范围→servlet mapping、重设springmvc配置文件路径乱码过滤器。

   <servlet>
        <servlet-name>Spring_mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--表示tomcat服务器启动时就创建该对象,数字越低优先级越高-->
        <load-on-startup>1</load-on-startup>
<!--等价于:
webapplicationcontext ctx = new  classpathxmlapplicationcontext(“springmvc.xml”)
getServletContext().setAttribute(key,ctx)
容器底部可以看成是map.put("Mycontroller对象",mycontroller)-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:Spring_mvc.xml</param-value>
        </init-param>
    </servlet>
<!--    有两种映射方式,第一种:常规的通过路径,第二种,通过通配符-->
    <servlet-mapping>
        <servlet-name>Spring_mvc</servlet-name>
        <url-pattern>/</url-pattern>
           <!--<url-pattern>*.dO</url-pattern>-->
    </servlet-mapping>


  <filter>
        <filter-name>中文乱码过滤器</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>
        <init-param>
<!--强制HttpServletRequest请求对象使用encoding编码-->
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
<!--强制HttpServletResponse应答对象使用encoding编码-->
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>中文乱码过滤器</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

【注意】在post方法中可能会出现乱码情况,此时需要配置上面的过滤器。

第三步:写处理方法,类头加上@Controller注解,方法加上@RequestMapping地址地址映射;

@Controller
public class mycontrol {
    @RequestMapping("/some.do")
    public ModelAndView dosome(String name,int age)  {
        System.out.println("mycontroller的dosome方法执行了");
        ModelAndView mv = new ModelAndView();

        mv.addObject("myname",name);
//添加数据,框架在最后把该数据放到request作用域:
        mv.addObject("myage",age);
        mv.setViewName("show");//转发
        return mv;
    }
}

第五步:配置Springmvc配置文件,定义要创建哪些对象,配置拦截器、视图解析器。

<context:component-scan base-package="com.zhuge.control"/>
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="prefix" value="/WEB-INF/view/"/>
                <property name="suffix" value=".jsp"/>
        </bean>
       <mvc:interceptors>
               <mvc:interceptor>
                        <mvc:mapping path="/**"/>
                       <bean class="com.zhuge.handler.myintercept"/>
               </mvc:interceptor>
       </mvc:interceptors>

第六步:创建结果页面,如show.jsp,此时springmvc自动转发到该页面。

第七步:配置Tomcat服务器,启动。

 【注意1】中央控制器即dispatcherservlet,又名前端控制器,其本质是一个Servlet,从源代码看,其父类继承自HttpServlet。

【注意2】如果不配置,dispatcherservlet会在[/WEB-INF/Spring_mvc-servlet.xml]目录下寻找对应的XX.xml配置文件,因此一般情况下会自己手动重新设置。

【注意3】dispatcherservlet设置开机启动,因为在他启动时会扫描对应包然后创建controller对象,放在Springmvc容器中以便使用,以此减少后续访问的响应时间,从源代码看,其会调用init()方法读取配置文件然后获得WebApplicationContext容器,将对象生成放到ServletContext中。

那么你可能已经猜到从浏览器输入http://localhost:8080/myweb/index.jsp,然后点击超链接假设是http://localhost:8080/myweb/some.do,然后会发生什么?

首先,此时springmvc容器已经有中央控制器对象和一个controller对象,然后当遇到该请求时,中央控制器查找能处理/some.do的对象和方法【内部本质是中央处理器调用其doDispatch方法执行目的方法,类似于Servlet的service方法】,这里是mycontrol的dosome方法,然后进行执行该方法,返回ModelAndView,经过视图解析最后返回给用户。


3.SpringMVC间接访问设置_视图

从上面的实验,我们不难发现,直接通过http://localhost:8080/myweb/show.jsp也能直接访问到我们系统中的资源,显然这是不合理的,因为有可能我们不想该资源直接给用户,要对其进行控制。

因此,如何设置只通过controller对象访问呢?

值得注意的是,有一个事实是,位于webapp→WEB-INF下的文件天然不能通过上述方法访问。

考虑到有可能多个资源,故可以在webapp/WEB-INF新建view文件夹,放入show.jsp、other.jsp等视图页面【此时springmvc配置文件路径也需要修改】。

3.1 视图

3.1.1 视图解析器

就是视图文件的路径,如这里的show.jsp完整路径为webapp/WEB-INF/view/show.jsp,在配置之后,我们在controller对象中只需使用逻辑名show即可,不用写全名了,其背后就是简单的字符串拼接操作。

  mv.setViewName("show");
 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="prefix" value="/WEB-INF/view/"/>
                <property name="suffix" value=".jsp"/>
</bean>

3.1.2 框架的setViewName方法

当调用setViewName方法时,等价于以前的手动转发操作:

request.getrequestdispatcher("/show.jsp").forward(...)

3.1.3 设置视图的两种方法

法1:setViewName方法→转换为视图

mv.setViewName("show");

法2:直接调用set方法→新建视图

 mv.setView(new RedirectView("/a.jsp"));

4.处理器方法_@RequestMapping_方法参数

这里讲解一下后端控制器的方法相关参数、返回值。

@Controller
@RequestMapping(value = "/test")
public class mycontrol {
    @RequestMapping(value = "/some.do")//类似于Servlet.doGet()
//@RequestMapping(value = {"/other.do","/zhngsan.do"})
    public ModelAndView dosome(){//service请求
        ModelAndView mv = new ModelAndView();
//等价于request.setattribute(msg,欢迎来到北京大学)

        mv.addObject("msg","欢迎来到北京大学");
        mv.addObject("fun","执行了dosome方法");
        mv.setViewName("show");
      
        return mv;
    }

 }

第一步:类头加@Controller,表明我是来处理用户请求的控制器对象;

第二步:类头加上模块名,表明我这个对象中的方法都是处理/test开头的uri的;

第三步:dosome方法头加上地址名,表明dosome是处理网站根地址/test/some.do的uri的;

注意的是,这里的value值是一个字符串数组,可以处理多个uri;

第四步:写法

  • 确定返回值类型和方法参数
    • 值得注意的是,方法头requestmapping有一个method属性,为枚举值,表示可以接收的方法:
      • method = RequestMethod.POST
      • 缺省的话两个方法都支持,上述显式请求则只会允许post请求;
  • 执行service业务方法
  • 返回

4.1 方法参数_逐个接收

方法参数可以认为是3+1,3个自带参数,一个用户提交的参数:

HttpServletRequest request,HttpServletResponse response,HttpSession session,用户提交参数

【注意】以上四种参数可以在方法中随意混用。

4.1.1 同名赋值

假设用户点击如下的连接,那么怎么获取zhangsan和18的值呢?

               http://localhost:8080/myweb/dome.do?name=zhangsan&age=18

回想以前写Servlet,我们通过request.getparametername("name"),因此在springmvc框架中,也有对应的方法:

@RequestMapping(value = "/received.do",method = RequestMethod.GET)
public ModelAndView dosome(String name,int age){//service请求


//index.jsp
    <form action="received.do" method="post">
        姓名:<input type="text" name="name"><BR/>
        年纪:<input type="text" name="age"><BR/>
        <input type="submit" value="点我提交参数">
    </form>

不难发现,在处理器方法中,我们引入与发过来的请求的同名参数,通过这样的方式就可以一步到位获取对应的参数值;

其本质:springmvc框架通过DispatcherServlet调用mycontrol对象的dosome方法,调用时,按参数名称对应赋值,其中涉及到了自动转换功能!        

public ModelAndView dosome(String name,int age){//service请求
//等价于:
String name = request.getParameter("name");
String age= request.getParameter("age");

你可能注意到这里转换的age是String类型,需要的是int类型:

String name = request.getParameter("name");
int age= Integer.valueof(request.getParameter("age"));

那么当用户输入的age一栏参数为空时,由于是int,显然无法赋值,发生空指针异常,那么怎么解决呢?

可以把它修改为Integer类型,当然也可以使用String,不过在实际开发中,我们要考虑到应该按照逻辑尽早暴露错误,而不是掩盖错误。

public ModelAndView dosome(String name,Integer age){//service请求

4.1.2 别名赋值

如果index.jsp的参数为rname,这里方法的形参为name,怎么映射呢?

public ModelAndView dosome(@RequestParam(value = "rname") String name,
 @RequestParam(value = "rage") int age){//service请求
    ModelAndView mv = new ModelAndView();

    mv.addObject("myname",name);
    mv.addObject("myage","age");
    mv.setViewName("show");
    return mv;
}

可以利用注解重命名参数,完成映射:

关于@RequestParam
* 属性1:value 表示请求中的参数名
* 属性2:required  ,默认取true表示本次请求中必须包含参数,否则报错。
  * 如果要求请求必须包括请求的参数,选true,否则选false,此时复制URL地址到浏览器也可以访问

4.2 方法参数_接收对象

顾名思义,该设置即把一个对象传入到控制器方法作为形参,在这个情况下,有如下要求:

第一:该对象的属性名必须和前台的形参名一致;

第二:此时RequestParam不能用在这里。

此时,框架会通过无参构造方法创建该对象,然后利用属性的setter方法进行赋值,实现set注入。

@RequestMapping(value = "/objectreceived.do")
    public ModelAndView receiveobjec(Student stu){
        System.out.println("getname:"+stu.getName()+"---getage:"+stu.getAge());
        ModelAndView mv = new ModelAndView();

        mv.addObject("myname",stu.getName());
        mv.addObject("myage",stu.getAge());
        mv.addObject("mystu",stu);
        mv.setViewName("show");
        return mv;
    }
public class Student {
    //要求这个名字和请求中的参数名一样
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public Student() {
        System.out.println("我时student的无参构造方法");
    }

    public void setName(String name) {
        System.out.println("我时setname"+name);
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println("setage"+age);
        this.age = age;
    }
}

【注意】该方法可以接收多个对象;

【注意2】该方法非常适合形参个数很多的情况。


5.处理器方法_返回值类型

5.1  ModelAndView

其中model表示数据,而view表示视图,适用于需要返回数据和视图页面的情况。

5.2 String视图

该方法用于仅返回视图,不返回数据:

@RequestMapping(value = "/returnString.do")
public String doreturn @RequestParam(value = "rname") String name,
                       @RequestParam(value = "rage") Integer age){//service请求
    System.out.println("名字是"+name+"age="+age);

    return "show";
}

   值得注意的是,此时show.jsp页面不能使用该方法的形参,因为不返回数据,那么如果想要返回视图,同时引用数据,还要在String方法的前提下,该怎么处理呢?

其实,前面已经讲到,我们的model的本质就是一句话:

mv.addObject("myname",stu.getName());

上述本质是框架将设置的key-value加入到request的作用域中,等价于:

 request.setAttribute("myname",stu.getName());

因此,在不使用model的语法情况下,我们可以直接手动添加数据到request作用域:

request.setAttribute("myname",name);
request.setAttribute("myage",age);

这样,在show.jsp即可引用:

<body>
    <h3>show jsp</h3><br/>
    <h3>myname数据:${myname}</h3>
    <h3>myage数据:${myage}</h3>
</body>

5.3 String对象

如果没有加上@ResponseBody注解,默认是视图,否则是String数据。

@RequestMapping(value = "/returnString.do")
@ResponseBody
    public String doreturn(HttpServletRequest request, @RequestParam(value = "rname") String name,
                           @RequestParam(value = "rage") Integer age){//service请求
        System.out.println("名字是"+name+"age="+age);
        request.setAttribute("myname",name);
        request.setAttribute("myage",age);
        return "你好,我是大白";
    }

【注意】此时如果前端发来的ajax需求中dataType是json,而这里显然是String类型,无法转换,怎么处理矛盾呢?

首先明确一点,即如果dataType没有指明,那么默认String返回的是text/Content-Type: text/plain;charset=ISO-8859-1,为了使用utf-8解决乱码问题,方法如下:

@RequestMapping(value = "/returndataString.do",produces = "text/plain;charset=utf-8")
@ResponseBody
public String doreturndataString(String name, Integer age){//service请求
    return "hellowfsadf发送到发送方三房";
}

即使用produces属性即可。

【问题】不是说可以用过滤器吗?不难证明,此时不经过过滤器,返回到ajax!

5.4void返回值

该返回值既无数据,也没有视图,那么有什么用呢?

                        一般多用于处理ajax的请求,通过response输出请求。       

 @RequestMapping(value = "/returnvoid-ajax.do")
    public void doretunajax(HttpServletResponse response,String name,int age){
        //service请求
        System.out.println("名字是"+name+"age="+age);
        //处理ajax,做json的数据格式
        //service调用完成,使用student表示处理结果
        Student stu = new Student("李白",99);
        //转化为json
        String json = "";
        if(stu!=null){
            ObjectMapper om = new ObjectMapper();
                json = om.writeValueAsString(stu);
                System.out.println("studnet的json为"+ json);
                //输出数据,响应ajax请求:
                //告诉浏览器,我发回来的正好是json格式。
                response.setContentType("application/json;charset=utf-8");
                PrintWriter pw = null;
                pw = response.getWriter();
                pw.println(json);
                pw.flush();
                pw.close();  
        }

    }

总的来说,ajax请求后端可以总计为三步走:

调用service方法→包装java对象为json格式→输出json

不难发现,这三步第二步和第三步是重复的,因此可以让框架去实现,即下文的返回Object对象。

5.5 Object返回值

5.5.1 步骤讲述

不难发现,此时返回的是对象,即数据,而不是视图,因此非常适合ajax请求。

因此对应上面的ajax三步走,引入了springmvc框架后的ajax后台步骤如下:

第一步:加入处理json的依赖,即jackson

第二步:告诉springmvc配置文件,我要转换→引入mvc驱动,将java对象转换为json、xml、text等二进制数据;

   <mvc:annotation-driven></mvc:annotation-driven>

【注意】等价于 json = om.writeValueAsString(stu);

【注意2】在引入驱动后,springmvc会自动创建HttpMessageConveter接口的七个实现类对象,例如MappingJackson2HttpMessageConveter,用jackson工具库的getmapper方法包装。

【注意3】其本质涉及canWrite()和Write()方法,canWrite()判断能否将java对象转化为所需的json等格式,后者用参数MediaType表示,如果转换,最后再使用Write方法转换,将其转化为json字符串!

第三步:处理器方法加上@ResponseBody注解

【注意】等价于如下代码:

 response.setContentType("application/json;charset=utf-8");
                PrintWriter pw = null;
                pw = response.getWriter();
                pw.println(json);
                pw.flush();
                pw.close();  

【注意2】本质上,是通过HttpServletResponse输出数据,然后响应ajax请求。

其实,可以证明,加入注解驱动后,状态变化如下:

5.5.2  流程总结

第一步:框架会把返回值Student,调用框架中arrayList<httpmessageconverter>中每个类的canwwire方法,检查哪个实现类能够处理Student类型的数据,发现-MappingJackson2HttpMessageconverter可以将对象转化为json。

第二步:框架会调用实现类的write方法,MappingJackson2HttpMessageconverter的write方法,把王五同学的student对象转化为json,调用jackson的objectmapper实现转化为json

第三步:框架会调用@ResponseBody把第二步结果的输出到浏览器,ajax数据请求处理完毕;

由此可见,对于String对象的返回过程,大家应该也明白了,只需把对应的实现类改为StringHttpMessageConverter。

为您推荐