SpringBoot
SpringBoot
请求和响应
请求
- 普通
//springboot方式
@RequestMapping("/p2")
public String parameter2(String name,Integer age) {
System.out.println(name);
System.out.println(age);
return "OK2";
}
- 参数名和请求参数名不一致
@RequestMapping("/p3")
public String parameter3(@RequestParam(value = "name", required = false) String username, Integer age) {
//@RequestParam服务端接受的参数根客户端传递的参数名字不同
//如果带了@RequestParam 默认必须携带这个参数否则会报错
//但可以设置可以不携带只要在这个注解中加一个参数required = false, 即@RequestParam(value = "name", required = false) String username
System.out.println(username);
System.out.println(age);
return "OK3";
}
- 实体类参数
//实体类参数
@RequestMapping("/p4")
public String parameter4(User user) {
// http://localhost:8080/p4?name=Tom&age=22&address.city=北京&address.street=胡同
System.out.println(user);
return "OK4";
}
- 数组参数
//数组类参数
@RequestMapping("/p5")
public String parameter5(String[] hobby) {
// http://localhost:8080/p5?hobby=sing&hobby=dance
System.out.println(Arrays.toString(hobby));
return "OK5";
}
@RequestMapping("/p6")
public String parameter6(@RequestParam List<String> hobby) {
//使用集合需要在前面加@RequestParam 注解
// http://localhost:8080/p6?hobby=sing&hobby=dance
System.out.println(hobby);
return "OK6";
}
- 日期参数
//日期参数
@RequestMapping("/p7")
public String parameter7(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime time) {
//pattern 指定格式
System.out.println(time);
return "OK7";
}
- json
//json
@RequestMapping("/p8")
public String parameter(@RequestBody User user) {
//用User封装了这些json属性, 只要用@RequestBody这个注解就能封装进去
/*
{
"name" : "小明",
"age" : 20,
"address" : {
"city" : "北京",
"street" : "胡同"
}
}
*/
System.out.println(user);
return "OK8";
}
- 路径参数
//路径参数
@RequestMapping("/p9/{id}/{name}")
public String parameter9(@PathVariable int id, @PathVariable String name) {
//http://localhost:8080/p9/1/dong
System.out.println(id);
System.out.println(name);
return "OK9";
}
响应
RestController = RestBody + Controller
(RestBody: 将返回类型是 集合/实体 对象的变成json类型的字符串返回给客户端)
原始方式
// 在之前响应给客户端的类型都不一样 如:
@RequestMapping("/t1")
public List<Address> test(){
List<Address> list = new ArrayList<>();
list.add(new Address("北京","胡同"));
list.add(new Address("湖南","长沙"));
return list;
/* [
{
"city": "北京",
"street": "胡同"
},
{
"city": "湖南",
"street": "长沙"
}
]*/
}
@RequestMapping("/t2")
public Address test2(){
return new Address("湖南","长沙");
/* {
"city": "湖南",
"street": "长沙"
}*/
}
封装响应的数据
Result类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private int code;
private String msg;
private Object data;
public static Result success(Object data) {
return new Result(1, "success", data);
}
}
@RequestMapping("/r1")
public Result test3(){
List<Address> list = new ArrayList<>();
list.add(new Address("北京","胡同"));
list.add(new Address("湖南","长沙"));
return Result.success(list);
}
@RequestMapping("/r2")
public Result test4(){
Address address = new Address("湖南", "长沙");
return Result.success(address);
}
分层解耦
依赖注入
IOC
注解
@Component, @Controller, @Service, @Repository
@SpringBootApplication具有包扫描作用,默认扫描当前包及其子包
1.依赖注入的注解
@Autowired:默认按照类型自动装配。
如果同类型的bean存在多个:
@Primary
@Autowired+@Qualifier("bean的名称")
@Resource(name="bean的名称")
Mybatis
创建项目时需要勾选依赖

