Springboot学习笔记

Web开发路线:

Maven:

  • Maven依赖的Java环境:JDK11

安装Maven:

此处安装的maven版本是3.6.1

1.配置本地仓库

在maven目录下新建mvn_repo文件夹作为maven的本地仓库

打开maven目录下conf/setting.xml文件

更改下面配置语句

 <localRepository>E:\Maven\apache-maven-3.6.1\mvn_repo</localRepository>

2.配置阿里云私服

     <mirror>  
          <id>alimaven</id>  
          <name>aliyun maven</name>  
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf>          
     </mirror>

3.配置环境变量

MAVEN_HOME 为maven的解压目录,并将其bin目录加入PATH环境变量

4.检查版本号

 mvn -v

配置Maven环境(全局):

创建Maven项目:

导入maven项目:

Web开发:

HTTP--请求数据:

请求数据格式分为 请求行 请求头 请求体

请求数据格式

请求方式:GET 请求参数在请求行中,没有请求体 GET请求大小是有限制的

请求方式:POST 请求参数在请求体中,POST请求大小是没有限制的

HTTP响应格式:

HTTP/1.1 200 OK

响应数据第一行(协议,状态码,描述)

响应头:key : value格式的数据

响应体:存放响应数据

  • 响应状态码

常用响应码

200 客户端请求成功

404 请求资源不存在

500 服务器发生错误

Web服务器:

对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作 , 提供网上信息浏览功能

Tomcat:

轻量级的web服务器,支持servlet , jsp 等少量javaEE规范

也被称为web容器,servlet容器

基本使用:

1.启动:

双击 bin\startup.bat

注意,前期是配置好JDK的环境变量JAVA_HOME

2.解决中文乱码

修改conf/logging.properties

修改编码格式从UTF-8 改为GBK

 java.util.logging.ConsoleHandler.encoding = GBK

3.配置Tomcat的端口号

conf/server.xml文件

4.部署项目

将项目放置到webapps目录下,即部署完成

请求:

1.1 简单参数:

定义同名的形参即可接收参数

如果方法形参名称与请求参数名称不匹配,可以使用@RequestParam 完成映射

     @RequestMapping("/simpleParam")
     public String simpleParam(@RequestParam(name = "name", required = false) String username, Integer age) {
         System.out.println(username + " " + age);
         return "ok";
     }

注意事项:

  • @RequestParam中的required属性默认为true,代表该请求参数必须传递
  • 将required属性设置为false,表示该参数是可选的
  • defaultValue:默认参数值,如果没有传该参数,就使用默认值

1.2 实体参数:

简单实体参数:

规则:请求参数名与形参对象属性名相同,即可通过POJO接收

     //简单实体参数类型
     @RequestMapping("/simplePojo")
     public String simplePojo(User user){
         System.out.println(user);
         return "ok";
     }
 public class User {
     private String name;
     private  Integer age;
     
    //省略getter和setter方法
     }

复杂实体对象:

复杂实体对象的封装,需要遵守如下规则:

  • 请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套实体类属性参数。

1.3 数组集合参数:

应用场景

对复选框传递过来的多个参数进行封装

数组参数

定义数组类型形参即可接收参数

     //传递数组参数
     @GetMapping("/arrayParam")
     public String arrayParam(String [] hobby){
         System.out.println( Arrays.toString(hobby));
         return "ok";
     }

postman中模拟:

http://localhost:8080/arrayParam?hobby=game&hobby=java

集合参数:

@RequestParam 绑定参数关系

    //集合参数
//    @RequestParam绑定参数关系
    @RequestMapping("/listParam")
    public String listParam(@RequestParam List<String> hobby){
        System.out.println(hobby);
        return "ok";
    }
http://localhost:8080/arrayParam?hobby=game&hobby=java

1.4 日期参数:

对于日期类型的参数在进行封装的时候,需要通过@DateTimeFormat注解,以及其pattern属性来设置日期的格式。

  • @DateTimeFormat注解的pattern属性中指定了哪种日期格式,前端的日期参数就必须按照指定的格式传递。

  • 后端controller方法中,需要使用Date类型或LocalDateTime类型,来封装传递的参数。

1.5 JSON参数:

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

    //传递JSON数据
    @PostMapping("/jsonParam")
    public String jsonParam(@RequestBody User user) {
        System.out.println(user);
        return "ok";
    }

控制台返回:

User{name='Tom', age=18}

