[豪の学习笔记] Spring框架学习碎碎念#1
跟学视频:黑马JavaWeb课程
三层架构

controller:控制层,接收前端发送的请求,对请求进行处理并响应数据
service:业务逻辑层,处理具体的业务逻辑
dao:数据访问层(Data Access Object),也叫持久层,负责数据访问操作,包括数据的增删改查
分层解耦
控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入
Bean对象:IOC容器中创建、管理的对象,称之为bean
@Component //将当前类交给IOC容器管理,成为IOC容器中的bean
@Autowired //运行时,IOC容器会提供该类型的bean对象并赋值该变量,即依赖注入
Service层及Dao层的实现类交给IOC容器管理,,为Controller及Service注入运行时依赖的对象
Bean的声明
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:
| 注解 | 说明 | 位置 |
|---|---|---|
| @Component | 声明bean的基础注解 | 不属于以下三类时使用此注解 |
| @Controller | @Component的衍生注解 | 标注在控制器类上 |
| @Service | @Component的衍生注解 | 标注在业务类上 |
| @Repository | @Component的衍生注解 | 标注在数据访问类上 (由于与MyBatis整合,使用少) |
声明bean的时候,可以通过value属性指定bean的名字,如果没有指定则默认为类名首字母小写
使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller
Bean组件扫描
前面声明bean的四大注解想要生效,还需要被组件扫描注解,@ComponentScan扫描
@ComponentScan注解虽然没有显式配置,但实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包
Bean注入
@Autowired注解,默认是按照类型进行自动装配,如果存在多个相同类型的bean将会报错,通过以下三种方案解决:
@Primary:
@Primary
@Service
public class EmpServiceA implements EmpService{}
@Qualifier:
@RestController
public class EmpController{
@Autowired
@Qualifier("empServiceA")
private EmpService empService;
}
@Resource:
@RestController
public class EmpController{
@Resource(name="empServiceB")
private EmpService empService;
}
其中@Resource与@Autowired的区别是:@Autowired是Spring框架提供的注解,默认按照类型注入;而@Resource是JDK提供的注解,默认按照名称注入
MyBatis
配置数据库连接信息 - 四要素
# 驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接的URL
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
# 连接数据库的用户名
spring.datasource.username=root
# 连接数据库的密码
spring.datasource.password=XXXXXXX
数据库连接池
数据库连接池是个容器,负责分配、管理数据库连接
它允许应用程序重复使用一个现有的数据库连接。而不是再重新建立一个
释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
优点:资源重用、提升系统响应速度、避免数据库连接遗漏
标准接口:DataSource
官方(sun)提供的数据库连接池接口,由第三方组织实现
功能:获取连接 Connection getConnection() throws SQLException;
常见产品:Hikari(springboot默认)、Druid、DBCP、C3P0
Druid德鲁伊:Druid连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一
lombok
Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率
| 注解 | 作用 |
|---|---|
| @Getter/@Setter | 为所有的属性提供get/set方法 |
| @ToString | 会给类自动生成易阅读的toString方法 |
| @EqualsAndHashCode | 根据类所拥有的非静态字段,自动重写equals方法和hashCode方法 |
| @Data | 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode) |
| @NoArgsConstructor | 为实体类生成无参的构造器方法 |
| @AllArgsConstructor | 为实体类生成除了static修饰的字段之外,带有各参数的构造器方法 |
Lombok会在编译时自动生成对应的Java代码,使用Lombok时还需要安装一个lombok的插件(IDEA自带)
参数占位符
#{...}
执行SQL时,会将#{...}替换为?,生成预编译SQL,会自动设置参数值
使用时机:参数传递
${...}
拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题
使用时机:如果对表名、列表进行动态设置时使用
基础操作CRUD
根据ID删除员工
@Delete("DELETE FROM emp WHERE id = #{id}")
public void delete(Integer id);
新增员工
@Options(keyProperty = "id", useGeneratedKeys = true) //会自动将生成的主键值赋值给emp对象的id属性
@Insert("INSERT INTO emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) VALUES (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime});")
public void insert(Emp emp);
更新员工信息
@Update("UPDATE emp SET username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, entrydate = #{enrtydate}, dept_id = #{deptId}, create_time = #{createTime}, update_time = #{updateTime} WHERE id = #{id}")
public void update(Emp emp);
根据ID查询员工
@Select("SELECT * FROM emp WHERE id = #{id}")
public Emp getById(Integer id);
根据条件查询员工
@Select("SELECT * FROM emp WHERE name LIKE CONCAT('%', #{name}, '%') AND gender = #{gender} AND entrydate BETWEEN #{begin} AND #{end} ORDER BY update_time DESC")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
数据封装
实体类属性名和数据库表查询返回的字段名一致,mubatis会自动封装
如果实体类属性名和数据库表查询返回的字段名不一致,则不能自动封装
①给字段起别名,让别名与实体类属性一致
@Select("SELECT id, username, password, name, gender, image, job, entrydate, dept_id deptId, create_time createTime, update_time updateTime FROM emp WHERE id = #{id}")
public Emp getById(Integer id);
②通过@Results、@Result注解手动映射封装
@Results({
@Result(column = "dept_id", property = "deptId"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("SELECT * FROM emp WHERE id = #{id}")
public Emp getById(Integer id);
③在properties中开启mybatis的驼峰命名自动映射的开关 a_column ---> aColumn
mybatis.configuration.map-underscore-to-camel-case=true
动态SQL
随着用户的输入或外部条件的变化而变化的SQL语句
XML映射文件
XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下,即同包同名
XML映射文件的namespace属性为Mapper接口全限定名一致
XML映射文件中SQL语句的id与Mapper接口中的方法名一致,并保持返回类型
@Mapper
public interface EmpMapper{
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
}
<mapper namespace="com.magicshushu.mapper.EmpMapper">
<select id="list" resultType="com.magicshushu.pojo.Emp">
SELECT * FROM emp WHERE name LIKE concat('%', #{name}, '%') AND gender = #{gender} AND entrydate BETWEEN #{begin} AND #{end} ORDER BY update_time DESC
</select>
</mapper>
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果需要做一些很复杂的操作,最好用 XML 来映射语句
选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于个人和团队。 不要拘泥于一种方式,可以在基于注解和 XML 的语句映射方式间自由移植和切换
if标签
<where>
<if test="name != null">
name LIKE concat('%', #{name}, '%')
</if>
<if test="gender != null">
AND gender = #{gender}
</if>
<if test="begin != null and end != null">
AND entrydate BETWEEN #{begin} AND #{end}
</if>
</where>
<update id="update2">
UPDATE emp
<set>
<if test="username != null">
username = #{username},
</if>
<if test="name != null">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="image != null">
image = #{image},
</if>
<if test="job != null">
job = #{job},
</if>
<if test="entrydate != null">
entrydate = #{enrtydate},
</if>
<if test="deptId != null">
dept_id = #{deptId},
</if>
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
WHERE id = #{id}
</update>
foreach标签
<!--根据ID批量删除员工-->
<!--
collection: 遍历的集合
item: 遍历出来的元素
separator: 分隔符
open: 遍历开始前拼接的SQL片段
close: 遍历结束后拼接的SQL片段
-->
<delete id="deleteByIds">
DELETE FROM emp WHERE id IN
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
sql + include标签
<sql id="commonSelect">
SELECT id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
FROM emp
</sql>
<select id="list" resultType="com.magicshushu.pojo.Emp">
<include refid="commonSelect"/>
<where>
<if test="name != null">
name LIKE concat('%', #{name}, '%')
</if>
<if test="gender != null">
AND gender = #{gender}
</if>
<if test="begin != null and end != null">
AND entrydate BETWEEN #{begin} AND #{end}
</if>
</where>
ORDER BY update_time DESC
</select>
开发规范-Restful
REST(REpresentational State Transfer),表述性状态转换,它是一种软件架构风格
传统URL风格URL:
http://localhost:8080/user/getById?id=1 GET:查询id为1的用户
http://localhost:8080/user/saveUser POST:新增用户
http://localhost:8080/user/updateUser POST:修改用户
http://localhost:8080/user/deleteUser?id=1 GET:删除id为1的用户
原始的传统URL,定义较复杂,而且将资源的访问行为对外暴露
基于REST风格URL:
http://localhost:8080/users/1 GET:查询id为1的用户
http://localhost:8080/users POST:新增用户
http://localhost:8080/users PUT:修改用户
http://localhost:8080/users/1 DELETE:删除id为1的用户
REST是风格,是约定方式,不是规定,可以打破
描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源而非单个资源,如users、emps、books
统一响应结果Result
前后端工程在进行交互时,使用统一响应结果 Result
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应信息 描述字符串
private Object data; //返回的数据
//增删改 成功响应
public static Result success(){
return new Result(1,"success",null);
}
//查询 成功响应
public static Result success(Object data){
return new Result(1,"success",data);
}
//失败响应
public static Result error(String msg){
return new Result(0,msg,null);
}
}
项目开发流程
查看页面原型明确需求 ---> 阅读接口文档 ---> 思路分析 ---> 接口开发 ---> 接口测试 ---> 前后端联调
文件上传
String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小 单位是字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws Exception {
log.info("文件上传: {}, {}, {}", username, age, image);
//获取原始文件名
String originalFilename = image.getOriginalFilename();
//构造唯一文件名 通用唯一识别码uuid
int index = originalFilename.lastIndexOf(".");
String extname = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString() + extname;
log.info("新的文件名: {}", newFileName);
//将文件存储在服务器的磁盘目录中
image.transferTo(new File("C:\\Develop\\" + newFileName));
return Result.success();
}
}
在SpringBoot中,文件上传默认单个文件允许最大大小为1M,如果需要上传大文件,可以进行如下配置:
#配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB
yml配置文件
配置格式
SpringBoot提供了多种属性配置方式
application.properties
server.port=8080
server.address=127.0.0.1
application.yml / application.yaml
server:
port: 8080
address: 127.0.0.1
基本语法
大小写敏感
数值前面必须有空格,作为分隔符
使用缩进表示层级关系,缩进时不允许使用Tab键,只能用空格(IDEA中会自动将Tab转换为空格)
缩进的空格数目不重要,只要相同层级的元素左端对齐即可
#表示注释,从这个字符一直到行尾都会被解释器忽略
yml数据格式
对象/Map集合
user:
name: zhangsan
age: 18
password: 123456
数组/List/Set集合
hobby:
- java
- game
- sport
配置优先级
- 命令行参数(--xxx=xxx)
- Java系统属性(-Dxxx=xxx)
- application.properties
- application.yml
- application.yaml
@ConfigurationProperties与@Value
相同点
都是用来注入外部配置的属性
不同点
@Value注解只能一个一个的进行外部属性的注入
@ConfigurationProperties可以批量地将外部的属性配置注入到bean对象的属性中
登录校验

登陆标记:用户登陆成功后每一次请求中都可以获取到该标记
统一拦截:过滤器Filter、拦截器Interceptor
会话技术
会话
用户打开浏览器访问web服务器的资源,会话建立,直到有一方断开连接,会话结束
在一次会话中可以包含多次请求和响应
会话跟踪
一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
会话跟踪方案
客户端会话跟踪技术:Cookie

优点:HTTP协议中支持的技术
缺点:移动端APP无法使用Cookie、不安全,用户可以自己禁用Cookie、Cookie不能跨域(跨域区分三个维度:协议、IP/域名、端口)
服务端会话跟踪技术:Session

优点:存储在服务器,较为安全
缺点:服务器集群环境下无法直接使用Session,Cookie的缺点
令牌技术

优点:支持PC端、移动端,解决集群环境下的认证问题,减轻服务器端存储压力
缺点:需要自己实现
JWT令牌
JWT
JWT,JSON Web Token,定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息
由于数字签名的存在,这些信息是可靠的
组成

第一部分:Header(头),记录令牌类型、签名算法等
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等
第三部分:Signature(签名),防止Token被篡改、确保安全性,将Header、Payload融合并加入指定密钥,通过指定签名算法计算而来
登录认证场景
①登录成功后,生成令牌
②后续每个请求都要携带JWT令牌,系统在每次请求处理之前先校验令牌,通过后再处理
生成与解析
/**
* 生成JWT
*/
@Test
public void testGenJwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("name", "tommy");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "magicshushu") //签名算法
.setClaims(claims) //自定义内容(载荷)
.setExpiration(new Date(System.currentTimeMillis() + 3600*1000)) //设置有效期为1h
.compact();
System.out.println(jwt);
}
/**
* 解析JWT
*/
@Test
public void testParseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("magicshushu")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tbXkiLCJpZCI6MSwiZXhwIjoxNzUxMDEzNzI1fQ.g9OeA7ok0rOcZhuRsV67N5T2h4a7gjNgwRtyTIkwgUY")
.getBody();
System.out.println(claims);
}
JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的
如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法
过滤器Filter
Filter过滤器是JavaWeb三大组件(Servlet、Filter、Listener)之一
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
过滤器一般完成一些通用的操作,如登录校验、统一编码处理、敏感字符处理等

| 拦截路径 | urlPatterns值 | 含义 |
|---|---|---|
| 拦截具体路径 | /login | 只有访问/login路径时才会被拦截 |
| 目录拦截 | /emps/* | 访问/emps下的所有资源都会被拦截 |
| 拦截所有 | /* | 访问所有资源都会被拦截 |
一个Web应用中可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序

拦截器Interceptor
拦截器Interceptor是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
拦截路径
拦截器可以根据需求,配置不同的拦截路径:
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
| 拦截路径 | 含义 | 举例 |
|---|---|---|
| /* | 一级路径 | 能匹配/depts,/emps,/login 不能匹配/depts/1 |
| /** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
| /depts/* | /depts下的一级路径 | 能匹配/depts/1 不能匹配/depts,/depts/1/2 |
| /depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 不能匹配/emps/1 |
拦截器执行流程

Filter与Interceptor
接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源
异常处理
全局异常处理器

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class) //捕获所有异常
public Result ex(Exception e) {
e.printStackTrace();
return Result.error(e.getMessage());
}
}
@RestControllerAdvice = @ControllerAdvice + @ResponseBody

浙公网安备 33010602011771号