编写SQL语句
- 注解
- xml配置文件
@Mapper
public interface BooksMapper {
@Select("select * from books")
List<Books> getAllBooks();
}
案例
分页查询
原始方式
Controller
@GetMapping("/emps")
public Result getEmpByPage(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10")Integer pageSize) {
log.info("员工列表数据的条件分页查询");
System.out.println(page + " " + pageSize);
PageBean pageBean = empService.selectByPage(page, pageSize);
return Result.success(pageBean);
}
Service
PageBean selectByPage(Integer page, Integer pageSize);
@Override
public PageBean selectByPage(Integer page, Integer pageSize) {
Long count = empMapper.countTotal();
Integer start = (page - 1) * pageSize;
List<Emp> list = empMapper.selectByPage(start, pageSize);
return new PageBean(count,list);
}
Mapper
@Select("select * from emp limit #{start},#{pageSize}")
List<Emp> selectByPage(Integer start, Integer pageSize);
@Select("select count(*) from emp")
Long countTotal();
通过分页插件PageHelper
依赖
<!--分页查询依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
Controller
@GetMapping("/emps")
public Result getEmpByPage(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10")Integer pageSize) {
log.info("员工列表数据的条件分页查询: {} {}", page, pageSize);
//分页插件方式
PageBean pageBean = empService.selectByPageAndPageHelper(page,pageSize);
return Result.success(pageBean);
}
Service
PageBean selectByPageAndPageHelper(Integer page, Integer pageSize);
@Override
public PageBean selectByPageAndPageHelper(Integer page, Integer pageSize) {
//设置分页参数
PageHelper.startPage(page,pageSize);
//执行查询
List<Emp> empList = empMapper.selectByPageAndPageHelper();
Page<Emp> p = (Page<Emp>) empList;//强转
return new PageBean(p.getTotal(),p.getResult());
Mapper
@Select("select * from emp")
List<Emp> selectByPageAndPageHelper();
条件分页查询
Controller
@GetMapping("/emps")
public Result getEmpByPageByCondition(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10")Integer pageSize,
String name, short gender,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
log.info("员工列表数据的条件分页查询: {} {} {} {} {} {}", page, pageSize, name, gender, begin, end);
PageBean pageBean = empService.getEmpByPageByCondition(page, pageSize, name, gender, begin, end);
return Result.success(pageBean);
}
Service
PageBean getEmpByPageByCondition(Integer page, Integer pageSize, String name, short gender, LocalDate begin, LocalDate end);
@Override
public PageBean getEmpByPageByCondition(Integer page, Integer pageSize, String name, short gender, LocalDate begin, LocalDate end) {
//设置分页参数
PageHelper.startPage(page,pageSize);
//执行查询
List<Emp> empList = empMapper.selectEmpByPageByCondition(name, gender, begin, end);
Page<Emp> p = (Page<Emp>) empList;//强转
return new PageBean(p.getTotal(),p.getResult());
}
Mapper
//这里需要动态SQL需要xml配置文件
List<Emp> selectEmpByPageByCondition(String name, short gender, LocalDate begin, LocalDate end);
Mapper.xml
<!--
select * from emp
where
name like concat('%', '张', '%')
and gender = 1
and entrydate between '2000-01-01' and '2010-01-01'
order by update_time DESC
-->
<select id="selectEmpByPageByCondition" resultType="com.dong.pojo.Emp">
select * from emp
<where>
<if test="name != null and name != ''">
<!-- concat()字符串拼接函数 -->
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>
数组删除操作
Controller
@DeleteMapping("/{ids}")
public Result deleteEmpById(@PathVariable List<Integer> ids) {
log.info("批量删除员工的数据信息: {}", ids);
empService.deleteEmp(ids);
return Result.success();
}
Service
void deleteEmp(List<Integer> ids);
@Override
public void deleteEmp(List<Integer> ids) {
empMapper.deleteEmp(ids);
}
Mapper
//这里需要动态SQL需要xml配置文件(遍历ids)
void deleteEmp(List<Integer> ids);
Mapper.xml
<delete id="deleteEmp">
delete from emp
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
增加
Controller
@PostMapping
public Result addEmp(@RequestBody Emp emp) {
log.info("新增员工: {}", emp.toString());
empService.addEmp(emp);
return Result.success();
}
Service
@Override
public void addEmp(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setPassword("123456");
empMapper.addEmp(emp);
}
Mapper
@Insert("insert into emp(username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time)" +
"values (#{username},#{password},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
void addEmp(Emp emp);
文件上传
<html>
<head>
<meta charset="utf-8">
<title>文件上传</title>
</head>
<body>
http://localhost:8080/upload.html
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
本地存储
Controller
@PostMapping("/upload")
public Result fileUpload(MultipartFile file) throws IOException {
String path = "C:\\Users\\dong\\Desktop\\Up\\";
log.info("文件开始上传: {}", file.getOriginalFilename());
// UUID
String uuid = UUID.randomUUID().toString();
// 文件类型(后缀)
String fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
// 获取文件名(不带后缀)
String fileName = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf("."));
// 新的文件名
String newFileName = uuid + "_" + fileName + "." + fileType;
System.out.println("新的文件名: " + newFileName);
//写入本地存储
file.transferTo(new File(path + newFileName));
return Result.success();
}
云端存储
阿里云对象存储 OSS(Object Storage Service)是一款云存储服务.
LTAI5t9f7apfJusNv1bVbXap
NM8wJIC79t2riLOib8MidHTdKWyUtb
修改
Controller
@PutMapping
public Result updateEmp(@RequestBody Emp emp) {
log.info("修改员工的数据信息: {}", emp.toString());
empService.updateEmp(emp);
return Result.success();
}
Service
void updateEmp(Emp emp);
@Override
public void updateEmp(Emp emp) {
emp.setUpdateTime(LocalDateTime.now());
empMapper.updateEmp(emp);
}
Mapper
//有些数据可能不传那就不更新
void updateEmp(Emp emp);
Mapper.xml
<update id="updateEmp">
update emp
<set>
<if test="username != null and username != ''">
username = #{username},
</if>
<if test="password != null and password != ''">
password = #{password},
</if>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="image != null and image != ''">
image = #{image},
</if>
<if test="job != null">
job = #{job},
</if>
<if test="entrydate != null">
entrydate = #{entrydate},
</if>
<if test="deptId != null">
dept_id = #{deptId},
</if>
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
where id = #{id}
</update>
配置文件
自定义配置
通过自定义配置文件可以实现全局管理配置
如在 application.properties编写自定义配置
#自定义配置
config.data.name=dong
config.data.password=dg123456
通过spring注入的方式
@Value("${ 配置文件中的键 }")
ReadConfigFile
@Component
public class ReadConfigFile {
@Value("${config.data.name}")
private String name;
@Value("${config.data.password}")
private String password;
public String getName() {
return name;
}
public String getPassword() {
return password;
}
}
测试
@SpringBootTest
public class ReadConfigFileTest {
@Autowired
private ReadConfigFile readConfigFile;
@Test
public void testReadConfigFile() {
// 使用 Spring 容器管理的 Bean
System.out.println("Name: " + readConfigFile.getName());
System.out.println("Password: " + readConfigFile.getPassword());
}
}
yml配置
yml 和 yaml 本质上没有区别
.yaml是 YAML 官方推荐的扩展名,也是更现代的标准写法。.yml是 YAML 文件的简写形式,方便书写和早期开发者习惯。
官方文档建议使用 .yaml,但 .yml 仍然广泛支持。
语法
#这是一段注释
#普通key value键值对
name: 董建成
#对象
user:
name: 小明
age: 30
#行内写法
user: {name: 小明,age: 30}
#数组
hobby:
- sing
- dance
#行内写法
hobby: [sing,dance]
通过springboot自动注入自定义配置(yaml)
之前的每个属性都要定义一个@value很麻烦
@Value("${config.data.name}")
private String name;
@Value("${config.data.password}")
private String password;
现在通过自动注入
#自定义配置
config:
data:
name: dong
password: dg123456
age: 20
prefix的值与yaml配置文件的层级关系一致用.分割
@Component
@Data
@ConfigurationProperties(prefix = "config.data")
public class CustomizeConfig {
private String name;
private String password;
private int age;
}
测试
@SpringBootTest
public class ReadConfigFileTest {
@Autowired
private CustomizeConfig customizeConfig;
@Test
public void testReadConfigFile() {
// 使用 Spring 容器管理的 Bean
System.out.println("Name: " + customizeConfig.getName());
System.out.println("Password: " + customizeConfig.getPassword());
System.out.println("Age: " + customizeConfig.getAge());
}
}
登录校验
只返回信息
Controller
@PostMapping
public Result login(@RequestBody Emp emp) {
log.info("员工登录: {}", emp);
Emp empForLogin = empService.login(emp);
return empForLogin == null ? Result.error("用户名或密码错误") : Result.success();
}
Service
@Override
public Emp login(Emp emp) {
return empMapper.selectLogin(emp);
}
Mapper
@Select("select * from emp where username = #{username} and password = #{password}")
Emp selectLogin(Emp emp);
会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
- Cookie
移动端无法用Cookie
不安全
不能跨域(前端工程在一个服务器,后端在一个服务器)
- Session
服务器集群无法使用(多台服务器存同一份后台 通过 负载均衡均匀 将请求到不同服务器)
- 令牌技术(JWT)
JWT
- 全称: JSON Web Token (https://jwt.io/)
- 定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
- 组成:
- 第一部分:Header(头),记录令牌类型、签名算法等。例如:
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:
- 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
) secret base64 encoded
使用JWT令牌
导入依赖
<!--JWT令牌 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成JWT令牌
@Test
public void jwtTest(){
Map<String,Object> map = new HashMap<>();
map.put("username","dong");
map.put("password","123456");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,"dongjiancheng")//签名算法
.setClaims(map)//自定义内容
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期1h
.compact();
System.out.println(jwt);
//eyJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMzQ1NiIsImV4cCI6MTczNDgwMjg0NCwidXNlcm5hbWUiOiJkb25nIn0.vsHeppZXMGMLBZmSQgKhvXcL1xRvWXuvmyG4faEawMA
}
解析JWT令牌
@Test
public void jwtTest2(){
Claims claims = Jwts.parser()
.setSigningKey("dongjiancheng")//指定签名秘钥
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMzQ1NiIsImV4cCI6MTczNDgwMjYxMiwidXNlcm5hbWUiOiJkb25nIn0.gBP873wH5DddTvDCu0zmj5GvCKtEnwGHlm3EfzAkWtA")
//解析令牌
.getBody();
System.out.println(claims);
//{password=123456, exp=1734802612, username=dong}
}
登录成功返回jwt令牌
@PostMapping
public Result login(@RequestBody Emp emp) {
log.info("员工登录: {}", emp);
Emp empForLogin = empService.login(emp);
//登录成功
if(empForLogin != null) {
Map<String, Object> data = new HashMap<>();
data.put("id", empForLogin.getId());
data.put("name", empForLogin.getName());
data.put("username", empForLogin.getUsername());
String jwt = JwtUtil.buildJWT(data);
return Result.success(jwt);
}
//登录失败
return Result.error("用户名或密码错误");
}
过滤器
简单实现
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("初始化");
}
@Override
public void destroy() {
Filter.super.destroy();
System.out.println("销毁");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截前");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("拦截后");
}
}
在启动类加@ServletComponentScan注解
@ServletComponentScan//filter 需要这个注解
@SpringBootApplication
public class Springboot04DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot04DemoApplication.class, args);
}
}
放行后访问对应资源,资源访问完成后,还会回到Filter中吗? 会
如果回到Filter中,是重新执行还是执行放行后的逻辑呢?执行放行后逻辑
过滤器链
一个web应用中可以使用多个过滤器,形成一个链条

