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/
显然不符合预期,根本原因就是参照地址变化了,因此有哪些修改方法呢?
法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。