Java0基础_day24_SpringMVC框架_PART2 - Cache One

 6.静态资源访问问题

回顾之前Tomcat的实验中,我们是否可以访问如下的连接呢?

http://localhost:8080/URL/index.jsp

http://localhost:8080/URL/js/jquery-3.5.1.js

http://localhost:8080/URL/html/3.html

http://localhost:8080/URL/some.do

其中除了最后一个,其他都是静态资源,显然不需要springmvc我们也可以使用tomcat进行访问,那么我们知道进行访问必须得有servlet吧?不然怎么接收、返回数据呢?

YOU GOT IT!

查看Tomcat的配置文件,便能发现,它里面有一个默认实现的servlet,在Tomcat服务器启动时启动,用于处理静态资源:

<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>fileEncoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>


 <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

不难发现,静态资源和所有的未指明的映射请求都是由该servlet进行处理的!

而在引入springmvc后,第四个链接如前面指明的一样,会通过中央处理器进行处理,技术由Springmvc容器进行处理,中央处理器找到对应的映射controller对象进行处理,而自身并没有处理能力!

那么问题来了,如果在web.xml中手动指定如下映射,那么会怎么样?

  <servlet-mapping>
        <servlet-name>Spring_mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

显然,可以猜出的是,会覆盖Tomcat自带的/映射,即现在所有的请求都经过中央处理器处理,那么现在能处理some.do吗?

当然可以,因为定义了对应的controller对象,那么可以处理静态资源吗?

经过实验,发现404,关键就在于中央处理器找不到可以处理静态资源的servlet,那么怎么办?

方法1:【基于转发】

在该方法中,我们在Springmvc中加入一个标签:

<mvc:default-servlet-handler></mvc:default-servlet-handler>

但是你会发现,现在静态资源ok了,但是servlet动态访问又毙掉了,怎么办?

此时可以把驱动注册打开:

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

【总结】通过引入驱动和自带的handler,此时让静态资源请求还是给Tomcat处理,动态资源还是交由中央调度器处理,你可以看到,该方法要求服务器具有处理静态资源的能力。

方法2:【resource标签】

可以借鉴上述思想,将动态资源交给自己【驱动注册】,静态资源也交给自己【resource标签】:

<mvc:resources mapping="image/**" location="/static/image/"/>
<mvc:resources mapping="html/**" location="/static/html/"/>
<mvc:resources mapping="/js/**" location="/static/js/"/>
<!--<mvc:resources mapping="/static/**" location="/static/"/>-->
<mvc:annotation-driven/>

关于resources标签,便能发现,mapping参数表示uri地址,location表示物理地址;

不过在实际开发中,我们会咋webapp下新建一个static文件夹,放入所有各类的静态资源,即上述代码注释的一行。

7.相对路径与绝对路径_EL表达式

结论:在带有协议的地址中,我们使用绝对地址;在其他地方,如jsp,html使用相对地址,相对地址,顾名思义有参考位置,一般是一个绝对地址。

7.1 相对路径问题

下面思考一个问题:

http://localhost:8080/URL/index.jsp 中有一个表单,action属性为:user/some.do,那么点击后地址是多少?

第一次:获取参照地址:http://localhost:8080/URL/user/

加上相对位置后:http://localhost:8080/URL/user/some.do

第二次:获取参照地址:http://localhost:8080/URL/user/

加上相对位置后:http://localhost:8080/URL/user/user/some.do

显然不符合预期,根本原因就是参照地址变化了,因此有哪些修改方法呢?

法1:引入不变的参照地址,即基地址:

<%
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>功能入口</title>
    <base href="<%=basePath%>">
<!--等价于<base href="http://localhost:8080/URL/">-->
</head>

【注意】该方法要求每个文件都要进行这样的配置,较为繁琐!

法2:在要使用地址的前面引入EL表达式

<a href = "${pageContext.request.contextPath}/user/some.do">点我查询</a>

7.2 绝对路径解析

http://localhost:8080/URL/index.jsp,点击/user/some.do那么访问的是什么地址呢?