多个过滤器的执行顺序默认是按类名排序的
或者指定顺序@Order(1) // 数值越小优先级越高
登录过滤

@Log4j2
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url{}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("login")){
log.info("登录操作: 放行");
filterChain.doFilter(servletRequest, servletResponse);
return;
}
//3.获取请求头中的令牌(token)
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){//是否为空串
log.info("请求头为空: ");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtil.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("解析令牌失败");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//6.放行
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest, servletResponse);
}
}
如果不登录,其他请求到会跳转到登录页面
拦截器(Interceptor)
简单实现
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标方法执行前执行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("目标方法执行前执行");
return true;
}
@Override //目标方法执行后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("目标方法执行后执行");
}
@Override //视图渲染完毕后,最后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("视图渲染完毕后,最后执行");
}
}
配置
@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor)
.addPathPatterns("/**") //需要拦截哪些资源
.excludePathPatterns("/login"); //不需要拦截哪些资源
}
}


接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现Handlerlnterceptor接口。
拦截范围不同:过滤器Filter会拦截所有的资源,而lnterceptor只会拦截Spring环境中的资源。
用拦截器实现登录校验
@Log4j2
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标方法执行前执行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url{}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("login")){
log.info("登录操作: 放行");
return true;
}
//3.获取请求头中的令牌(token)
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
if(!StringUtils.hasLength(jwt)){//是否为空串
log.info("请求头为空: ");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//5.解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtil.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("解析令牌失败");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//6.放行
log.info("令牌合法,放行");
return true;
}
}
异常处理
- 在每个Controller里都捕获异常抛出(很麻烦)
- 全局异常处理器

