Spring boot学习记录

1.快速入门

简单例子

需求:使用Spring boot开发一个web应用,浏览器发起请求 /hello 后,给浏览器返回字符串 “hello world” 。

浏览器中输入:http://localhost:8080/hello
步骤:

  1. 创建springboot工程,并勾选web开发相关依赖
  2. 定义HelloController类,添加方法hello,并添加注解
  3. 运行测试

创建一个请求处理类:HelloController

package com.lonely.springboottest.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 请求处理类
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello(){
        System.out.println("hello world");
        return "hello";
    }
}

浏览器中访问
在这里插入图片描述

2.传参:

简单参数

http://localhost:8080/test?name=Tom&age=10
参数名与形参变量名相同,定义形参即可接收参数。
接收过程中会自动进行类型转换

	@RequestMapping("/simpleParams")
    public String simpleParams(String name,int age){  // 
        System.out.println("前端传来的参数为:"+name+":"+age);
        return "已收到前端传来的参数";
    }

在这里插入图片描述
post请求也一样,在表单中输入数据即可
在这里插入图片描述
如果方法形参名称与请求参数名称不匹配,可以使用@RequestParam完成映射,@RequestParam中的required属性默认为true,代表该请求参数必须传递,如果不传递将报错,如果该参数是可选的,可以将required属性设置为false

@RequestMapping("/simpleParams2") 
public String simpleParams2(@RequestParam(name="name",required=true)String username,Integer age){
	// 会将地址栏中的name参数映射到username形参上
   System.out.println("接收到来自前端的数据:"+username+":"+age);
   return "前端的数据:"+username+":"+age;
}

在这里插入图片描述

实体参数

简单实体对象:请求参数名与形参对象属性名相同,定义POJO接收即可

@RequestMapping("/simplePojo")
public String simplePojo(User user){
System.out.println(user);
return "OK";
}
public class User{
	private String name;
	private Integer age;
}

在这里插入图片描述
复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。

@RequestMapping("/complexPojo")
public String complexPojo(User user){
System.out.println(user);
return "已接受到参数:"+user;
}
public class User{
	private String name;
	private Integer age;
	private Address address;
}
public class Address {
    private String province;
    private String city;
}

浏览器访问:http://localhost:8080/complexPojo?name=Tom&age=22&address.province=河南省&address.city=郑州
在这里插入图片描述

数组集合参数

数组参数:
请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

    @RequestMapping("/arrayParam")
    public String arrayParam(String hobby[]){
        for (String s : hobby) {
            System.out.print(s+" ");
        }
        return "ok";
    }

浏览器访问:http://localhost:8080/arrayParam?hobby=game&hobby=java
在这里插入图片描述

集合参数:
请求参数名与形参中集合变量名相同,通过@RequestParam绑定参数关系

    @RequestMapping("/listParam")
    public String listParam(@RequestParam List<String> hobbies){
        for (String s : hobbies) {
            System.out.print(s+" ");
        }
        return "ok";
    }

浏览器访问:http://localhost:8080/listParam?hobbies=game&hobbies=java
在这里插入图片描述

日期参数

使用 @DateTimeFormat 注解完成日期参数格式转换

浏览器中输入:
http://localhost:8080/dateParam?updateTime=2022-12-12 10:05:34

@RequestMapping("/dateParam")
public String dateParam(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")LocalDateTime updateTime){
System.out.println(updateTime);
    return "后端已收到数据"+updateTime;
}

在这里插入图片描述

JSON参数

JSON参数:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用 @RequestBody 标识

    @RequestMapping("/jsonParam")
    public String jsonParam(@RequestBody User user){
        System.out.println(user);
        return user.toString();
    }

在这里插入图片描述

路径参数

路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数

http://localhost:8080/path/1

    @RequestMapping("/path/{id}")
    public String pathParam(@PathVariable Integer id){
        System.out.println(id);
        return "后端已接受到:"+id.toString();
    }

在这里插入图片描述

传递多个参数:
http://localhost:8080/pathParam/1/tom

    @RequestMapping("/pathParam/{id}/{name}")
    public String pathParam(@PathVariable Integer id,@PathVariable String name){
        System.out.println(id+name);
        return "后端已接受到:id="+id.toString()+",name="+name;
    }

在这里插入图片描述

3.响应数据

@ResponseBody

