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

创建项目时需要勾选依赖
img

编写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配置

ymlyaml 本质上没有区别

  • .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应用中可以使用多个过滤器,形成一个链条

img

多个过滤器的执行顺序默认是按类名排序

或者指定顺序@Order(1) // 数值越小优先级越高

登录过滤

img

@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"); //不需要拦截哪些资源


    }
}

img

img

接口规范不同:过滤器需要实现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里都捕获异常抛出(很麻烦)
  • 全局异常处理器

img

定义全局异常管理器

//全局异常处理器
@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;
    }
}

概念

img

通知类型

img

测试

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("异常后通知");
    }
}

通知执行顺序

默认是按类名的顺序执行的

img

但可以通过@Order(数字)注解指定顺序

@Aspect 
@Component
@Order(1) //指定顺序
public class TimeAspect {
    //...
}

切入点表达式

execution

img

@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记录操作日志

img

准备工作

-- 操作日志表
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作用域

img

通过 @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);
    }

}
posted @ 2025-03-24 21:14  -殇情-  阅读(20)  评论(0)    收藏  举报