定义全局异常管理器
//全局异常处理器
@Log4j2
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Result exception(Exception e) {
log.error("出现异常: {}" ,e.getMessage());
return Result.error("操作失败");
}
}
有了统一的返回信息,前端就能解析异常信息给出提示,弹窗等
事物管理
在删除部门时同时删除该部门下的员工
@Transactional注解
//这里要开启事物管理
@Transactional //交给spring进行事物管理
//方法开始:事物开启 方法结束:事物提交 出现异常:回滚事物
//还可以在类,接口上
@Override
public void deleteByID(int id) {
deptMapper.deleteByID(id);//删除部门
//int i = 1 / 0; 假设抛出异常
empMapper.deleteEmpByDeptID(id);//删除部门下的员工
}
#开启事物管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
但是在默认情况下只有运行时异常才会进行回滚
@Transactional
@Override
public void deleteByID(int id) throws Exception {
deptMapper.deleteByID(id);//删除部门
if (true){
throw new Exception("出错");
//但是在这样的情况下不会回滚事物,不是运行时异常
}
empMapper.deleteEmpByDeptID(id);//删除部门下的员工
}
此时只要在@Transactional后加一个属性
@Transactional(rollbackFor = Exception.class)
此时什么异常都会进行事物回滚
传播行为
如有以下的业务
@Transactional
public void A(){
//此时有异常出现
B();
}
@Transactional
private void B() {
}
如果A有异常出现,那B是否回滚,还是B是一个单独的事务
可以通过 @Transactional的参数设置
- REQUIRED 默认
- SUPPORTS 支持
- MANDATORY 强制
- REQUIRES_NEW 新建
- NOT_SUPPORTED 不支持
- NEVER 从不
- NESTED 嵌套
AOP
依赖
<!--AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
入门(记录操作时间)
编写程序使得能够得到每个方法的执行时间
@Log4j2
@Aspect //AOP类
@Component
public class TimeAspect {
@Around("execution(* com.dong.service.*.*(..))") //在哪些方法上执行此操作
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info(joinPoint.getSignature() + "执行耗时: {}ms", endTime - startTime);
return result;
}
}
概念