不难发现,这是一个绝对地址,因此相对于根地址:http://localhost:8080

故:http://localhost:8080/user/some.do

你会发现,非常不方便!


8.SSM整合开发流程

8.1 整合思路

前面讲到三层架构,分别是用户层、业务层和持久化层,对应的是SpringMvc、Spring、mybatis三大框架,Springmvc负责接收用户请求、返回视图;Spring容器负责计算,业务处理,管理service、dao和工具类对象;mybatis负责数据库的crud。

从用户点击http://localhost:8080/URL/index.jsp分析,当填写表单提交后:

首先:浏览器拼接字符串,发送请求;

然后:SpringMvc的中央控制器得到数据,逐个接收/按对象接收数据,找到对应的controler对象;

其次:Spring容器中service对象和dao对象的方法在controller对应的方法执行,返回数据/视图,其中可能设计数据库的操作;

最后:view对象被返回给用户,完成响应。

不难发现,SpringMVC负责Controller对象和web相关,Spring容器负责service、dao和工具类对象,鉴于二者是父子关系,故天然可以互相通信。

8.2 整合步骤

第一步:准备mydb数据库和student表,id设置自增主键,name,age属性

第二步:新建maven-web项目,加入依赖:springmvc,spring,mybatis,javckson,druid连接池,mysql驱动,jsp,servlet依赖 ;

第三步:写web.xml

  • 注册DispatcherServlet→创建springmvc容器对象以便controller对象;创建servlet对象,接收请求;

  • 注册spring 的监听器:ContextLoaderListener 创建spring的容器对象,才能创建service、dao对象

  • 注册字符集过滤器,解决post乱码问题

第四步:创建包、control包、service、dao、实体类包

第五步:写三个配置文件

  • springmvc配置文件 注解方式-组件扫描器,视图解析器,加上注解驱动;

  • spring配置文件

    • 数据库登录配置文件、阿里数据源、tomcat配置、调试

    • sqlfactory配置

  • mybatis主配置文件

    • 日志

    • 实体类包名映射

    • mapper文件包名映射

  • 数据库的属性配置文件

  • 声明mybatis扫描器,创建dao对象

    • 指明sqlsessionfactory

    • 映射dao包,创建旗下对象

  • 声明service注解→service所在包名→完成service对象创建

<context:component-scan base-package="cn.zhangsan.Service"/>
  • 再调试,看是否有异常