1.6 路径参数:

通过请求URL直接传递参数 ,需要使用@PathVariable获取路径参数

    //路径参数
    @RequestMapping("/path/{id}")
    public String pathParam(@PathVariable Integer id){
        System.out.println(id);
        return "ok";
    }

传递多个路径参数:

    //路径参数
    @RequestMapping("/path/{id}/{name}")
    public String pathParam(@PathVariable Integer id,@PathVariable String name) {
        System.out.println(id+" "+name);
        return "ok";
    }

响应:

响应数据:

controller方法中的return的结果,怎么就可以响应给浏览器呢?

答案:使用@ResponseBody注解

@ResponseBody:

类型 : 方法注解 ,类注解

位置: Controller方法上 / 类上

作用: 将方法返回值直接响应 , 如果返回值类型是 实体对象/集合 , 将会转换为JSON格式响应

说明: @RestController = @Controller + @ResponseBody

统一响应结果:
/**
 * 统一响应结果封装类
 */
public class Result {
    private Integer code;//1 成功 , 0 失败
    private String msg; //提示信息
    private Object data; //数据 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 +
                '}';
    }
}

分层解耦:

IOC和DI:

控制反转简称IOC。Service层及Dao层的实现类,交给IOC容器管理。IOC容器可以创建他们的对象

依赖注入简称DI。为Controller及Service注入运行时,依赖的对象

Bean对象:IOC容器中创建的对象,称之为bean

IOC详解:

Bean的声明

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

注解说明位置
@Component声明bean的基础注解,(一般不用)不属于以下三类时,用此注解(一些工具类)
@Controller@Component的衍生注解Controller层
@Service@Component的衍生注解Service层
@Repository@Component的衍生注解Dao层(由于与mybatis整合,用的少)

注意:

  1. 声明bean的时候,bean的名字默认为类名首字母小写。也可以通过value指定bean的名字

  2. 在springboot集成web开发中,声明控制器bean只能用@Controller

Bean组件扫描:

@SpringBootApplication 具有包扫描作用,默认扫描范围启动类所在包及其子包

DI详解:

Bean注入:

如果在IOC容器中,存在多个相同类型的bean对象,会出现什么情况

@Service
public class DeptServiceImplA implements DeptService {}
@Service
public class DeptServiceImplB implements DeptService {}

public class EmpController {
    @Autowired
    private EmpService empService;  
}

解决方案:

@Primary注解 , 在Service类上加上@Primary 注解

@Primary
@Service
public class DeptServiceImplA implements DeptService {}
@Service
public class DeptServiceImplB implements DeptService {}

@Resource注解

是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

     @Autowired
	@Resource(name = "deptServiceImplA")
     private DeptService deptService;

Mybatis:

Mybatis 时一款持久层框架 , 用于简化JDBC的开发

@Mapper 注解:

@Mapper  //在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {
    //查询全部用户信息
    @Select("select  * from mybatis.user")
    public List<User> list();
}

配置SQL提示:

数据库连接池:

数据库连接池是个容器,负责分配,管理数据库连接

优势:资源复用,提升响应速度

标准接口 DataSource

功能:获取连接

lombok:

通过注解的方式自动生成构造器

Mybatis基础操作:

  • 删除

  • 插入

  • 更新

  • 查询

日志输出:
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
查询操作:

数据响应案例:

可以看到这里的gender , job , deptId 都是number型的,解析成对应的数据会在前端完成

{
  "code": 1,
  "msg": "success",
  "data": {
    "total": 2,
    "rows": [
       {
        "id": 1,
        "username": "jinyong",
        "password": "123456",
        "name": "金庸",
        "gender": 1,
        "job": 2,
        "entrydate": "2015-01-01",
        "deptId": 2,
      },
    ]
  }
}

条件查询

@Mapper
public interface EmpMapper {

    @Select("select * from emp " +
            "where name like concat('%',#{name},'%') " +
            "and gender = #{gender} " +
            "and entrydate between #{begin} and #{end} " +
            "order by update_time desc")
    public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

}
删除操作:

Mapper

public interface EmpMapper {
    @Delete("delete from emp where id=#{id}")
    void delete(Integer id);
}
新增操作:
    //新增数据
    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
            "VALUES (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    void insert(Emp emp);
新增(主键返回 ):
    @Options(keyProperty = "id" , useGeneratedKeys = true)
    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
            "VALUES (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    void insert(Emp emp);
