目录
1:拦截器(Interceptor)
1.1:拦截器代码实现
1.2:拦截器源码分析和流程总结
2:过滤器(Filter)、自定义(Servlet)、监听器(Listener)
3:文件上传
3.1:文件上传代码实现
3.2:文件上传源码分析
4:整合druid数据源
4.1:整合德鲁伊
4.2:德鲁伊监控页面
5:Spingboot指标监控
6:整合mybatis、mybatis-plus
6.1:整合mybatis
6.2:整合mybatis-plus
7:整合redis集群
8:错误处理
8.1:请求不存在的controller
8.2:请求存在但是代码错误的controller
8.3:前后端未分离
8.4: 前后端分析,全局异常
1:拦截器(Interceptor)
1.1:拦截器代码实现
拦截器是Spring容器进行管理的,对请求路径进行动态的拦截,可以进行各种权限日志等等骚操作。
代码实现第一步:自己实现HandlerInterceptor接口
/**
* 登录检查 拦截器
*
* 1:写拦截器代码逻辑
* 2 配置拦截器拦截 那些请求
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* controller方法 执行之前
* @return
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("拦截请求{}"+request.getRequestURI());
User user = (User) request.getSession().getAttribute("loginUser");
if (user == null) {
System.out.println("拦截器重定向");
//如果session不存在登录信息,重定向到登录页面
request.setAttribute("msg","拦截器拦截未登录");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
return true;//返回true 程序接着执行
}
/**
* controller方法执行之后,在页面渲染之前
*/
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
log.info("controller方法执行之后{}",modelAndView);
}
/**
* 页面渲染之后
*/
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
log.info("页面渲染之后{}",ex);
}
}
代码实现第二步:自定义拦截器拦截路径和放行路径
/**
* 实现WebMvcConfigurer接口 改变MVC行为
* 使用addInterceptors 接口添加拦截器
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/*") // /**拦截所有请求,包含静态页面 /*:只拦截后面一级 /**:拦截内容包含多个层级
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/*","/js/**");//过滤掉登录请求和静态资源
}
}
1.2:拦截器源码分析和流程总结
所以真正的执行步骤如下:
2:过滤器(Filter)、自定义(Servlet)、监听器(Listener)
Filter、servlet是Servlet规范,拦截的是自定义的请求。因为DispatcherServlet拦截的是/
但是Filter、servlet可以自定义拦截请求,根据匹配规则,他们会自己处理请求。
Filter代码实现:
/**
* 继承Filter
* 不会经过拦截器
* 拦截/*所有请求 不管get post
*
* 使用次注解生效
* @ServletComponentScan(basePackages = "com.example.springboot2_web03")
*/
@WebFilter(urlPatterns = "/*",filterName = "MyFilter")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String name = request.getParameter("name");
System.out.println("执行MyFilter无论什么get、post方法:"+name);
chain.doFilter(request,response);//调用链
}
}
Servlet代码实现:
/**
* 继承HttpServlet类,可以重写各种get post put 等等方法
*
* 使用次注解生效
* @ServletComponentScan(basePackages = "com.example.springboot2_web03")
*
* servlet 拦截/s1(精度高 优先匹配) 就不会进入DispatcherServlet拦截/
*/
@WebServlet(urlPatterns = "/s1",name = "MyServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
System.out.println("执行MyServlet请求get:"+name);
resp.getWriter().write("get请求MyServlet拦截/s1请求:"+name);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
System.out.println("执行MyServlet请求post:"+name);
resp.getWriter().write("post请求MyServlet拦截/s1请求:"+name);
}
}
监听器代码实现:
/**
* 继承ServletContextListener
* 容器启动的时候生效
*
* 使用次注解生效
* @ServletComponentScan(basePackages = "com.example.springboot2_web03")
*/
@WebListener(value = "MyListener")
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("MyListener监听ServletContext初始化,容器启动的时候输出");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("MyListener监听ServletContext销毁");
}
}
3:文件上传
3.1:文件上传代码实现
第一步:html文件
<!--文件上传必须是post和enctype="multipart/form-data"-->
<form role="form" th:action="@{/fileUpload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">名字</label>
<input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<!--type="file" 没有multiple代表 单个文件-->
<input type="file" name="headerImg" id="exampleInputFile">
</div>
<div class="form-group">
<label for="exampleInputFile">生活照</label>
<!--type="file" multiple代表多文件上传 -->
<input type="file" name="photos" multiple>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
第二步Java代码实现:
/**
* 文件上传
* @param files
* @return
*/
@PostMapping(value = "/fileUpload")
public String fileUpload(@RequestParam(value = "username") String username,
@RequestParam(value = "email")String email,
@RequestParam(value = "headerImg") MultipartFile headerImg,
@RequestParam(value = "photos") MultipartFile[] photos,
Files files) throws IOException {
if (!headerImg.isEmpty()){
System.out.println("username:"+username);
System.out.println("email:"+email);
System.out.println("files:"+files);
//headerImg 单文件
System.out.println("headerImg名字:"+ headerImg.getOriginalFilename());
System.out.println("headerImg大小:"+ headerImg.getSize());
System.out.println("headerImg文件类型:"+ headerImg.getContentType());
String newFileHeaderImg="A"+headerImg.getOriginalFilename();
headerImg.transferTo(new File("/Users/huyiju/Desktop/upload/"+newFileHeaderImg));
}
if (photos.length>0){
//photos多文件用MultipartFile[]数组
for (MultipartFile photo : photos) {
File file=new File("","");
String newFilePhotos="B"+photo.getOriginalFilename();
photo.transferTo(new File("/Users/huyiju/Desktop/upload/"+newFilePhotos));
}
}
return "/form/form_layouts";
}
第三步配置文件设置文件上传大小
#单个文件上限
spring.servlet.multipart.max-file-size=10MB
#多个文件请求上限
spring.servlet.multipart.max-request-size=100MB
3.2:文件上传源码分析
首先查看源码MultipartAutoConfiguration的自动装配
有了文件上传解析器,我们在配置文件中设置上传文件大小配置,默认是1MB(单个文件)和10MB(最大请求)
然后查看源码
//for循环将controller的参数逐个绑定
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
//根据请求multipartRequest获取MultipartFile 并将返回值
//赋值给 controller中的MultipartFile 类型的参数
//
List<MultipartFile> files = multipartRequest.getFiles(name);
return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
4:整合druid数据源
4.1:整合德鲁伊
1:导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--德鲁伊操作数据库-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
2:配置数据库信息和连接池
#德鲁伊数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
#连接池信息
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=5
spring.datasource.druid.initial-size=5
spring.datasource.druid.max-wait=60000
3:代码测试(未使用mybatis,所以这里使用了jdbcTemplate)
@Autowired
JdbcTemplate jdbcTemplate;
/**
* druid测试
*/
@Test
void starter_jdbc() {
String sql="select * from t1";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
maps.forEach(map -> System.out.println(map));
}
4:结果查看(图片里边初始化的德鲁伊连接池)
4.2:德鲁伊监控页面
德鲁伊的依赖包含监控页面功能,只是没有开启我们只需要配置文件开启监控页面就可以
1:配置监控页面信息
# StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
#是否启用StatViewServlet(监控页面)默认值为false(考虑到安全问题默认并未启动,如需启用建议设置密码或白名单以保障安全)
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=123456
### WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
spring.datasource.druid.filters=stat,wall
spring.datasource.druid.filter.stat.slow-sql-millis=1000
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.enabled=true
2:页面验证
登录页面,输入账户密码root、123456
http://localhost:8080/druid/login.html
5:Spingboot指标监控
6:整合mybatis、mybatis-plus
6.1:整合mybatis
1:导入依赖
<!-- springboot——整合mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
2:配置mapper映射位置
#mybatis配置 config-location配置mybatis文件地址 mapper-locations mapper地址
mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/*.xml
3:接口
package com.example.springboot2_web03.mapper;
import com.example.springboot2_web03.entity.T1;
@Mapper
public interface T1Mapper {
List<T1> selectAll();
T1 selectById(@Param(value = "id") int id);
@Select("select * from t1 where id=#{id}")
T1 selectById1(@Param(value = "id") int id);
}
4:mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springboot2_web03.mapper.T1Mapper">
<!--查询全部-->
<select id="selectAll" resultType="com.example.springboot2_web03.entity.T1">
select * from t1
</select>
<!--根据id查询-->
<select id="selectById" resultType="com.example.springboot2_web03.entity.T1">
select * from t1 where id=#{id}
</select>
</mapper>
代码测试:
@Autowired
T1Mapper t1Mapper;
/**
* mybatis测试
*/
@Test
void mybatis_test() {
List<T1> t1s = t1Mapper.selectAll();
for (T1 t1 : t1s) {
System.out.println("查询数据:"+t1);
}
T1 t1 = t1Mapper.selectById1(1);
System.out.println("根据id查询:"+t1);
}
6.2:整合mybatis-plus
1:导入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--德鲁伊操作数据库-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<!-- springboot——整合mybatis-->
<!-- <dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- <version>2.1.3</version>-->
<!-- </dependency>-->
<!--mybatisPlus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MybatisPlus分页插件注解-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.5.3.1</version>
<scope>compile</scope>
</dependency>
2:配置文件
#mybatisPlus配置 config-location配置mybatis文件地址 mapper-locations mapper地址
#mybatis-plus.config-location=classpath:mybatis-config.xml
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
3:添加分页拦截器
@Configuration
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true);//最后一页的下一页调回首页
paginationInnerInterceptor.setMaxLimit(500L);//-1不受限制 这里限制500条
interceptor.addInnerInterceptor(paginationInnerInterceptor);//如果配置多个插件,切记分页最后添加
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
4:代码测试
//mapper代码
@Mapper
public interface TestMapper extends BaseMapper<Test> {
//自定义分页查询
Page<Test> selectOrderByAgePage(@Param(value = "page") Page<Test> page);
}
//接口和实现类代码
public interface TestService extends IService<Test> {
Page<Test> selectOrderByAgePage(Page<Test> page);
}
@Service
public class TestServiceImpl extends ServiceImpl<TestMapper, Test> implements TestService{
@Autowired
TestMapper testMapper;
/**
* 查询数据 分页显示
* @return
*/
public Page<Test> selectOrderByAgePage(Page<Test> page){
return testMapper.selectOrderByAgePage(page);
}
}
//controller 代码
@GetMapping(value = "/dynamic_table")
public String dynamic_table(@RequestParam(name = "pageIndex",defaultValue = "1",required = false)Integer pageIndex,
Model model){
System.out.println("执行dynamic_table方法:默认值是第一页");
//开始页和条数
Page<Test> page=new Page<>(pageIndex,3);
Page<Test> pages = testService.selectOrderByAgePage(page);
System.out.println("当前页:"+pages.getCurrent());
System.out.println("总页数:"+pages.getPages());
System.out.println("总行数:"+pages.getTotal());
System.out.println("当前页数据:"+pages.getRecords());
model.addAttribute("pages",pages);
return "/table/dynamic_table";
}
7:整合redis集群
集群搭建过程见其他文章
1:导入依赖
<!-- ======================= -->
<!-- 这里是Redis的集群测试案例 -->
<!-- ======================= -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2:配置集群信息
#配置redis 集群
#集群密码
spring.redis.password=123456
#集群节点81 - 86
spring.redis.cluster.nodes=127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:6383,127.0.0.1:6384,127.0.0.1:6385,127.0.0.1:6386
#最大重定向次数
spring.redis.cluster.max-redirects=5
#连接池最大连接量
spring.redis.lettuce.pool.max-active=8
#连接最大阻塞时间 毫秒
spring.redis.lettuce.pool.max-wait=1s
#空闲的最大数量
spring.redis.lettuce.pool.max-idle=8
#空闲的最小数量
spring.redis.lettuce.pool.min-idle=0
3:测试代码
//redistemplate配置
@Configuration
public class RedisConfig集群 {
@Bean("redisTemplate")
public RedisTemplate<Object, Object> redisTemplateJQ(
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
RedisConnectionFactory factory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
//测试代码
@Autowired
RedisTemplate redisTemplate;
//StringRedisTemplate redisTemplate;
@Test
void t1() {
redisTemplate.opsForValue().set("代", "测试", 10, TimeUnit.MINUTES);
Set<String> keys = redisTemplate.keys("*");
for (String key : keys) {
System.out.println("key:"+key);
}
System.out.println(keys);
System.out.println("返回值a:" + redisTemplate.opsForValue().get("代"));
}
8:错误处理
8.1:请求不存在的controller
1:浏览器请求没有服务的controller,返回html
2:postMan请求没有服务的controller,返回json
源码分析:
当请求不存在的时候,找不到处理方法报错
服务器转发了一个http://localhost:8080/error 请求,该请求会被自动装配的basicErrorController拦截
1:在ErrorMvcAutoConfiguration声明了一个bean是basicErrorController这个controller会拦截/error请求的错误。
2:这个controller拦截/error请求,在源码中可以看到这连个拦截错误请求
3:根据不同的请求方式浏览器和postMan程序来决定返回html和json
8.2:请求存在但是代码错误的controller
controller代码
@GetMapping(value = "/basic_table")
public String basic_table(){
//自定义错误 抛出 java.lang.ArithmeticException: / by zero
int a=10/0;
return "/table/basic_table";
}
源码分析:
1:执行controller代码报错,DispatcherServlet拦截到异常。
2:异常处理
3:系统默认的异常解析器处理异常,结果都处理不了,回到上边用baseErrorController来处理
8.3:前后端未分离
当我们配置了4XX和5XX错误的html页面的时候
我们配置在项目中配置错误页,baseErrorController就会在浏览器请求错误的情况下,跳转到这些错误页面。我们在错误页面中取出异常信息
html页面
<h3 th:text="${path}">请求路径</h3>
<h3 th:text="${status}">状态</h3>
<h3 th:text="${message}">错误消息</h3>
<h3 th:text="${timestamp}">时间</h3>
浏览器页面显示
8.4: 前后端分析,全局异常
首先我们定义两个Controller
@Controller
public class 异常Controller {
/**
* 前后端分离Controller 直接返回json
*/
@GetMapping(value = "/basic_table1")
@ResponseBody
public CommonReturnType basic_table1(@RequestParam(value = "a",required = false) int b ){
//1:重点:没有自定义全局异常,默认的异常解析器解析
//这里出现异常没有返回值 会被DispatcherServlet拦截,捕获异常 发送/error
// 会进入baseErrorController 返回html
//2:重点:自定义全局异常,拦截指定异常
// 会根据自定异常返回值 是走ModelAndView 还是直接返回json
// 会进入baseErrorController 返回html
int a=10/0; //报错 后边执行
List<User> users = Arrays.asList(new User("张三1","000"),
new User("张三1","111"),
new User("张三2","222"),
new User("张三3","333"));
return CommonReturnType.crateCommonReturnType(users);
}
/**
* 前后端不分离Controller 返回视图
*/
@GetMapping(value = "/basic_table")
public String basic_table(@RequestParam(value = "a") int b ){
//1:重点:没有自定义全局异常,默认的异常解析器解析
//这里出现异常没有返回值 会被DispatcherServlet拦截,捕获异常 发送/error
// 会进入baseErrorController 返回html
//2:重点:自定义全局异常,拦截指定异常
// 会根据自定异常返回值 是走ModelAndView 还是直接返回json
// 会进入baseErrorController 返回html
//自定义错误 抛出 java.lang.ArithmeticException: / by zero
int a=10/0; //报错 后边不执行
// 这个异常ExceptionHandler 直接返回视图 return "login";//返回视图
return "/table/basic_table";
}
}
自定义全局异常
@Slf4j
@ControllerAdvice //底层Component
public class GlobalExceptionHandler {
//自定义异常CommonReturnType {
// "status": "fail",
// "data": {
// "errCode": "300",
// "errMsg": "参数绑定错误"
// }
//}
//第一个自定义异常
@ExceptionHandler(value = Exception.class)
@ResponseBody
public CommonReturnType GlobalControllerExceptions1(Exception exception){
log.info("异常是{}:",exception);
Map<String,Object> map=new HashMap();
if (exception instanceof ServletRequestBindingException) {
map.put("errCode", "300");
map.put("errMsg", "参数绑定错误");
} else if (exception instanceof NoHandlerFoundException) {
map.put("errCode", "404");
map.put("errMsg", "404错误");
} else {
map.put("errCode", "8888");
map.put("errMsg",exception.getMessage());
}
return CommonReturnType.crateCommonReturnType("fail", map);//返回指定异常
}
//前提:没有配置全局异常解析器 出现异常会被抛出,没有异常解析器处理
//然后发送/error请求 被baseErrorController拦截 返回标准页面
//拦截指定异常,会返回ModelAndView 不加@ResponseBody java.lang.ArithmeticException: / by zero
@ExceptionHandler(value ={NullPointerException.class,ArithmeticException.class})
//@ResponseBody 异常直接返回页面
public String GlobalControllerExceptions(Exception exception) {
log.info("异常是{}:",exception);
return "login";//返回视图
}
}