通知类型

测试
public void test(){
System.out.println("============================================");
int i = 1/0;
System.out.println("方法开始");
System.out.println("============================================");
}
@Component
@Log4j2
@Aspect
public class MyAspect {
@Around("execution(* com.dong.aop.TestService.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始前");
Object result = joinPoint.proceed();
System.out.println("环绕通知结束");
return result;
}
@Before("execution(* com.dong.aop.TestService.test(..))")
public void before() {
System.out.println("前置通知");
}
@After("execution(* com.dong.aop.TestService.test(..))")
public void after() {
System.out.println("后置通知");
}
@AfterReturning("execution(* com.dong.aop.TestService.test(..))")
public void afterReturning() {
System.out.println("返回后通知");
}
@AfterThrowing("execution(* com.dong.aop.TestService.test(..))")
public void afterThrowing() {
System.out.println("异常后通知");
}
}
@SpringBootTest
class Springboot04DemoApplicationTests {
@Autowired
private TestService testService;
@Test
void contextLoads() {
testService.test();
}
}
切入点表达式提取
将execution抽取成一个方法,其他引用这个方法
如果为public则其他包下也能引用此切入点
@Component
@Log4j2
@Aspect
public class MyAspect {
@Pointcut("execution(* com.dong.aop.TestService.test(..))")
private void pointCut() {}
//public void pointCut() {} 如果为public则其他包下也能引用此切入点
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知开始前");
Object result = joinPoint.proceed();
System.out.println("环绕通知结束");
return result;
}
@Before("pointCut()")
public void before() {
System.out.println("前置通知");
}
@After("pointCut()")
public void after() {
System.out.println("后置通知");
}
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("返回后通知");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("异常后通知");
}
}
通知执行顺序
默认是按类名的顺序执行的

但可以通过@Order(数字)注解指定顺序
@Aspect
@Component
@Order(1) //指定顺序
public class TimeAspect {
//...
}
切入点表达式
execution