类型:方法注解、类注解
位置:Controller方法上/类上
作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
说明:@RestController = @Controller+@ResponseBody

    @RequestMapping("/responseTest1")
    public List<User> responseTest1(){
        User u1 = new User("张三",22,new Address("河南省","郑州市"));
        User u2 = new User("李四",23,new Address("广东省","广州市"));
        User u3 = new User("王五",21,new Address("甘肃省","兰州市"));
        List<User> list = new ArrayList<>();
        list.add(u1);
        list.add(u2);
        list.add(u3);
        return list;
    }

在这里插入图片描述

统一响应结果

需要有一个统一的类作为响应结果

public class Result {
    private Integer code;
    private String msg;
    private Object data;
    public Result() {}

    public Result(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }


    public static Result success(Object data){
        return new Result(1,"success",data);
    }

    public static Result success(){
        return new Result(1,"success",null);
    }
    public static Result error(String msg){
        return new Result(0,msg,null);
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}
    @RequestMapping("/responseTest1")
    public Result responseTest1(){
        User u1 = new User("张三",22,new Address("河南省","郑州市"));
        User u2 = new User("李四",23,new Address("广东省","广州市"));
        User u3 = new User("王五",21,new Address("甘肃省","兰州市"));
        List<User> list = new ArrayList<>();
        list.add(u1);
        list.add(u2);
        list.add(u3);
        return Result.success(list);
    }

浏览器输入:localhost:8080/responseTest1
在这里插入图片描述

4.分层解耦

三层架构

在这里插入图片描述

  • Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
  • Service:业务逻辑层,处理具体的业务逻辑
  • Dao:数据访问层(持久层),负责数据访问操作,包括数据的增、删、改、查

分层解耦

内聚:软件中各个功能模块内部的功能联系
耦合:衡量软件中各个层/模块之间的依赖、关联的程度
软件设计原则:高内聚低耦合

控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
Bean对象:IOC容器中创建、管理的对象,称之为bean

IOC

IOC&DI入门

  1. Service层及Dao层的实现类,交给IOC容器管理
  2. 为Controller及Service注入运行时,依赖的对象
  3. 运行测试
@RestController  
public class UserController {  // 用户控制层
	@AutoWired  //运行时,IOC容器会提供该类型的bean对象,并赋值给改变了--依赖注入
    private UserService userService;
    @RequestMapping("/responseTest1")
    public Result responseTest1(){
        List<User> list = userService.listUser();
        return Result.success(list);
    }
}
@Component  // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class UserServiceA implements UserService {  // 用户服务层
	@AutoWired  //运行时,IOC容器会提供该类型的bean对象,并赋值给改变了--依赖注入
    private UserDao userDao;
    @Override
    public List<User> listUser() {
        List<User> list = userDao.users();
        // 数据处理
        return list;
    }
}
@Component  // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class UserDaoA implements UserDao {  // 用户Dao层
    @Override
    public List<User> users() {
        User u1 = new User("张三",22,new Address("河南省","郑州市"));
        User u2 = new User("李四",23,new Address("广东省","广州市"));
        User u3 = new User("王五",21,new Address("甘肃省","兰州市"));
        List<User> list = new ArrayList<>();
        list.add(u1);
        list.add(u2);
        list.add(u3);
        return list;
    }
}

Bean的声明:
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

注解说明位置
@Component声明bean的基础注解不属于以下三类时,用此注解
@Controller@Component的衍生注解标注在控制器类上
@Service@Component的衍生注解标注在业务类上
@Repository@Component的衍生注解标注在数据访问类上(由于与mybatis整合,用的少)

注意:

  • 声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写
  • 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller

@SpringBootApplication具有包扫描作用,默认扫描当前包及其子包

Bean注入
@Autowired注解,默认是按照类型进行,如果存在多个相同类型的bean,将会报错

通过以下几种解决方案来解决:

@Primary
@Autowired+@Qualifier(“bean的名称”)
@Resource(name=“bean的名称”)

@Primary
@Service
public class UserServiceA implements UserService{
}
@RestController
public class EmpController{
	@Resource(name="userServiceA")
	private UserService userServce;
}
@RestController
public class EmpController{
	@Autowired
	@Qualifier("userServiceA")
	private UserService userService;
}

@resource和@Autowired区别:

  • @Autowired是spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired默认是按照类型注入,而@Resource默认是按照名称注入

5.发送请求

GET请求

通过@RequestMapping或者@GetMapping实现

