掌握 SpringMVC 的统一异常处理
掌握springmvc拦截器
掌握ssm(springmvc+spring+mybatis)整合
在J2EE程序中,异常分类为预期的异常和运行时异常。预期的异常可以通过捕获进行处(try{…}catch(Exception e){…});运行时异常只能通过规范代码编写,增加测试来减少。
客户端(浏览器)–>前端控制器(DispatcherServlet)–>表现层(controller)–>业务层(service)–>持久层(dao)
从持久层dao开始,每一层发生异常都向上一层抛出,一直抛到前端控制器(DispatcherServlet)。到此不能再抛出,我们不能把异常抛给用户。在前端控制器需要进行异常处理,前端控制器是中央处理器,不负责具体的业务处理,因此在前端控制器需要调用异常处理器进行异常处理,最终返回一个友好的异常提示页面,提示用户。
控制层的方法写 try …catch …
@RequestMapping("save2")
public String save2() {
try {
int i = 1 / 0;
} catch (Exception e) {
return "error";
}
return "success";
}
异常过滤器 (servlet 技术实现控制层 )拦截所有请求 *,过滤器拦截所有请求: controller 、servlet 、jsp 、html /css/js /css/js /
例如: springmvc 中 HandlerExceptionResolver 异常处理器接口。
写一个异常处理类,实现接口: HandlerExceptionResolver
package com.exception;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CustomerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//打印异常信息
e.printStackTrace();
ModelAndView modelAndView = new ModelAndView();
//保存错误信息
modelAndView.addObject("error","小伙子,请联系系统管理员");
//设置错误页面
modelAndView.setViewName("error");
return modelAndView;
}
}
配置异常管理器
<bean class="com.exception.CustomerExceptionResolver"/>
springmvc框架中的拦截器,相当于web阶段学习的过滤器(filter),可以实现前置增强和后置增强功能。在springmvc框架中,拦截器可以对处理器方法执行预处理(前置增强),和执行后处理(后置增强)。
springmvc框架中,自定义拦截器需要实现接口(HandlerInterceptor)。
package com.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoHandlerInterceptor1 implements HandlerInterceptor {
public DemoHandlerInterceptor1() {
System.out.println("创建拦截器对象");
}
/**
* 前置增强的方法:preHandle
* 方法参数:
* request:请求对象
* response:响应对象
* handler:目标处理器方法
* 拦截的方法,业务逻辑控制校验代码都写到这里。
* 此方法返回true,表示放行。放行的含义是指,如果有下一个拦截器就执行下一个,
* 如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法。
* 此方法返回false,表示不放行,不会执行下一个拦截器以及控制器
* 如何调用? 按拦截器定义顺序调用
* 何时调用? 只要配置了都会调用
* 有什么用? 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回 true。
* 如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("DemoHandlerInterceptor1执行了preHandle方法");
return true;
}
/**
* 后置增强的方法:postHandle
* 方法参数:
* request:请求对象
* response:响应对象
* handler:目标处理器方法
* modelAndView:模型和视图
* 如何调用?
* 按拦截器定义逆序调用
* 何时调用? 在拦截器链内所有拦截器返成功调用
* 有什么用? 在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("DemoHandlerInterceptor1执行了postHandle");
}
/**
* 后置增强的方法:afterCompletion
* 方法参数:
* request:请求对象
* response:响应对象
* handler:目标处理器方法
* ex:异常对象
* 如何调用? 按拦截器定义逆序调用
* 何时调用? 只有 preHandle 返回 true 才调用
* 有什么用? 在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("DemoHandlerInterceptor1执行了afterCompletion");
}
}
在springmvc.xml增加配置:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.interceptor.DemoHandlerInterceptor1"/>
mvc:interceptor>
mvc:interceptors>
package com.interceptor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class InterceptorController {
public InterceptorController() {
System.out.println("开始生成InterceptorController");
}
@RequestMapping("interceptor")
public ModelAndView test() {
System.out.println("开始执行controller方法");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("cn", "china");
modelAndView.setViewName("success");
return modelAndView;
}
@RequestMapping("interceptor2")
public ModelAndView test2() {
System.out.println("开始执行controller2方法");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("usa", "american");
modelAndView.setViewName("success");
return modelAndView;
}
}
图一:
图二:
执行结果说明:
首先执行拦截器方法:preHandle
执行处理器方法:testInterceptor
执行拦截器方法:postHandle
响应jsp页面(在postHandle方法封装的模型数据,页面可以获取到)
执行拦截器方法:afterCompletion
图一:(没有数据)
图二:
执行结果说明:
方法 | 方法说明 |
---|---|
preHandle | preHandle方法,在处理器方法执行前执行,在响应jsp页面前执行。是对处理器方法执行预处理。 该方法返回布尔类型的值。返回true继续执行;返回false终止执行。 在实际项目中,适合于在该方法实现检查用户是否登录、是否有资源操作的权限等业务逻辑处理 |
postHandle | postHandle方法,在处理器方法执行后执行,在响应jsp页面前执行。是对处理器方法执行后处理。 该方法中提供了ModelAndView参数对象,可以封装响应的模型数据。 在实际项目中,适合于在该方法实现公共模型数据的设置。比如页面头部的欢迎信息、页面尾部的友情 链接、备案、免责声明等信息。 |
afterCompletion | afterCompletion方法,在处理器方法执行后执行,在响应jsp页面后执行。是对处理器方法执行后处理。 该方法的特点是只要当前拦截器preHandle方法返回true,就可以得到执行。 在实际项目中,适合于在该方法实现记录用户访问日志、释放资源。 |
package com.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoHandlerInterceptor2 implements HandlerInterceptor {
public DemoHandlerInterceptor2() {
System.out.println("创建拦截器对象");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("DemoHandlerInterceptor2执行了preHandle方法");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("DemoHandlerInterceptor2执行了postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("DemoHandlerInterceptor2执行了afterCompletion");
}
}
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.interceptor.DemoHandlerInterceptor2"/>
mvc:interceptor>
执行结果说明:
preHandle方法按照配置顺序执行
postHandle方法按照配置逆序执行
afterCompletion方法按照配置逆序执行
表现层:springmvc(controller)
业务层:service
持久层:mybatis(mapper)
controller、service、mapper都是javaBean,通过spring框架进行整合。
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)ENGINE=InnoDB character set utf8 collate utf8_general_ci;
insert into account(name,money) values('小明',1000);
insert into account(name,money) values('小花',1000);
insert into account(name,money) values('小王',1000);
spring核心支持包
aspectj 切面支持
spring事务支持、jdbc包
springmvc支持包
mybatis支持包
spring整合mybatis支持包
spring对junit支持包、junit–>
数据库驱动、连接池、servlet/jsp支持包、jstl支持包
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>springgroupId>
<artifactId>ssmartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>warpackaging>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.6version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>3.0version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
dependency>
<dependency>
<groupId>jstlgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
dependencies>
project>
package ssm.po;
@Component
public class Account {
private int id;
private String name;
private double money;
public Account() {
}
public Account(int id, String name, double money) {
this.id = id;
this.name = name;
this.money = money;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
编写账户dao接口(只写dao接口即可)
package ssm.dao;
import org.apache.ibatis.annotations.Select;
import ssm.po.Account;
import java.util.List;
public interface AccountDao {
List<Account> findAll();
}
<mapper namespace="ssm.dao.AccountDao">
<select id="findAll" resultType="ssm.po.Account">
select * from account
select>
mapper>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zzw?characterEncoding=utf8
jdbc.username=root
jdbc.password=root
jdbc.initialSize=3
jdbc.maxActive=10
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
package ssm.server;
import ssm.po.Account;
import java.util.List;
public interface AccountServer {
List<Account> findAll();
}
package ssm.server.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ssm.dao.AccountDao;
import ssm.po.Account;
import ssm.server.AccountServer;
import java.util.List;
@Service
public class AccountServerImpl implements AccountServer {
@Autowired
private AccountDao accountDao;
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="ssm.server"/>
beans>
在spring配置文件中增加实体类bean进行测试
<bean id="account" class="ssm.po.Account"/>
package spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import ssm.po.Account;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans.xml")
public class Springtest {
@Autowired
private Account account;
@Test
public void test() {
System.out.println(account);
}
}
配置包扫描controller
配置处理器映射器
配置处理器适配器
配置视图解析器
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="ssm.controller"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/pages/"/>
<property name="suffix" value=".jsp"/>
bean>
<mvc:annotation-driven/>
beans>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
问题:
要让spring与springmvc协作起来,即在springmvc的控制器中需要知道spring的容器,并且从spring容器获取到账户service对象。那么问题来了:如何创建spring容器?又如何从spring容器中获取service对象呢?
推导:
在web项目中,tomcat服务器加载每一个web项目,都会创建一个application域的对象,它是ServletContext。
我们只要通过监听器,监听ServletContext对象的创建,一旦ServletContext对象创建,立即创建spring容器,并且把spring容器放入ServletContxt对象中。那么问题来了:需要我们创建这样的监听器吗?
答案:
spring框架给我们提供了一个监听器ContextLoaderListener,它实现了ServletContextListener监听器接口。它会在ServletContext对象创建的时候,帮助我们创建spring的容器,并且把spring容器放入ServletContext对象中。我们只需要配置即可。
在web.xml配置ContextLoaderListener:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqE3EfFo-1582269649076)(…/Day57-springmvc%E7%AC%AC%E4%B8%89%E5%A4%A9/media/2e8e9aa8bcbc43bf7a0fd4b4925144d9.png)]
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:beans.xmlparam-value>
context-param>
package ssm.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import ssm.po.Account;
import ssm.server.AccountServer;
import java.util.List;
@Controller
public class AccountController {
@Autowired
private Account account;
@RequestMapping("list")
public ModelAndView findAll() {
System.out.println(account);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("list");
return modelAndView;
}
}
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="ssm.dao"/>
bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* ssm.server.impl.*.*(..))"/>
aop:config>