SpringBoot
第一节课
1.图例
2.三大组件
Servlet
-
作用
- 接受请求–>处理请求–>返回响应
-
流程
- 客户端发送请求到服务端
- 服务端将请求消息发送给Servlet
- Servlet生成响应发送给服务器
- 服务器将响应发送给客户端
Filter
-
作用
- 过滤请求和响应
-
流程
- 1.请求进入Filter,执行相关操作
- 2.判断通行,进入Servlet,执行完毕,再返回给Filter,最后返回请求方
- 3.判断失败,直接返回失败结果
Listener
- 监听对象的状态
- (类似观察者模式)
3.三大框架(SSM)
组成
Spring,SpringMVC,Mybatis
思考
- Spring和SpringMVC的区别
- Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring
-
IOC(控制反转)和AOP(面向切片编程)的实现原理
- 配置文件,反射机制
-
SpringBoot和SpringMVC的区别
- SpringBoot实现了自动配置,降低了项目搭建的复杂度,是一套快速开发整合包,内嵌了常用的样板代码
- Spring MVC提供了一种轻度耦合的方式来开发web应用
-
为什么不使用JDBC
- 因为MyBatis ,只需要提供 SQL 语句就好了,其余的诸如:建立连接,操作 Statment,ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点于是可以就此集中在 SQL 语句上,关注在增删改查这些操作层面上
MVC模式组成
有点类似于适配器模式
- M(模型层,即数据)
- V(视图层,展示模型的数据)
- C(控制层,不同的model展现到不同的view)
课后作业
1.MVC设计模式与传统Web开发模式的区别
与传统Web的区别
-
传统Web将显示层、控制层、数据层的操作全部交给 JSP 或者 JavaBean 来进行处理的缺点
- 代码严重耦合,不利于扩展和维护
- 代码难以复用
- 工作模式同步,前端等待后端,后端等待前段
-
传统Web水平划分视图和逻辑两层,MVC垂直划分3层
2.接口定义及其实现分开的好处
-
有利于代码规范化
- 接口相当于类的行为规范
-
代码可维护和易扩展
- 就拿最近学习设计模式的例子来说,商家卖红茶,直接new BlackTea(),需求变化,扩展业务,卖绿茶,就得添加new GreenTea(),需求再变,红茶不卖了,这时如果修改就得一个个去删,会显得很繁琐,但如果采用工厂模式的话,完全可以只去修改工厂中的接口实现时的类型,而在外的代码一直是new Factory()不会发生变化
-
有利于代码安全和严密
- 对接口的调用不需要关注接口内部的实现,保证了接口内部的严密
-
丰富了继承的形式
- java中没有多继承,但可以通过继承多个接口的方式变相实现多继承
-
实现松耦合,方便注入
第二节课
1.JUnit单元测试
基本概念
- 区分与人工测试,更加快捷方便和有保证
- java单元测试框架
- 测试驱动编程
用处
-
测试代码逻辑的正确性(尤其是复杂工程)
-
已知输入的先决条件,预期输出后置条件
- TestCase断言
-
正负检验
实践
首先需要在测试类配置如下的注解,特别注意@SpringBootTest后面跟着的是主程序入口类的class
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
Ex1Application.class})
@AutoConfigureMockMvc
前置理论
-
@Before,发生在测试之前
-
@Test,测试时
-
@After,测试之后
-
MockMvc类,主要用于模拟http请求
-
get
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/ex1/hello/list")) .andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); int status = mvcResult.getResponse().getStatus(); TestCase.assertEquals(200, status);
- 执行MockMvcRequestBuilders请求,如果获取的状态码不是200(OK),抛出异常,正常就返回MvcResult对象
-
post
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/api/ex1/user/login") .content(JSONArray.toJSON(user).toString()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(MockMvcResultMatchers.status().isOk()).andReturn(); JsonData jsonData = JSONArray.parseObject(mvcResult.getResponse().getContentAsString(UTF_8), JsonData.class); TestCase.assertEquals(jsonData.getCode(), 0);
- post在http请求报文的body中有数据,所以还得传递一个json格式的对象user
-
-
碰到的问题
-
post测试时传参时json对象和自定义对象的转化
解决方法:http://www.mamicode.com/info-detail-2668986.html
-
-
热部署
- 编译器会根据修改的代码重新调整程序
2.Thymeleaf
轻量级引擎模板
静态模板放在templates
注意点:
- 需要添加xmlns:th=“http://www.thymeleaf.org”
- 具体用法参考文档,https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
private Charset encoding;
private boolean cache;
private Integer templateResolverOrder;
private String[] viewNames;
private String[] excludedViewNames;
private boolean enableSpringElCompiler;
private boolean renderHiddenMarkersBeforeCheckboxes;
private boolean enabled;
private final ThymeleafProperties.Servlet servlet;
private final ThymeleafProperties.Reactive reactive;
.....
}
上面是一部分的ThymeleafProperties的源码,我们可以看到,Thymeleaf默认是去"classpath:/templates/"
里找后缀为.html
的文件的
第三节课(2020-10-15/第四周)
1.自定义全局异常错误
如何配置全局自定义全局异常
对应异常的处理方法上添加@ExceptionHandler(value = Exception.class)注解
类型
- Json格式
@RestControllerAdvice
public class JsonUserHandler {
@ExceptionHandler(value = Exception.class)//捕获什么异常
JsonData handlerException(Exception ex, HttpServletRequest request) {
return JsonData.buildError("服务端异常报错");
}
}
- 自定义页面,通过ModelAndView实现
@ControllerAdvice
public class ViewUserHandler {
@ExceptionHandler(value = Exception.class)
Object handlerException(Exception ex, HttpServletRequest request) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html");
modelAndView.addObject("msg", ex.getMessage());
return modelAndView;
}
}
2.过滤器
作用
权限控制,用户状态控制
编码
实现Filter接口,并重写init,doFilter,destroy方法,添加@WebFilter注解,使启动类可以回调自定义的Filter
@WebFilter(urlPatterns = "/api/ch/pri/*", filterName = "LoginFilter")
public class LoginFilter implements Filter {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
@Override
public void destroy() {
System.out.println("destroy");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String token = request.getHeader("token");
if (StringUtils.isEmpty(token))
token = request.getParameter("token");
if (!StringUtils.isEmpty(token)) {
User user = UserServiceImpl.sessionMap.get(token);
if (user != null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
JsonData jsonData = JsonData.buildError("登录失败", -2);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response, jsonStr);
}
} else {
JsonData jsonData = JsonData.buildError("未登录", -3);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response, jsonStr);
}
}
public void renderJson(HttpServletResponse response, String json) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.print(json);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
3.拦截器
作用
同过滤器
编码
public class LoginInterceptor implements HandlerInterceptor {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
String token = request.getHeader("token");
if (StringUtils.isEmpty(token))
token = request.getParameter("token");
if (!StringUtils.isEmpty(token)) {
User user = UserServiceImpl.sessionMap.get(token);
if (user != null) {
return true;
} else {
JsonData jsonData = JsonData.buildError("登录失败", -2);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response, jsonStr);
return false;
}
} else {
JsonData jsonData = JsonData.buildError("未登录", -3);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response, jsonStr);
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
public void renderJson(HttpServletResponse response, String json) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.print(json);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
多个拦截器的执行过程
实践
如果请求一旦被某个拦截器拦截,那后面的拦截器就不会再执行,相当for-if-break
多个拦截器阻拦效果,首先需要配置拦截器的路由
@Configuration
public class UserWebMvcController implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/ch/pri/**");
registry.addInterceptor(new TwoInterceptor()).addPathPatterns("/api/ch/pri/**");
registry.addInterceptor(new ThreeInterceptor()).addPathPatterns("/api/ch/pri/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
@Bean
public LoginInterceptor getLoginInterceptor() {
return new LoginInterceptor();
}
}
-
部分放行(第二个拦截)
第二个拦截器拦截:第一个拦截器preHandle和afterCompletion会被执行,但因为被第二个拦截器拦截,所以第一个postHandle不会被执行
-
(部分拦截)第三个拦截
同理
-
全部放行
栈的既视感,先进后出
全部放行:具体流程可以用下图解释
原理
4.监听器(了解)
- 应用启动监听
- 会话监听
- 请求监听
5.课后作业
过滤器和拦截器比较
1.编码
- 过滤器
- 实现Filter接口,重载init,destroy,doFilter(过滤逻辑实现的地方)方法,同时需要添加注解
- @WebFilter(urlPatterns = “/api/ch/pri/*”, filterName = “LoginFilter”),访问/api/ch/pri/*就会触发过滤器
- 拦截器
- 实现HandlerInterceptor接口,重载preHandle(拦截器逻辑实现的地方),postHandle,afterCompletion,不需要添加注解,但需要配合自定义的config类,覆盖原来的WebMvcConfigure类
- 重载addInterceptors方法添加对应的路由(注意添加@Configuration)
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/ch/pri/**");
2.生命周期
- 过滤器
- init随程序启动被调用,init和destroy在整个Filter生命周期只会被调用一次,而doFilter在对应的每一次请求都被会调用
- 拦截器
- 在每一次对应请求都会执行一个完整的生命周期,即preHandle,postHandle,afterCompletion都会在一次请求中被执行
3.实现原理
- 过滤器
- 函数回调
filterChain.doFilter(servletRequest, servletResponse);
接下来我们重点分析这一句代码
我们先来看一下FilterChain这个类
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
通过上面的代码我们可以发现它是一个接口,那我们再来看一下它的应用级实现类,也就是ApplicationFilterChain这个类
ApplicationFilterChain.this.internalDoFilter(req, res);
我截取了比较doFilter核心语句,所以我们再来扒一扒internalDoFilterf
方法
if (this.pos < this.n)
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
从上面的这几句中我们可以发现,这个方法其实是对过滤器的一个遍历,不断获取我们定义的过滤器,然后进行过滤操作,最后将结果回调,当不符合过滤条件就抛出异常
总结:ApplicationFilterChain
里面能拿到我们自定义的xxxFilter
类,在其内部回调方法doFilter()
里调用各个自定义xxxFilter
过滤器,并执行 doFilter()
方法。而每个xxxFilter
会先执行自身的 doFilter()
过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse)
,也就是回调ApplicationFilterChain
的doFilter()
方法,以此循环执行实现函数回调
- 拦截器
- 基于Java的反射机制,动态代理==(留坑)==
4.灵活度
- 过滤器
- 实现javax.servlet.Filter接口,依赖于Servlet,需要使用服务器容器,只能限于web程序中
- 拦截器
- spring的一个组件,使用范围包括application,web等等
5.触发时机
过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用
6.参考文献
第四节课(2020-10-22/第五周)
1.JDBC(了解)
基本流程
- 加载JDBC驱动
- 创建数据库连接
- 创建preparedStatement对象
- 执行sql语句
- 处理结果集
- 关闭JDBC对象资源
2.ORM框架
理解
数据库的表和java对象做映射关系,比如User类中的username字段映射user表中的username字段
Mybatis(重点)
1.简介
Mybatis是什么?
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- 存储地:apache——>google——>github(现在)
如何获得?
- maven
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
- github:https://github.com/mybatis/mybatis-3/releases
- 中文文档:https://mybatis.org/mybatis-3/zh/index.html
持久化
持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
2.流程
1.导入依赖
mysql,mybatis,log4j(可以不添加)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
2.环境配置
- 配置连接数据库的信息
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/mailDB?useUnicode=true&characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="XXXXX"/>
</dataSource>
</environment>
</environments>
- 配置数据库操作的xml映射
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/MailMapper.xml"/>
</mappers>
- 注意(xml需添加这一段,不然不会联想)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
3.java类和sql语句的映射
- 定义数据表的实体类
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private String mobile;
}
- 定义sql语句的接口
public interface UserMapper {
User selectById(int useId);
int add(User user);
List<User> selectListByXML();
List<User></