第六步:写代码,dao接口和mapper文件,service和实现类,controller,实体类

  • 写Student类
  • 定义StudentDao接口
  • 写studentDaoxml mapper文件  建议:不要写*,因为表结构可能变化
    •  节约网络带宽,节省存储,加快查询速度
    •  由于id自增,故insert  into student(name,age) values(#{name},#{age})
  • 写studentservice接口及其实现类
    • 添加service注解
    •  添加studentdao属性
    •  采用自动注入
    •   实现方法
  • 写controller
    • 添加@controller注解
    •  详解StudentController
    •  最后加上jsp-result。jsp结果页面

第七步:写jsp页面

第八步:前端发送ajax请求

  • 新建index.jsp
    • 引入图片---找不到路径问题:建议加上EL表达式

  • 注册页面
    • 添加stuadd.jsp 添加表单【controller的形参名与value一样】
    • 引入路径前缀【index.jsp、addstu.jsp】
  • 过程分析

index.jsp→addStu.jsp→student/addStu.do(service方法,调用dao方法)→result.jsp

  • 查询功能
    • 引入jquery
    •  新建liststudents.jsp
    • 搭骨架,发请求,填表格
    • 需求更改:点击浏览页面学生就显示了;
      • 封装、直接调用

【注意】值得注意的是,在实际开发中,以上架构别人已经写好了,只需要自己写代码即可。


9.SpringMVC转发与重定向

转发:在Springmvc内部,转发是框架内部的事,对用户不可见,例如执行servlet中触发跳转页面操作,用户经过一次请求,然后就可获得想要的结果;

 mv.setViewName("forward:/WEB-INF/view/show.jsp")

重定向:在SpringMVC外部,用户提交链接url,然后系统内部找不到数据,返回一个新的链接,然后浏览器再次提交请求,即用户发出两次请求,然后获得结果。

 mv.setViewName("redirect:/hello.jsp");//创建逻辑名称,在jsp目录下创建result.jsp

【注意1】转发和重定向都不与视图解析器合作,都必须写全部的路径;

【注意2】重定向中不能获取原链接中的参数,因为位于两个request作用域中,除非如下的设置:

<title>重定向</title>
<h3>show redirect</h3><br/>
<h3>myname数据:${param.msg}</h3>
<h3>myage数据:${param.fun}</h3>
<h1><%request.getParameter("myname")%></h1>

【注意3】重定向不管怎么样都不可以访问WEB-INF的资源。



10.异常处理

以前我们知道,在进行异常处理后,每个方法都要加上try-catch,这样不仅繁琐,而且后期一旦异常类型改变,修改起来麻烦,同时注意到这种模式中业务方法和非业务方法交叉,不符合解耦思想,因此我们引入Spring的AOP机制,采用统一、全局的异常处理,把业务逻辑和异常处理的代码分开。

我们采用了两个注解,@ExceptionHandler和@ControllerAdvice,那么异常处理步骤如下:

第一:新建项目的web、加入依赖;

第二:新建一个自定义异常类myuserexception,定义子类nameexceptino,aegexception

第三:在control中根据业务逻辑抛出异常

第四:创建普通类,作为全局异常处理类

  • 在类上面加入@ControllerAdvice

  • 在类中定义方法,方法上加入@ExceptionHandler

@ExceptionHandler(value = nameexception.class)
    public ModelAndView donameexception(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","姓名只能是张三,其他用户非法");
        mv.addObject("ex",exception);//放入map
        mv.setViewName("NameError");
        return mv;
    }

 @ExceptionHandler
    public ModelAndView doothereception(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","年纪只能是0~80岁");
        mv.addObject("ex",exception);//放入map
        mv.setViewName("defaultError");
        return mv;
    }

【注意】这里下面一个方法表示处理其他未明确声明的异常,建议写上!

第五:创建处理异常的视图页面

第六:创建springmvc配置文件

  • 组件扫描器 扫描@controller
<context:component-scan base-package="com.zhuge.control"/>
  • 组件扫描器,扫描@ControllerAdvice

<!-- 组件扫描器处理异常,去寻找@controllerhander注解-->
<context:component-scan base-package="com.zhuge.handler"/>
  • 声明注解驱动

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

那么异常处理有什么用呢?

在写异常处理的时候,要考虑如下几点:

第一:把异常记录到日志文件、数据库中;

第二:返回给用户异常发生时的友好视图;

第三:发送短信、邮箱通知给开发者。

11.拦截器_Interceptor

你可能会问,怎么控制用户请求的参数呢?

你可能想到,可以在controller对象中控制,显然是可以的,但是是否不符合解耦合的思想呢?此时业务方法与权限验证这一非业务方法紧密结合,不合逻辑,那么怎么处理呢?

dei!Springmvc考虑到这一点,引入了拦截器,拦截器是一个全局的保安,多个control对象都会被该保安保卫检验,这可以看成是aop思想的应用,多个control对象的拦截功能分离到保安切面。

11.1 拦截器的使用

定义一个类实现HandlerInterceptor接口,重写方法

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        time = System.currentTimeMillis();
        System.out.println("myintercept的preHandle方法执行了");
        //增加返回结果,以便验证失败时用户体验→抓饭页面
//        request.getRequestDispatcher("/tips.jsp").forward(request,response);

        return true;
    } 

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView mv) throws Exception {
        System.out.println("postHandle完成了");
//        对原来的结果进行修改
        if(mv!=null){
            //修改数据
            mv.addObject("mydata",new Date());
            //修改视图
            mv.setViewName("other");
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        System.out.println("afterCompletion完成了");
        //演示时间计算,从prehandle到这里的耗时:
        System.out.println("耗时毫秒数"+(System.currentTimeMillis()-time));

    }

11.2 拦截器方法

第一个:preHandle→保安

  • 参数:Object handler→被拦截的对象→mycontrol
  • 时间:在mycontrol.dosome方法执行前进行拦截
  • 用途:获取请求信息,进行合法性、权限验证
  • 返回值:true→放行;false→拦截;

【注意】在拦截前最好转发到友好的界面。

第二个:postHandle

  • 参数:

        ModelAndView mv→返回的结果

        Object handler→被拦截的对象→mycontrol

  • 时间:在mycontrol.dosome方法执行后进行拦截
  • 用途:二次修改返回的结果

第三个:afterCompletion

  • 参数:

        Exception ex→运行时抛出的异常

        Object handler→被拦截的对象→mycontrol

  • 时间:在请求处理完成后【视图完成后→即forward后】进行拦截
  • 用途:资源回收、删除创建的对象等;

11.3 拦截器使用步骤

新建maven-web→引入依赖→创建controller类→创建普通类实现拦截器→创建show.jsp→配置文件声明组件扫描器和拦截地址

<mvc:interceptors>
<!--               声明第一个拦截器-->
               <mvc:interceptor>
<!--                       path:拦截的uri地址!,可以使用通配符**:任意字符/多级目录-->
<!--                            <mvc:mapping path="/**"拦截所有请求-->
                       <mvc:mapping path="/**"/>
<!--                       声明拦截器对象-->
                       <bean class="com.zhuge.handler.myintercept"/>
               </mvc:interceptor>
</mvc:interceptors>

11.4 多个拦截器的拦截顺序

首先说明一点,设置在配置文件的拦截器依照顺序被放入到ArrayList集合中存放,故先声明的先拦截。

现在假设有两个拦截器,为L1和L2,在配置文件中,L1在L2上面声明,分析其拦截过程:

case1:L1和L2的preHandle都返回true

L1.preHandle→L2.preHandle→controller→L2.postHandle→L1.postHandle→L2.afterCompletion

→L1.afterCompletion

case2:L1.preHandle=true,L2.preHandle=false

L1.preHandle→L2.preHandle→L1.afterCompletion

此时没有任何数据,因为没有执行controller方法。

case3:L1.preHandle=false,L2.preHandle=true/false

直接拦截;L1.preHandle

【结论】在实际开发中,一般是多个拦截器一起使用,各司其职,例如L1用于用户身份验证;L2用于用户权限验证;L3用于日志记录。

11.5 拦截器与过滤器比对

选项拦截器过滤器
时机
作用拦截controller对象过滤jsp、html、js
创建者SpringMVC        Tomcat
实现实现HandlerInterceptor接口实现filter接口
侧重截断请求

        适合request

       response参数


12.Springmvc处理流程

假设我们所有代码已经写好,下面讲解Tomcat服务器启动时,然后用户访问url的过程:

第一步:web.xml

Tomcat服务器启动,查看web.xml,启动DispatcherServlet,根据classpath找到配置文件,配置乱码过滤器,创建controller对象,构造视图解析器,添加拦截器;

第二步:index.jsp

用户访问,经过index.jsp传入参数,先经过L1拦截器,假设放行,则找到对应的能处理some.do的controller对象,执行dosome方法,返回ModelAndView对象;

第三步:show.jsp

转发到show.jsp,同时传入参数,显示结果页面;

其中:

第一个:处理器映射器

在中央处理器收到请求后发送给处理器映射器,从容器找对象(Application.getbean(somedo)),放到处理方法链中;

处理方法链[HandlerExecutionChain]:处理器对象-mycontrol、拦截器-List<HandlerInterceptor>;

第二个:适配器

实现HandleAdpter接口的对象,用于执行处理器的dosome方法。

第三个:视图解析器

实现ViewResoler接口的对象,用于拼接字符逻辑名,创建View对象。

【注意】在Springmvc中,jsp和html都不是String类型,而是View视图类型,即InternalResourceViewResolver。

为您推荐