更新操作:
    //更新数据
    @Update("update  emp set username=#{username},name= #{name},gender=#{gender} where id = #{id}")
    void update(Emp emp);
数据封装:

实体类属性名 和 数据库表查询返回的字段名不一致 ,不能自动封装

解决方案:开启mybatis 的驼峰命名自动映射开关

mybatis:
    map-underscore-to-camel-case: true

XML映射文件:

内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace里写EmpMapper的全限定名 -->
<mapper namespace="com.example.mapper.EmpMapper">

</mapper>
MybatisX :

作用 : 快速定位XML对应的接口方法

Mybatis动态SQL:

<sql>:定义可重用的SQL片段

<include>:通过属性refid , 指定包含的SQL片段

SQL片段: 抽取重复的代码

<sql id="commonSelect">
 	select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp
</sql>

然后通过<include> 标签在原来抽取的地方进行引用。操作如下:

<select id="list" resultType="com.itheima.pojo.Emp">
    <include refid="commonSelect"/>
    <where>
        <if test="name != null">
            name like concat('%',#{name},'%')
        </if>
        <if test="gender != null">
            and gender = #{gender}
        </if>
        <if test="begin != null and end != null">
            and entrydate between #{begin} and #{end}
        </if>
    </where>
    order by update_time desc
</select>

SpringBoot案例:

前端页面:

TLIAS教学管理系统

开发接口一定要看接口文档

EditStarters插件

1.打开pom.xml文件

2.使用EditStarters快捷键 , 选择edit starters

alt + ins

3.搜索添加依赖

员工管理:

调试后端:

在控制层中写完接口方法后就可以先用postman来调试接口方法,

没必要把service层和mapper层都写完再进行调试

条件分页查询:

分页插件PageHeleper , 只能在Springboot 2.x 的版本使用

准备工作:

PageBean 对象类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
private Long total; //总记录数
private List rows; //当前页数据列表
}

在xml文件中引入依赖:

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>
代码实现:

EmpController

@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {

    @Autowired
    private EmpService empService;

    //条件分页查询
    @GetMapping
    public Result page(@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.page(page, pageSize, name, gender, begin, end);
        //响应
        return Result.success(pageBean);
    }
}

EmpService

public interface EmpService {
    /**
     * 条件分页查询
     * @param page     页码
     * @param pageSize 每页展示记录数
     * @param name     姓名
     * @param gender   性别
     * @param begin   开始时间
     * @param end     结束时间
     * @return
     */
    PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end);
}

EmpServiceImpl

@Slf4j
@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private EmpMapper empMapper;

    @Override
    public PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
        //设置分页参数
        PageHelper.startPage(page, pageSize);
        //执行条件分页查询
        List<Emp> empList = empMapper.list(name, gender, begin, end);
        //获取查询结果
        Page<Emp> p = (Page<Emp>) empList;
        //封装PageBean
        PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
        return pageBean;
    }
}

EmpMapper

@Mapper
public interface EmpMapper {
    //获取当前页的结果列表
    public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
}

EmpMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">

    <!-- 条件分页查询 -->
    <select id="list" resultType="com.itheima.pojo.Emp">
        select * from emp
        <where>
            <if test="name != null and name != ''">
                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>
</mapper>

阿里云OSS:

阿里云服务:

提供了很多云服务,语音服务,短信服务,身份认证,翻译服务,对象存储服务等等

云存储服务

开通对象存储服务(OSS) --> 创建bucket ---> 获取AccessKey密钥 ---> 参照官方SDK编写入门程序 ---> 案例集成OSS

登录功能:

请求数据样例:

{
	"username": "jinyong",
    "password": "123456"
}

登录功能本质上和查询功能是一样的,在数据库中查找是否有这个数据,

如果有,也需要返回数据 , 而且需要用返回数据来判断是否存在这个用户

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {

        // 我自己写的时候我觉得 没有返回值也可以吧 ---->不可以
        //没有返回值的逻辑是  在Mapper层将查询到的数据不返回
        // 其实登录的逻辑和查询的逻辑是一样的,在数据库里查找有没有这个用户,如果有就返回
        // 删除操作是你接收删除的id , 在数据库里直接删除
        Emp emp1 = empService.login(emp);
        return emp1 != null ? Result.success() : Result.error();
    }
登录校验:

会话跟踪方案

  • 客户端会话跟踪技术:Cookie

  • 服务端会话跟踪技术:Session

  • 令牌技术

JWT令牌技术:

Base64:是一种基于64个可打印字符( A-Z a-z 0-9 + / )来表示二进制数据的编码方式

JWT组成:

第一部分:Header(头),记录令牌类型,签名算法等。

第二部分:Payload(载荷) ,携带一些自定义信息,默认信息等

第三部分:Signature(签名),防止Token被篡改,确保安全性。将header,patyload,并加入指定秘钥,通过指定签名算法计算而来

登录认账:

登录成功后,生成令牌

后续每个请求,都要携带令牌,系统在每次请求处理之前,先校验令牌,通过后,在处理。

JWT-生成:
  1. 引入JWT依赖

<!--        JWT依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
  1. Test类:

    //生成JWT令牌
	@Test
    public void testJWt() {
        Map<String, Object> cliams = new HashMap<>();
        cliams.put("id", 1);
        cliams.put("name", "tom");
        String jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "hello jwt")        //签名算法
                .setClaims(cliams)                                         //设置自定义内容
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 3600))      //设置令牌有效期
                .compact();                                                 //生成字符串
        System.out.println(jwtBuilder);
         
         //解析JWT令牌
             //解析JWT令牌的方法
    //解析JWT令牌的方法
    @Test
    public void testParseJwt() {
        Claims claims = Jwts.parser()
                .setSigningKey("hello jwt")     //指定签名秘钥
                // 解析令牌
             .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY5NjgyMDAxMn0.sOXhIAaQ-YFKonaqozFjtxGpHS40mJn3QtOC88B41QY")
                .getBody(); 
        System.out.println(claims);

注意:

  • JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。

  • 如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。

下发令牌:

环境依赖

       <!--        JWT依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

JWT工具类

public class JwtUtils {

    private static String signKey = "itheima";//签名密钥
    private static Long expire = 43200000L; //有效时间

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)//自定义信息(有效载荷)
                .signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
                .setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)//指定签名密钥
                .parseClaimsJws(jwt)//指定令牌Token
                .getBody();
        return claims;
    }
}

登录成功,生成JWT令牌并返回

@RestController
@Slf4j
public class LoginController {
    //依赖业务层对象
    @Autowired
    private EmpService empService;
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        //调用业务层:登录功能
        Emp loginEmp = empService.login(emp);
        //判断:登录用户是否存在
        if (loginEmp != null) {
            //自定义信息
            Map<String, Object> claims = new HashMap<>();
            claims.put("id", loginEmp.getId());
            claims.put("username", loginEmp.getUsername());
            claims.put("name", loginEmp.getName());
            //使用JWT工具类,生成身份令牌
            String token = JwtUtils.generateJwt(claims);
            return Result.success(token);
        }
        return Result.error("用户名或密码错误");
    }
}

校验令牌:

过滤器(Filter):

将资源的请求拦截下来,实现一些通用的操作

基本使用:

1.定义一个类实现Filter接口,并重写其所有方法

2.配置Filter:Filter类上加@WebFilter注解,配置拦截资源的路径。启动项上加@ServletComponentScan 开启Servlet组件支持

@WebFilter(urlPatterns = "/*")
public class FilterImpl implements Filter {
     // init() 和 destored() 会默认实现,可以不重写
    //  只实现doFilter()
}

执行流程

放行后访问完对应资源 , 还会回到Filter中

回到Filter中,执行放行后的逻辑

    // 拦截请求时,调用该方法,可调用多次
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       
        System.out.println("拦截到了请求...放行前的逻辑");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);

        System.out.println("拦截到了请求...放行后的逻辑");
    }

过滤器链

一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链

配置的多个Filter , 优先级是按照过滤器类名(字符串)的自然排序

登录校验:

代码实现

@Slf4j
@WebFilter(urlPatterns = "/login")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        //1.请求url地址

        String url = req.getRequestURI().toString();
        log.info("请求的url:{}", url);

        //2.判断请求url中是否包含login.如果包含,则说明是登录操作,放行

        if (url.contains("login")) {
            log.info("登录操作,放行....");
            chain.doFilter(request, response);
            return;
        }

        //3.获取请求头中的令牌(token)

        String jwt = req.getHeader("token");

        //4.判断令牌是否存在,不存在则返回登录失败

        if (!StringUtils.hasLength(jwt)) {
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            // 手动转化 对象--json  ----->阿里巴巴 fastJSON
            //把Result对象转化为JSON格式字符串
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return;
        }

        //5.令牌存在,解析令牌。解析令牌失败,返回登录失败

        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {     //jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            // 手动转化 对象--json  ----->阿里巴巴 fastJSON
            //把Result对象转化为JSON格式字符串
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return;
        }

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