@anaotation注解
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAop {
}
在方法上加上自定义注解
@MyAop
public void test(){
System.out.println("============================================");
System.out.println("方法开始");
System.out.println("============================================");
}
用@anaotation注解匹配加了注解的方法
@Component
@Aspect
public class MyAspect {
@Pointcut("@annotation(com.dong.aop.MyAop)")
public void pointCut() {}
@Before("pointCut()")
public void before() {
System.out.println("前置通知");
}
}
连接点
通过连接点获取方法执行时的相关信息
@MyAop
public Integer test(Integer a, Integer b) {
System.out.println("运行结果:" + (a+b));
return a + b;
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("==========环绕通知开始============");
// 获取目标对象的类名
String className = joinPoint.getTarget().getClass().getName();
System.out.println("目标对象的类名: " + className);
// 获取目标方法的方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("目标方法的方法名: " + methodName);
// 获取目标方法运行时传入的参数
Object[] args = joinPoint.getArgs();
System.out.println("目标方法运行时传入的参数: " + Arrays.toString(args));
// 放行目标方法执行
Object result = joinPoint.proceed();
// 获取目标方法的返回值
System.out.println("目标方法的返回值: " + result);
System.out.println("==========环绕通知结束============");
// 返回目标方法的执行结果
return result;
}
==========环绕通知开始============
目标对象的类名: com.dong.aop.TestService
目标方法的方法名: test
目标方法运行时传入的参数: [1, 4]
运行结果:5
目标方法的返回值: 5
==========环绕通知结束============
可以修改传入的参数
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("==========环绕通知开始============");
// 获取目标方法运行时传入的参数
Object[] args = joinPoint.getArgs();
System.out.println("目标方法运行时传入的参数: " + Arrays.toString(args));
//修改参数
args[0] = 1;
args[1] = 10;
// 放行目标方法执行
Object result = joinPoint.proceed(args);
// 获取目标方法的返回值
System.out.println("目标方法的返回值: " + result);
System.out.println("==========环绕通知结束============");
// 返回目标方法的执行结果
return null;
}
==========环绕通知开始============
目标方法运行时传入的参数: [1, 4]
运行结果:11
目标方法的返回值: 11
==========环绕通知结束============
通过AOP记录操作日志

准备工作
-- 操作日志表
CREATE TABLE operate_log (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
operate_user INT UNSIGNED COMMENT '操作人ID',
operate_time DATETIME COMMENT '操作时间',
class_name VARCHAR(100) COMMENT '操作的类名',
method_name VARCHAR(100) COMMENT '操作的方法名',
method_params VARCHAR(1000) COMMENT '方法参数',
return_value VARCHAR(2000) COMMENT '返回值',
cost_time BIGINT COMMENT '方法执行耗时,单位:ms'
) COMMENT '操作日志表';
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id;//ID
private Integer operateUser;//操作人ID
private LocalDateTime operateTime;//操作时间
private String className;//操作类名
private String methodName;//操作方法名
private String methodParams;//操作方法参数
private String returnValue;//操作方法返回值
private Long costTime;//操作耗时
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
编写切面类
@Log4j2
@Aspect
@Component
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.dong.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID(通过spring注入的方式获取HttpServletRequest获取令牌,获取id)
String jwt = request.getHeader("token");
Claims claims = JwtUtil.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().getName();
//方法名
String methodName = joinPoint.getSignature().getName();
//方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
Long begin = System.currentTimeMillis();//开始时间
//调用原始方法运行
Object result = joinPoint.proceed();
Long end = System.currentTimeMillis();//结束时间
//方法返回值
String returnValue = JSONObject.toJSONString(result);
//操作时间
Long costTime = end - begin;
OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
log.info("AOP记录操作日志: {}",operateLog);
operateLogMapper.addOperateLog(operateLog);
return result;
}
}
OperateLogMapper
@Mapper
public interface OperateLogMapper {
@Insert("INSERT INTO operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"VALUES (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime})")
public void addOperateLog(OperateLog operateLog);
}
在增删改的controller方法上加上@Log注解
@PostMapping
@Log
public Result addDept(@RequestBody Dept dept){
log.info("添加部门数据: " + dept);
deptService.addDept(dept);
return Result.success();
}
@PutMapping
@Log
public Result updateDept(@RequestBody Dept dept){
log.info("修改部门数据" + dept);
deptService.updateDept(dept);
return Result.success();
}
配置优先级
在项目中有三种配置文件
- properties
- yml
- yaml
外部有两种配置
- java系统属性
- 命令行参数
java -Dserver.port=9000 项目的.jar包 -jar --server.port=10000
# java系统属性 命令行参数
#java系统属性: 用-D为前缀 加 key=value
#命令行参数: 用--为前缀 加 key=value
优先级为(从大到小)
命令行参数 > java系统属性 > yaml > yml > properties
bean管理
获取bean
注册bean
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "test";
}
}
三种获取方式
@SpringBootTest
class Springboot05BeanApplicationTests {
//注入获取bean的对象
@Autowired
private ApplicationContext applicationContext;
@Test
void getBean() {
//通过名字获取bean
TestController testController1 = (TestController) applicationContext.getBean("testController");
//没有指定名字就是默认类名首字母小写
System.out.println(testController1);
//通过类获取bean
TestController testController2 = applicationContext.getBean(com.dong.controller.TestController.class);
System.out.println(testController2);
//通过名字和类获取bean
TestController testController3 = applicationContext.getBean("testController", com.dong.controller.TestController.class);
System.out.println(testController3);
//com.dong.controller.TestController@b379bc6
//com.dong.controller.TestController@b379bc6
//com.dong.controller.TestController@b379bc6
}
}
bean作用域