@RestController
public class DeptController {
//  第一种   @RequestMapping(value = "/depts",method = RequestMethod.GET)
	// 第二种
    @GetMapping("/depts")
    public Result list(){
		// 代码块
        return Result.success();
    }
}

6.springboot常用配置选项

可以通过@Value注解将外部的属性注入,具体用法为:@Value(“${配置文件中的key}”)

springboot中提供了多种属性配置方式

1.application.properties

server.port=8080
server.address=127.0.0.1

2.application.yml或application.yaml

server:
	port:8080
	address:127.0.0.1

企业开发当中使用的大多为第2种方式
yml基本语法:

  1. 大小写敏感
  2. 数值前边必须有空格,作为分隔符
  3. 使用缩进表示层级关系,缩进时不允许使用tab键,只能用空格
  4. 缩进的空格不重要,只要相同层级的元素左侧对其即可
  5. #表示注释,从这个字符一直到行尾,都会被解析器忽略

yml数据格式:
1.对象/Map集合:

user:
 name:zhangsan
 age:18
 password:123456

2.数组/List/Set集合:

languages:
 -java
 -golang
 -python

文件上传相关配置:

spring:
  servlet:
    multipart:
      max-file-size: 10MB #配置单个上传文件的最大文件大小
      max-request-size: 100MB #配置一次请求可以上传文件的总大小

将配置项直接注入到类中的属性当中:

使用@ConfigurationProperrties(prefix=“”)注解

一个例子
aliyun:
  oss:
    endpoint: https://oss-cn-beijing.aliyuncs.com
    accessKeyId: ${env.OSS_ACCESS_KEY_ID}
    accessSecret: ${env.OSS_ACCESS_KEY_SECRET}
    bucketName: lonely-bucket
@Data
@Component
@ConfigurationProperties(prefix="aliyun.oss")
public class AliOssUtils{
	private String endPoint;
	private String accessKeyId;
	private String accessKeySecret;
	private String bucketName;
}

@Value与@ConfigurationProperties区别:

相同点:
都是用来注入外部配置的属性的

不同点:
@Value只能一个个的进行外部注入
@ConfigurationProperties可以批量的将外部的属性配置注入到bean对象的属性中

7.会话跟踪技术

1.常用的会话跟踪技术

1.cookie的使用

示例用法:

 @GetMapping("/setcookie")
    public Result setCookie(HttpServletResponse response){
        response.addCookie(new Cookie("login_username","sssssskkkkkk"));
        return Result.success();
    }

    @GetMapping("/getcookie")
    public Result getCookie(HttpServletRequest request){
        Cookie [] cookies = request.getCookies();
        String login_username = "";
        String login_username_value = "";
        for (Cookie cookie : cookies){
            if(cookie.getName().equals("login_username")){
                login_username = cookie.getName();
                login_username_value = cookie.getValue();
            }
        }
        return Result.success(new Cookie(login_username,login_username_value));
    }

在这里插入图片描述

在这里插入图片描述
cookie的优缺点:
优点:HTTP协议中支持的技术

缺点:

  1. 移动端app无法使用cookie
  2. 不安全,用户可以自己禁用cookie
  3. Cookie不能跨域

2.session的使用

示例:

    @GetMapping("/setsession")
    public Result setsession(HttpSession session){
        session.setAttribute("loginuser","jack");
        return Result.success("成功");
    }

    @GetMapping("/getsession")
    public Result getsession(HttpServletRequest request){
        HttpSession session = request.getSession();
        Object loginuser = session.getAttribute("loginuser");
        return Result.success(loginuser);
    }

在这里插入图片描述
在这里插入图片描述
session的优缺点:
优点:存储在服务端,安全

缺点:
服务器集群环境下无法直接使用session
移动端app无法使用session
Session不能跨域

3.令牌技术(主流方案)

优点:

  1. 支持PC端,移动端
  2. 解决集群环境下的认证问题
  3. 减轻服务器端存储压力

缺点:需要自己实现

1.JWT令牌

定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

组成:

  • 第一部分:Header(头),记录令牌类型、签名算法等。例如:{“alg”:“HS256”,“type”:“JWT”}
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{“id”:“1”,“username”:“tom”}
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定密钥,通过指定签名算法计算而来。

JWT-生成:
引入依赖:

   <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt</artifactId>
       <version>0.9.1</version>
   </dependency>

生成:

@Test
public void testGenJwt(){
    Map<String,Object> claims = new HashMap<>();
    claims.put("name","zhangsanfeng");
    claims.put("id",1);
    String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256,"lonely") //签名算法
        				.setClaims(claims) // 自定义内容(载荷)
        				.setExpiration(new Date(System.currentTimeMillis()+1000*3600)) // 设置1小时后过期
        				.compact();
    System.out.println(jwt);
}

校验JWT令牌:

@Test
public void testParseJwt(){
    Claims claims =  Jwts.parser()
            .setSigningKey("lonely") // 指定签名密钥
            .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW5mZW5nIiwiaWQiOjEsImV4cCI6MTcxODYyNTY4M30.CibHAVdkRW7Wm_jGnHducGp2RZXmQLUdPYVVXARCwJc")  // 解析令牌
            .getBody();  // 可以获取载荷
    System.out.println(claims);

注意事项:

  1. JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的
  2. 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法

服务端需要对请求进行统一拦截,看其中是否包含JWT,如果包含看是否已经过期,如果没有过期,则执行相应的请求。

2.请求拦截器

1.过滤器

过滤器可以把对资源的请求拦截下来从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等

快速入门:
1.定义Filter:定义一个类,实现Filter接口,并重写其所有方法
2.配置Filter:Filter类上加@WebFilter注解,配置拦截资源的路径。引导启动类上加@ServletComponentScan开启Servlet组件支持

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
    @Override // 初始化方法,只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override // 拦截到请求之后调用,调用多次
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("拦截到了请求");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override // 销毁方法,只调用一次
    public void destroy() {
        Filter.super.destroy();
    }
}

2.Filter详解

filter拦截路径:

拦截路径urlPattern值含义
拦截具体路径/login只有访问/login路径时,才会被拦截
目录拦截/emps/*访问/emp下的所有资源,都会被拦截
拦截所有/*访问所有资源,都会被拦截

检查登录的Filter示例:

import com.alibaba.fastjson.JSONObject;
import com.lonely.mybatisdemo.pojo.Result;
import com.lonely.mybatisdemo.utils.JwtUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.IOException;

@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        // 1.获取请求url
        String requestURL = req.getRequestURL().toString();
        // 2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if (requestURL.contains("login")){
            log.info("登陆操作,放行");
            chain.doFilter(req,resp);
            return ;
        }
        // 3.获取请求头中的令牌(token)
        String jwt = req.getHeader("token");
        // 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回错误信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error); // 手动转换为json
            resp.getWriter().write(notLogin);  // 响应给浏览器未登录的信息
            return ;
        }
        // 5.解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtil.parseJWT(jwt);
        }catch(Exception e){ // jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error); // 手动转换为json
            resp.getWriter().write(notLogin);  // 响应给浏览器未登录的信息
            return ;
        }

        // 6.放行
        log.info("令牌合法,放行");
        chain.doFilter(request,response);
    }
}

3.拦截器Interceptor

概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

1.快速入门

1.定义拦截器,实现HandlerInterceptor接口,并重写其所有方法。
2.注册拦截器

定义拦截器:

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override // 目标资源方法运行前运行,返回值为true代表放行,返回值为false代表不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return true;
    }

    @Override // 目标资源方法运行之后运行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
//        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override // 视图渲染完毕后运行,最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
//        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

注册拦截器:

import com.lonely.mybatisdemo.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**"); // 设置拦截器的路径为/*
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

2.拦截路径

拦截器可以根据需求,配置不同的拦截路径:

@Override
public void addInterceptors(InterceptorRegistry registry){
	registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); // 拦截所有的路径并排除/login路径
}
拦截路径含义举例
/*一级路径能匹配/depts,/emps,/login,不能匹配 /depts/1
/**任意级路径能匹配/depts,/depts/1,/depts/1/2
/depts/*/depts下的一级路径能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/**/depts下的任意级路径能匹配/depts,/depts/1,/depts/1/2

8.异常处理

出现异常,该如何处理
方案一:在Controller的方法中进行try…catch处理(不推荐)
方案二:全局异常处理器(推荐)
定义一个全局异常处理器:

import com.lonely.mybatisdemo.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result ex(Exception ex){
        ex.printStackTrace();
        return Result.error("对不起,操作失败,请联系管理员");
    }
}

9.事务管理

事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。

案例:解散部门:删除部门,同时删除该部门下的员工
完善删除部门功能:
DeptServiceImpl类中:

@Service
public class DeptServiceImpl implements DeptService{
	@AutoWired
	private DeptMapper deptMapper;
	@AutoWired
	private EmpMapper empMapper;
	@Override
	public void delete(Integer id){
		deptMapper.delete(id);// 1.删除部门
		empMapper.deleteByDeptId(id); // 2.根据部门id,删除部门下的员工信息
	}
}

DeptMapper中:

@Delete("delete from emp where dept_id = #{deptId}")
void deleteByDeptId(Integer Id);

但这样存在问题,删除部门和删除部门下的员工并不是同时发生的,可能会导致数据不一致的问题。

所以要使用到事务

1.Spring事务管理:

注解:@Transactional
位置:业务(service)层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
需要在DeptServiceImpl类中的delete方法前加上注解@Transactional:

@Service
public class DeptServiceImpl implements DeptService{
	@AutoWired
	private DeptMapper deptMapper;
	@AutoWired
	private EmpMapper empMapper;
	
	@Transactional
	@Override
	public void delete(Integer id){
		deptMapper.delete(id);// 1.删除部门
		empMapper.deleteByDeptId(id); // 2.根据部门id,删除部门下的员工信息
	}
}

如果要开启事务管理日志,需要在application.yml中添加配置:

logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

2.事务属性

1.事务属性-回滚
rollbackFor:默认情况下,只有出现RuntimeException(运行时异常)才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务。

@Transactional(rollbackFor=Exception.class)代表出现所有异常都会回滚事务

2.事务属性-传播行为
propagation
事务传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

@Transactional
public void a(){
	// ...
	userService.b();
	// ...
}
@Transactional(propagation = Propagation.REQUIRED)
public void b(){
	// ...
}
属性值含义
REQUIRED(默认值)需要事务,有则加入,无则创建新事物
REQUIRES_NEW需要新事物,无论有无,总是创建新事物
SUPPORTS支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY必须有事务,否则抛异常
NEVER必须没事务,否则抛异常

10.AOP(面向切面编程)

1.AOP概述

面向切面编程
场景:某些部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的耗时。

动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

2.快速入门

统计各个业务层方法执行耗时

  • 导入依赖:在pom.xml中导入AOP的依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  • 编写AOP程序:针对于特定方法根据业务需要进行编程
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect // 当前类是一个AOP类
@Slf4j
public class TimeAspect {
    @Around("execution(* com.lonely.mybatisdemo.service.*.*(..))")  // 切入点表达式
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {

        // 1.记录开始时间
        long begin = System.currentTimeMillis();
        // 2.调用原始方法
        Object result = joinPoint.proceed();

        // 3.记录结束时间,计算方法执行耗时
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature()+"方法执行总耗时:{}",end-begin);
        return result;
    }
}

AOP场景:记录操作日志,权限控制,事务管理

连接点:JoinPoint,可以被AOP控制的方法。
通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用。
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
目标对象:Target,通知所应用的对象

3.通知类型

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

注意事项:

  • @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定该为Object,来接收原始方法的返回值
@Component
@Aspect
public class MyAspect{
	// 可以用@Pointcut将切入点抽取出来
	@Pointcut("exection(* com.lonely.test.impl.DeptServiceImpl.*(..))")
	private void pt(){}
	
	@Before("pt()")
	public void before(){
		System.out.println("before ...")
	}
}

通知的顺序:当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行

通过在切面类上加@Order(数字)注解来控制顺序:
目标方法前的通知方法:数字小的先执行
目标方法后的通知方法:数字小的后执行

切入点表达式:execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分
访问修饰符:可省略(比如public、private)
包名.类名:可省略
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

pl:

@Before("execution(public void com.lonely.test.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
public void before(JoinPoint joinPoint){
	// 代码块
}

可以使用通配符描述切入点:
* :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

execution(* com.*.service.*.update*(*))

.. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

execution(* com.lonely.test..DeptService.*(..))

切入点表达式:@annotation
@annotation切入点表达式,用于匹配标识有特定注解的方法
@annotation(com.lonely.anno.Log)
pl:

@Before("@annotation(com.lonely.anno.MyLog)")
public void before(){
	log.info("before ...");
}

首先自定义注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // 运行时有效
@Target(ElementType.METHOD) // 作用在方法上
public @interface MyLog {
}

4.连接点

在spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用 ProceedingJoinPoint
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是Proceeding Join Point的父类型。
@Around("execution(* com.lonely.test.service.DeptService.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
	String className = joinPoint.getTarget().getClass().getName(); // 获取目标类型
	Signature signature = joinPoint.getSignature(); // 获取目标方法签名
	String methodName = joinPoint.getSignature().getName(); // 获取目标方法名
	Object[] args = joinPoint.getArgs(); // 获取目标方法运行参数
	Object res = joinPoint.proceed(); // 执行原始方法,获取返回值
	return res;
}

原理篇

1.配置优先级

SpringBoot中支持三种格式的配置文件:

server.port=8081      // application.properties
server:
	port: 8082  // application.yml
server:
	port: 8083  // application.yaml

优先级:properties–> yml ->yaml
推荐使用yml

SpringBoot除了支持配置文件属性配置,还支持Java系统属性和命令行参数的方式进行属性配置

-Dserver.port=9000   // Java系统属性
--server.port=10010  // 命令行参数

执行maven打包指令package
test-web-system-0.0.1-SNAPSHOT.jar
执行java指令,运行jar包
java -Dserver.port=9000 -jar test-web-system-0.0.1-SNAPSHOT.jar --server.port=10010

优先级:
命令行参数>java系统属性>application.properties>application.yml>application.yaml

2.bean的管理

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testGetBean(){
        // 1.根据bean的名称获取
        DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
        System.out.println(bean1);
        // 2.根据bean的类型获取
        DeptController bean2 = applicationContext.getBean(DeptController.class);
        System.out.println(bean2);
        // 3.根据bean的名称及类型获取
        DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
        System.out.println(bean3);
    }

bean作用域:

作用域说明
singleton容器内同名称的bean只有一个实例(单例)(默认)
prototype每次 使用该bean时会创建新的实例(非单例)
request每个请求范围内会创建新的实例(web环境,了解)
session每个会话范围内会创建新的实例(web环境,了解)
application每个应用范围内会创建新的实例(web环境,了解)

通过@Scope注解来进行配置作用域

@Lazy  // 延迟初始化,延迟到第一次使用该bean对象
@Scope("prototype") // 设置作用域为prototype
@RestController
@RequestMapping("/depts")
public class DeptController{
}

注意事项:

  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)
  • prototype的bean,每一次使用该bean的时候都会创建一个新的实例
  • 实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性

第三方bean
如果要管理的bean对象来自于第三方,是无法使用@Component及衍生注解声明bean的,就需要用到@Bean注解。

@SpringBootApplication
public class SpringbootWebConfigApplication{
	@Bean  // 将方法返回值交给IOC容器管理,成为IOC容器的bean对象
	public SAXReader saxReader(){
		return new SAXReader();
	}    // 在启动类中不建议
}

推荐在配置类中管理

@Configuration
public class CommonConfig{
	@Bean  // 将当前方法的返回值对象交给IOC容器管理,成为IOC容器bean
			// 通过@Bean注解的name/value属性指定bean名称,如果未指定,默认是方法名
	public SAXReader saxReader(DeptService deptService){ 
		return new SAXReader();
	}
}

注意事项:

  • 通过@Bean注解的name或value属性可以声明bean的名称,如果不知道,默认bean的名称就是方法名。
  • 如果第三方bean需要依赖其他bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配

3.SpringBoot原理

1.起步依赖原理

maven的依赖传递

2.自动配置原理

SpringBoot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

由于springboot只能扫描本包及子包下的类

方案一:可以使用@ComponentScan({包名})扫描第三方包及其子包

@ComponentScan({"com.example","com.lonely"})
@SpringBootApplication
public class SpringbootWebConfigApplication{

}

方案二:@Import导入。使用@Import导入的类会被Spring加载到IOC容器中,导入形式主要有以下几种:

  • 导入普通类
  • 导入配置类
  • 导入 ImportSelector 接口实现类
  • @EnableXxxx注解,封装@Import注解
@Import({TokenParser.class,HeaderConfig.class})
@SpringBootApplication
public class SpringbootwebConfigApplication{
}

条件装配:@Conditional
作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring IOC容器中。
位置:方法、类
@Conditional本身是一个父注解,派生出大量的子注解:

  • @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器
  • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器
  • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
@Bean
@ConditionalOnMissingBean
public Gson gson(GsonBuilder gsonBuilder){
	return gsonBuilder.create();
}
  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值