拦截器Interceptor:

Spring框架中提供的,用来动态拦截控制器方法的执行。

快速入门

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

  2. 注册拦截器

拦截路径:

在配置拦截器时,不仅可以指定要拦截哪些资源,还可以指定不拦截哪些资源

调用excludePathPatterns("不拦截路径")方法,指定哪些资源不需要拦截

在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:

拦截路径含义举例
/*一级路径能匹配/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,不能匹配/emps/1

登录校验:

代码实现:

定义拦截器

@Component
@Slf4j
public class LoginChenkInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        1.请求url地址
        String url = request.getRequestURI();
        log.info("请求的url地址是:{}", url);
//        2.判断请求的url中是否包含login,如果包含就放行
        if (url.contains("login")) {
            log.info("登录请求,放行");
            return true;
        }
//        3.url中不包含login,就去获取请求头中的令牌
        String jwt = request.getHeader("token");
//        4.判断令牌是否存在,令牌不存在就返回,存在放行
        if (!StringUtils.hasLength(jwt)) {
            log.info("请求头token为空,返回");
            Result error = Result.error("NOT_NOLOGIN");
//            把对象转化为JSON返回
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }
//       5. 这一步表示令牌存在,解析令牌。解析成功,放行,解析失败,返回
//        令牌解析失败会出现异常,这里用try...catch来写
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            Result error = Result.error("NOT_LOGIN");
            //把对象转化为JSON返回
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }
//        6.令牌解析成功,放行
        log.info("令牌解析成功,放行");
        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 LoginChenkInterceptor loginChenkInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginChenkInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

异常处理:

全局异常处理器:
//全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)  //捕获所有异常
    public Result ex(Exception ex) {
        ex.printStackTrace();
        return Result.error("对不起,请联系管理员");
    }
}

事务管理:

事务是一组操作的集合。这组操作要么同时成功,要么同时失败。

案例:

解散部门

需求:当部门解散了不仅需要把部门信息删除了,还需要把该部门下的员工数据也删除了。

    //开始事务管理
    //删除部门的同时删除部门下的员工信息
    @Transactional
    @Override
    public void delete(Integer id) {
        deptMapper.delete(id);	//删除部门
        int i = 1 / 0;       //模拟抛出异常
        empMapper.deleteByDeptId(id);  //删除员工
    }

说明:可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

事务进阶:

事务属性---回滚

rollbackFor

默认情况下,只有出现RuntimeException才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务。

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(Integer id) throws Exception {
        deptMapper.delete(id);
        if (true) {
            throw new Exception("出错啦"); //模拟抛出异常
        }
        empMapper.deleteByDeptId(id);
    }

事务属性----传播行为

当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

@Transactional(rollbackFor = Exception.class ,propagation = Propagation.REQUIRED)

REQUIRED ---> 默认值,需要事务,有则加入,无则创建新事物

AOP基础:

什么是AOP?

面向切面编程 或者 面向特定方法编程。

场景:

案例部分功能运行缓慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时

1.获取方法运行开始时间

2.运行原始方法

3.获取方法运行结束时间

实现:

动态代理时面向切面编程的最主流的实现。SpringAOP旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程

AOP快速入门:

导入依赖:

<!--      AOP依赖  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

编写AOP程序,针对于特定方法根据业务需要进行编程

AOP测试类:TimeAspect

@Slf4j
@Component
@Aspect     //AOP类
public class TimeAspect {
    @Around("execution(* com.itheima.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() + "方法执行耗时:{}ms", end - begin);
        return result;
    }
}
AOP核心概念:

连接点 JoinPoint , 可以被AOP控制的方法。

通知:Advice,指哪些重复的逻辑,也就是共性功能

切入点:PointCut , 被AOP控制的方法

AOP进阶:

通知类型:

@Around(重点) : 环绕通知,此注解标注的通知方法在目标方法前,后都被执行

@Before:前置通知,此注解标注的通知方法在目标方法前被执行

@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

@Around 环绕通知需要自己调用ProceedingJoinPoint.proceed( ) 来让原始方法执行,其它通知不需要考虑目标方法执行。

@Around 环绕通知方法的返回值,必须指定为Object ,来接收原始方法的返回值 。

通知顺序:

切入点表达式:

常用形式:1.execution(...) : 根据方法的签名来匹配

2.@annotation(...) : 根据注解匹配

AOP案例:

将增,删, 改相关接口的操作日志记录到数据库表中

日志信息包括:操作人,操作时间,执行方法的全类名,执行方法名,方法运行时参数,返回值,方法执行时长

思路分析:

1.对所有业务类中的增,删,改方法添加统一功能,使用AOP技术最为方便,

2.由于增,删,改方法名没有规律,可以自定义@Log 注解完成目标方法匹配

SpringBoot原理篇:

配置文件的优先级:

优先级排名(从高到低):

application.properties

application.yml

application.yaml

自动配置:

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

自动配置原理

Web后端开发总结:

image-20230114191737227

Maven高级:

分模块设计与开发:

创建maven模块,存放实体类

创建maven模块,存放相关工具类

注意:分模块开发需要先针对模块进行设计,在进行编码。不会先将工程开发完毕,然后进行拆分。

        <!--        tlias-pojo依赖 -->
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>tlias-pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--        tlias-utils依赖-->
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>tlias-utils</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

继承与聚合:

子工程可以继承父工程中的依赖,简化依赖配置,统一管理依赖。

实现:

1,创建maven模块tlias-parent , 该工程为父工程,设置默认打包方式为pom( 默认jar包 )

2,在子工程中配置继承关系

3,在父工程中配置各个工程共有的依赖

若父子工程都配置了同一个依赖的不同版本,以子工程的为准。

版本锁定:

在maven中,可以在父工程的pom文件中通过 <dependencyManagement> 来统一管理依赖版本。

父工程:

<!--统一管理依赖版本-->
<dependencyManagement>
    <dependencies>
        <!--JWT令牌-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子工程:

<dependencies>
    <!--JWT令牌-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

注意:

  • 在父工程中所配置的 <dependencyManagement> 只能统一管理依赖版本,并不会将这个依赖直接引入进来。 这点和 <dependencies> 是不同的。

  • 子工程要使用这个依赖,还是需要引入的,只是此时就无需指定 <version> 版本号了,父工程统一管理。变更依赖版本,只需在父工程中统一变更。

属性配置

我们也可以通过自定义属性及属性引用的形式,在父工程中将依赖的版本号进行集中管理维护。

1). 自定义属性

<properties>
	<lombok.version>1.18.24</lombok.version>
</properties>

2). 引用属性

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
</dependency>

接下来,我们就可以在父工程中,将所有的版本号,都集中管理维护起来。

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>

    <lombok.version>1.18.24</lombok.version>
    <jjwt.version>0.9.1</jjwt.version>
    <aliyun.oss.version>3.15.1</aliyun.oss.version>
</properties>


<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
    </dependency>
</dependencies>

<!--统一管理依赖版本-->
<dependencyManagement>
    <dependencies>
        <!--JWT令牌-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>

        <!--阿里云OSS-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>${aliyun.oss.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

版本集中管理之后,我们要想修改依赖的版本,就只需要在父工程中自定义属性的位置,修改对应的属性值即可。

聚合:

继承关系下,打包相互依赖的子工程,需要先把子工程依赖的其它模块安装到maven的本地仓库,然后才能打包

maven聚合可以实现一键构建,打包等功能

实现

在maven中,我们可以在聚合工程中通过 <moudules> 设置当前聚合工程所包含的子模块的名称。我们可以在 tlias-parent中,添加如下配置,来指定当前聚合工程,需要聚合的模块:

<!--聚合其他模块-->
<modules>
    <module>../tlias-pojo</module>
    <module>../tlias-utils</module>
    <module>../tlias-web-management</module>
</modules>

那此时,我们要进行编译、打包、安装操作,就无需在每一个模块上操作了。只需要在聚合工程上,统一进行操作就可以了。

继承与聚合对比:
  • 作用

    • 聚合用于快速构建项目

    • 继承用于简化依赖配置、统一管理依赖

  • 相同点:

    • 聚合与继承的pom.xml文件打包方式均为pom,通常将两种关系制作到同一个pom文件中

    • 聚合与继承均属于设计型模块,并无实际的模块内容

  • 不同点:

    • 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些

    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值