通过 @Scope("作用域") 注解声明作用域
测试getBean
- 设置为singleton
//若设置作用域为singleton
@Scope("singleton")
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "test";
}
}
@Test
void getBean2() {
for (int i = 0; i < 100; i++) {
//通过类获取bean
TestController testController2 = applicationContext.getBean(com.dong.controller.TestController.class);
System.out.println(i + " :" + testController2);
//0 :com.dong.controller.TestController@5a4d4f9c
//1 :com.dong.controller.TestController@5a4d4f9c
//2 :com.dong.controller.TestController@5a4d4f9c
//3 :com.dong.controller.TestController@5a4d4f9c
//4 :com.dong.controller.TestController@5a4d4f9c
//5 :com.dong.controller.TestController@5a4d4f9c
//.....
}
}
- 设置作用域为prototype
//若设置作用域为prototype
@Scope("prototype")
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "test";
}
}
@Test
void getBean2() {
for (int i = 0; i < 100; i++) {
//通过类获取bean
TestController testController2 = applicationContext.getBean(com.dong.controller.TestController.class);
System.out.println(i + " :" + testController2);
//0 :com.dong.controller.TestController@5a4d4f9c
//1 :com.dong.controller.TestController@153d6d74
//2 :com.dong.controller.TestController@6c9b44bf
//3 :com.dong.controller.TestController@299b9851
//4 :com.dong.controller.TestController@191a0351
//5 :com.dong.controller.TestController@67328bcb
//.....
}
}
第三方bean
如要读取xml文件时
//依赖
<!--读取xml -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.4</version>
</dependency>
创建一个配置类用来注册第三方bean(也可以在springboot主启动类注册bean)\
@Configuration
public class ComponentConfig {
//注册第三方bean
@Bean
//注册的bean默认是方法名
public SAXReader saxReader() {
return new SAXReader();
}
}
测试
@Autowired
private SAXReader sax;
@Test
void getBean3(){
try {
// 你可以在这里使用 sax 来读取 XML 文件
// 例如读取某个 XML 文件
Document document = sax.read("D:\\CODE\\java\\JavaWeb\\StudySpringBoot\\springboot-05-bean\\src\\main\\resources\\test.xml");
System.out.println(document.asXML()); // 打印整个 XML 文件的内容
} catch (Exception e) {
e.printStackTrace();
}
}
springboot原理
起步依赖
自动装配
如需要引入第三方依赖的类加到ioc容器中
1.引入依赖
2.导入包
//1
@ComponentScan(("com.example", "com.itheima"))
//2普通类
@Import(JwtUtil.class)
//3导入配置类
@Import(WebConfig.class)
//4导入实现了接口的类
@Import(MyImportSelector.class)
//5.导入第三方依赖定义好导入什么包的注解(@EnableHeaderConfig)
@EnableHeaderConfig
@SpringBootApplication
public class Springboot06PrincipleApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot06PrincipleApplication.class, args);
}
}

浙公网安备 33010602011771号