Featured image of post javaweb

javaweb

本文阅读量

Tomcat

简介

Tomcat服务器软件是一个免费的开源的web应用轻量级服务器(只支持Servlet/JSP少量JavaEE规范),也被称为Web容器、Servlet容器。

JavaWeb程序需要依赖Tomcat才能运行

目录介绍

1
2
3
4
5
6
7
8
// tomcat目录结构
--bin           // 可执行文件目录,录下有两类文件,一种是以.bat 结尾的,是Windows系统的可执行文件,一种是以.sh结尾的,是Linux系统的可执行文件
--conf          // 配置文件
--lib           // Tomcat依赖的jar包
--logs          // 日志文件
--temp          // 临时文件
--webapps       // 应用发布目录(以后项目部署的目录)
--work          // 工作目录

配置

config/logging.prooperties

1
2
// 修改控制台编码
java.util.logging.ConsoleHandler.encoding=GBK

conf/server.xml

1
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirecotPort="8443">

Servlet

简介

通过HTTP协议接收和响应来自于客户端的请求。

Servlet 是JavaEE规范之一。

Servlet依赖Tomcat服务器运行。

使用

导入Servlet

servlet-api 依赖的作用范围 必须设置为 provided

1
2
3
4
5
6
<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>4.0.1</version>
	<scope>provided</scope>
</dependency>

Servlet接口实现

Servlet接口实现需要继承HttpServlet,并实现所有方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@WebServlet(urlPatterns="/hello")
public class HelloServlet expends HttpServlet{
  @Override
  protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
  	//接收请求参数
		String name = request.getParameter("name");
		//响应结果
		String respMsg = "<h1>Hello, " + name + " ~</h1>";
		response.getWriter().write(respMsg);
  }
  
  @Override
	protected void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException {
		doGet(request, response);
	}
}

Http协议

HTTP:Hyper Text Transfer Protocol(超文本传输协议),规定了浏览器与服务器之间数据传输的规则。

状态码概述

状态码分类 说明
1xx 响应中 — 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略
2xx 成功 — 表示请求已经被成功接收,处理已完成
3xx 重定向 — 重定向到其它地方,让客户端再发起一个请求以完成整个处理
4xx 客户端错误 — 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等
5xx 服务器端错误 — 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等
状态码 英文描述 解释
==200== OK 客户端请求成功,即处理成功,这是我们最想看到的状态码
302 Found 指示所请求的资源已移动到由Location响应头给定的 URL,浏览器会自动重新访问到这个页面
304 Not Modified 告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向
400 Bad Request 客户端请求有语法错误,不能被服务器所理解
403 Forbidden 服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
==404== Not Found 请求资源不存在,一般是URL输入有误,或者网站资源被删除了
405 Method Not Allowed 请求方式有误,比如应该用GET请求方式的资源,用了POST
428 Precondition Required 服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
429 Too Many Requests 指示用户在给定时间内发送了太多请求(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用
431 Request Header Fields Too Large 请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。
==500== Internal Server Error 服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
503 Service Unavailable 服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好

状态码大全:https://cloud.tencent.com/developer/chapter/13553

SpringBoot

Spring最基础、最核心的是SpringFramework。其他的spring家族的技术,都是基于SpringFramework的。

直接基于SpringFramework进行开发,存在两个问题:配置繁琐、入门难度大

Spring Boot 可以帮助我们非常快速的构建应用程序、简化开发、提高效率

SpringBoot为什么可以运行我们编写的controller文件

Tomcat是一个Servlet容器,而我们编写的Controller程序是不能被Tomcat服务器识别的

SpringBoot框架的底层源码中,给我们提供了一个核心的Servlet程序,叫 DispatcherServlet。而 DispatcherServlet 是一个Servlet程序,继承了HttpServlet(DispatcherServlet->FrameworkServlet->HttpServletBean->HttpServlet)。

前端发起的所有请求到达服务器之后,都会被DispatcherServlet接收并处理,而DispatcherServlet并不会直接对请求进行处理,而是将请求转发给后面我们自己编写的Controller程序,最终由Controller程序来进行请求的处理

注解

@RestController

@Controller@ResponseBody两个注解组合起来的

ResponseBody

**位置:**controller方法上或类上

作用:

  1. 写下方法上,将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器,
  2. 写在类上:
  3. 相当于该类所有的方法中都已经添加了@ResponseBody注解

@RequestMapping

声明请求方式为任意方式

@GetMapping

请求方法为get,对@RequestMapping请求方式的封装

1
2
@GetMapping("/depts")
public void list(){}

@Component

作用于类上,把当前类产生的bean对象交给IOC容器进行管理。

1
2
@Component
public class DeptServiceImpl implements DeptService{}

@Controller

@Component的衍生注解 ,标注在控制层类上

@Service

@Component的衍生注解 ,标注在业务层类上

@Repository

@Component的衍生注解,标注在数据访问层类上(由于与mybatis整合,用的少)

@Primary

结合@Service@Repository等使用,用于解决存在多个bean时,指定默认的bean

1
2
3
@Primary
@Service
public class DeptServiceImpl implements DeptService {}

@Qualifier

配合@Autowired使用,用于解决存在多个bean时,指定注入哪个bean(bean名称)

1
2
3
@Qualifier("deptServiceImpl")
@Autowired
private DeptService deptService;

@Resource

按照bean的名称进行注入。用于解决存在多个bean时,通过name属性指定要注入的bean的名称。

1
2
@Resource(name = "deptServiceImpl")
private DeptService deptService;

Autowired

为应用程序提供运行时依赖的对象。常用于获取IOC容器对象

1
2
@Autowired
private DeptDao deptDao;

参数获取

方式1:HttpServletRequest

1
2
3
4
5
@DeleteMapping("/depts")
public Result delete(HttpServletRequest request){
  String idStr = request.getParameter("id");
  int id = Integer.parseInt(idStr);
}

方式2: @RequestParam

1
2
3
4
5
@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer id){}

@DeleteMapping("/depts")
public Result delete(@RequestParam List<Integer> ids){}

@RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。 如果参数可选,可以将属性设置为false。

方式3:

1
2
3
4
5
@DeleteMapping("/depts")
public Result delete(Integer id){}

@DeleteMapping("/depts")
public Result delete(Integer[] ids){}

当请求参数名与形参变量名相同,直接定义方法形参即可接收(实际是对方式2的简写)

常用配置

Mysql数据库配置

1
2
3
4
5
6
7
8
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/web01
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root@1234

MyBatis配置

1
2
3
4
# 在控制台输出sql语句的执行日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# mapper映射开启驼峰命名,create_time会被映射成createTime
mybatis.configuration.map-underscore-to-camel-case=true

事务日志

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

上传配置

1
2
3
4
#配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

本地上传

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@PostMapping("/upload")
public Result upload(MultipartFile file) throws IOException {
  // file.getInputStream(); 获取接收到的文件内容的输入流
  //获取原始文件名
  String originalFilename = file.getOriginalFilename();
  //构建新的文件名
  String newFileName = UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));
  //将文件保存在服务器端 D:/images/ 目录下
  file.transferTo(new File("D:/images/"+newFileName));
  return Result.success();
}

三层架构

分层原因

软件设计原则:高内聚低耦合

高内聚:指的是一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。

低耦合:指的是软件中各个层、模块之间的依赖关联程序越低越好

分层

  1. Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。

  2. Service:业务逻辑层。处理具体的业务逻辑

    接口层:定义各种操作接口,文件名以Service结尾

    实现层:实现接口,文件名以ServiceImpl结尾

  3. Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查

    接口层:定义数据操作接口,文件名以Dao结尾

    实现层:根据数据源(文件或各种数据库)来实现接口,文件名以DaoImpl结尾

IOC

控制反转(Inversion Of Control),简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

IOC容器中创建、管理的对象,称之为:bean对象。

声明bean的时候,可以通过注解的value属性指定bean的名字,如果没有指定,默认为类名首字母小写

注解声明的bean,一定会生效吗?

不一定。因为bean想要生效,还需要被组件扫描 (@ComponentScan)

该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包

DI

依赖注入( Dependency Injection)简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。

@Autowird 与 @Resource的区别

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

数据库

JDBC

Java DataBase Connectivity,使用Java语言操作关系型数据库的一套API(是操作数据库最为基础、底层的技术)

Mybatis、MybatisPlus、Hibernate、SpringDataJPA都是基于JDBC封装的高级框架

mysql驱动

1
2
3
4
5
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.33</version>
</dependency>

操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Test
public void testUpdate() throws Exception {
  //1.准备工作
  //1.1 注册驱动
  Class.forName("com.mysql.cj.jdbc.Driver");
  //1.2 获取连接
  String url = "jdbc:mysql://localhost:3306/web";
  Connection connection = DriverManager.getConnection(url,"root", "1234");
  //1.3 获取SQL语句执行对象
  Statement statement = connection.createStatement();
  //2.执行SQL
  statement.executeUpdate("update user set password ='1234567890' where id = 1");
  //3.释放资源
  statement.close();
  connection.close();
}

处理多行数据集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ResultSet resultSet = statement.executeQuery("select * from user where username = 'lvbu' and password = '123456'");
// 获取结果
while (resultSet.next()){// 将光标从当前位置向前移动一行,并判断当前行是否为有效行,返回值为 boolean
  int id = resultSet.getInt("id");
  String username = resultSet.getString("username");
  String password = resultSet.getString("password");
  String name = resultSet.getString("name");
  int age = resultSet.getInt("age");
  System.out.println(new
  User(id,username,password,name,age));
}

解决sql注入(PreparedStatement预编译sql语句并执行)

1
2
3
4
5
6
// 获取数据库执行对象 Statement
PreparedStatement preparedStatement = connection.prepareStatement("select * from user where username =? and password = ?");
preparedStatement.setString(1, _username);
preparedStatement.setString(2, _password);
// 执行sql
ResultSet resultSet = preparedStatement.executeQuery();

select * from user where username =? and password = ?,被称为预编译sql语句

MyBatis

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

Pom依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- Mybatis的起步依赖 -->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>3.0.3</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>

在 mapper 包下创建一个接口 UserMapper ,这是一个持久层接口(Mybatis的持久层接口规范一般都叫XxxMapper)

注解开发

主要是来完成一些简单的增删改查功能

查询所有用户信息

1
2
3
// com.itheima.mapper包下的EmpMapper接口文件
@Select("select * from user")
public List<User> findAll();

手动映射字段名称

用于解决:实体类属性名和数据库表查询返回的字段名不一致,不能自动封装问题

1
2
3
4
@Results({@Result(column = "create_time", property ="createTime"),
@Result(column = "update_time", property = "updateTime")})
@Select("select id, name, create_time, update_time from dept")
public List<Dept> findAll();

还可以通过起别名与开启驼峰命名来解决

xml开发

可以实现复杂的SQL功能

查询所用用户信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
  <!--查询操作-->
  <select id="findAll" resultType="com.itheima.pojo.User">
  select * from user
  </select>
</mapper>

注意:

  1. namespace中的值:Mapper接口全限定名
  2. id:Mapper接口中的方法名称
  3. resultType: Mapper接口中对应的方法的返回类型

映射字段名

动态sql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<mapper namespace="com.itheima.mapper.DeptMapper">
	<!--动态更新部门数据-->
	<update id="update">
		update dept
		<set>
      <if test="name != null and name != ''">
      name = #{name},
      </if>
      <if test="updateTime != null">
      update_time = #{updateTime}
      </if>
		</set>
  	where id = #{id}
  </update>
</mapper>
  1. <set>标签:动态地在行首插入 SET 关键字,并会删掉额外的逗号
  2. <if test>标签:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL

动态批量插入数据

1
2
3
4
5
6
7
8
9
<mapper namespace="com.itheima.mapper.EmpExprMapper">
	<!--批量插入员工工作经历信息-->
  <insert id="insertBatch">
    insert into emp_expr (emp_id, begin, end, company, job) values
    <foreach collection="exprList" item="expr" separator=",">
      (#{expr.empId}, #{expr.begin}, #{expr.end}, #{expr.company}, #{expr.job})
    </foreach>
  </insert>
</mapper>

foreach标签用来循环集合,进行sql拼接

  • collection:集合名称
  • item:集合遍历出来的元素/项
  • separator:每一次遍历使用的分隔符
  • open:遍历开始前拼接的片段
  • close:遍历结束后拼接的片段

一对多查询

1
2
3
4
5
6
7
8
9
select e.*,
  ee.id ee_id,
  ee.emp_id ee_empid,
  ee.begin ee_begin,
  ee.end ee_end,
  ee.company ee_company,
  ee.job ee_job
from emp e left join emp_expr ee on e.id = ee.emp_id
where e.id = #{id}

使用xml书写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!--自定义结果集ResultMap-->
<resultMap id="empResultMap" type="com.itheima.pojo.Emp">
  <id column="id" property="id" />
  <result column="username" property="username" />
  <result column="password" property="password" />
  <result column="name" property="name" />
  <result column="gender" property="gender" />
  <result column="phone" property="phone" />
  <result column="job" property="job" />
  <result column="salary" property="salary" />
  <result column="image" property="image" />
  <result column="entry_date" property="entryDate" />
  <result column="dept_id" property="deptId" />
  <result column="create_time" property="createTime" />
  <result column="update_time" property="updateTime" />
  <!--封装exprList-->
  <collection property="exprList" ofType="com.itheima.pojo.EmpExpr">
    <id column="ee_id" property="id"/>
    <result column="ee_company" property="company"/>
    <result column="ee_job" property="job"/>
    <result column="ee_begin" property="begin"/>
    <result column="ee_end" property="end"/>
    <result column="ee_empid" property="empId"/>
  </collection>
</resultMap>
<!--根据ID查询员工的详细信息-->
<select id="getById" resultMap="empResultMap">
  select e.*,
    ee.id ee_id,
    ee.emp_id ee_empid,
    ee.begin ee_begin,
    ee.end ee_end,
    ee.company ee_company,
    ee.job ee_job
  from emp e left join emp_expr ee on e.id = ee.emp_id
  where e.id = #{id}
</select>

其他

MyBatis中#与$区别

符号 说明 场景 优缺点
#{…} 行时,会将#{…}替换为?,生成预编译SQL,并自动设置参数值 参数值传递 安全、性能高(推荐)
${…} 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题 表名、字段名动态设置时使用 不安全、性能低

连接池

SpringBoot默认连接池是Hikari(追光者)

Druid(德鲁伊)连接池是阿里巴巴开源的数据库连接池项目

配置使用Druid连接池

1
2
3
4
5
6
<dependency>
  <!-- Druid连接池依赖 -->
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.19</version>
</dependency>

修改配置

1
2
3
4
5
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.druid.username=root
spring.datasource.druid.password=1234

注解

SpringBoot

@RequestParam

包:org.springframework.web.bind.annotation.RequestParam

作用:用于获取请求参数,如:/depts?ids=1,2

1
2
3
4
5
@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer id){}

@DeleteMapping("/depts")
public Result delete(@RequestParam List<Integer> ids){}

@RequestBody

包:org.springframework.web.bind.annotation.RequestBody

作用:用于获取请求参数为json的数据

1
2
3
4
@PostMapping
public Result add(@RequestBody Dept dept){
	System.out.println("添加部门: " + dept);
}

@PathVariable

包:org.springframework.web.bind.annotation.PathVariable

作用:用于获取路由中的传递参数

1
2
@GetMapping("/{id}")
public Result getInfo(@PathVariable Integer id){}

@DateTimeFormat

包:

作用:

1
2
public void countSaleData(@DateTimeFormat(pattern="yyyy-MM-dd") LocalDate begin,
													@DateTimeFormat(pattern="yyyy-MM-dd") LocalDate end){}

/test?begin=2024-06-12&end=2024-08-12

@Value

包:

作用:加载配置内容

1
2
@Value("${aliyun.oss.endpint}")
private String endpoint;

@ConfigurationProperties

包:

作用:批量添加配置项

1
2
3
4
5
6
7
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
	private String endpoint;
	private String bucketName;
}
  • @ConfigurationProperties的实体类必须要提供getter / setter方法(@Data注解)
  • 实体类的属性名必须与配置项的名称一致
  • 该实体类要交给IOC容器管理(@Component注解)

@RestControllerAdvice

包:

作用:声明一个全局异常处理器

1
2
@RestControllerAdvice
public class GlobalExceptionHandler {}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

@ExceptionHandler

包:

作用:声明我们要处理的是哪一类型的异常

@ServletComponentScan

包:

位置:引导类上

作用:开启springboot对Servlet组件支持

WebFilter

包:

位置:过滤器类上

作用:配置过滤器要拦截的请求路径

属性:

  • urlPatterns:

@MapperScan

包:org.mybatis.spring.annotation.MapperScan;

作用:扫描指定包下的mapper,这个com.sky.mapper下的mapper类上就不需要加@Mapper注解了

位置:项目启动类上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@MapperScan("com.sky.mapper")
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
@EnableScheduling
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

MyBatis

MyBatis前身名称为iBatis,所以包名基本也都是iBatis

@Select

包:import org.apache.ibatis.annotations.Select;

作用:用于属性selct查询语句

1
2
@Select("select * from user")
public List<User> findAll();

@Mapper

包:org.apache.ibatis.annotations.Mapper

作用:程序运行时:框架会自动生成接口的实现类对象(代理对象),并给交Spring的IOC容器管理

1
2
3
4
@Mapper
public interface UserMapper {
  // 其他查询接口
}

@Options(useGeneratedKeys = true, keyProperty = “id”)

包:

作用:主键返回

1
2
3
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into emp(username, name, gender, phone, job,salary, image, entry_date, dept_id, create_time, update_time) " +"values (#{username},#{name},#{gender},#{phone},#{job},#{salary},#{image},#{entryDate},#{deptId},#{createTime},#{updateTime})")
void insert(Emp emp);

@Transactional

包:

作用:在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作

位置:

  • 方法:当前方法交给spring进行事务管理
  • 类:当前类中所有的方法都交由spring进行事务管理
  • 接口:接口下所有的实现类当中所有的方法都交给spring 进行事务管理

属性:

  • rollbackFor:用于指定回滚的异常类型。@Transactional(rollbackFor = Exception.class)

    默认只有运行时异常 RuntimeException才会回滚,@Transactional(rollbackFor = Exception.class)编译式异常也会回滚

  • propagation:用来配置事务的传播行为的。

    REQUIRED:【默认值】需要事务,有则加入,无则创建新事务

    REQUIRES_NEW :需要新事务,无论有无,总是创建新事务

    SUPPORTS:支持事务,有则加入,无则在无事务状态中运行

    NOT_SUPPORTED:不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务

    MANDATORY:必须有事务,否则抛异常

    NEVER:必须没事务,否则抛异常

Spring Cache

@EnableCaching

包:org.springframework.scheduling.annotation.EnableScheduling

作用:开启缓存注解功能

位置:加在启动类上

1
2
3
4
@EnableCaching
public class SkyApplication {
  
}

@Cacheable

包:org.springframework.cache.annotation.Cacheable;

作用:在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中

位置:方法上,常位于Controller的方法上

参数:

  1. cacheName: 等同于value,定义缓存的名称

  2. key:缓存的键值

    key="#id": 获取参数中的id的值

    key=“root.args[1]”: 获取第二个参数name的值

    key=“p1”: 获取第二个参数name的值

    key=“a1”: 获取第二个参数name的值

1
2
3
4
5
6
7
8
@Cacheable(cacheNames="user",key="#id") // 最终生成的key为:user::id的值
@GetMapping
public void getById(Long id) {}


@Cacheable(cacheNames="user",key="root.args[1]") // 最终生成的key为:user::name的值
@GetMapping
public void getById(Long id,String name) {}

@CachePut

包:org.springframework.cache.annotation.CachePut;

作用:将方法的返回值放到缓存中,一般不用,使用场景是:新增并有返回值的时候

位置:方法上

参数:

  1. cacheName: 等同于value,定义缓存的名称

  2. key:缓存的键值

    key="#result.id": 获取返回值中的id的值,#result:如果result是对象,可以通过result.xx获取属性值

1
2
3
4
5
6
@CachePut(cahceNames="user", key="#result.id")
@PostMapping
public User save(@ResponseBody User user){
  userMpaaer.insert(user);
  return user;
}

@CacheEvict

包:org.springframework.cache.annotation.CacheEvict ;

作用:将一条或多条数据从缓存中删除

位置:方法上,常位于Controller的方法上

1
2
@CacheEvict(CacheName="user",key="#id") // 删除指定id的缓存
@CacheEvict(CacheName="user",allEntries=true) // allEntries属性,默认值是false,设置为true代表删除全部

全局异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@RestControllerAdvice
public class GlobalExceptionHandler {
  //处理异常
  @ExceptionHandler
  public Result ex(Exception e){//方法形参中指定能够处理的异常类型
  	e.printStackTrace();//打印堆栈中的异常信息
  	//捕获到异常之后,响应一个标准的Result
  	return Result.error("对不起,操作失败,请联系管理员");
  }
}

过滤器

过滤器按照指定规则,把对资源的请求拦截下来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * 令牌校验过滤器
 */
@Slf4j
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //1. 获取请求url。
        String url = request.getRequestURL().toString();

        //2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if(url.contains("login")){ //登录请求
            log.info("登录请求 , 直接放行");
            chain.doFilter(request, response);
            return;
        }

        //3. 获取请求头中的令牌(token)。
        String jwt = request.getHeader("token");

        //4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)。
        if(!StringUtils.hasLength(jwt)){ //jwt为空
            log.info("获取到jwt令牌为空, 返回错误结果");
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);
            return;
        }

        //5. 解析token,如果解析失败,返回错误结果(未登录)。
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("解析令牌失败, 返回错误结果");
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);
            return;
        }

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

步骤:

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

过滤器链

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

执行优先级是按时过滤器类名的自动排序确定的,类名排名越靠前,优先级越高

假设我们有过滤器a,过滤器b,执行顺序

  1. 过滤器a的init方法
  2. 过滤器a的doFilter方法中chain.doFilter前面的内容
  3. 过滤器b的init方法
  4. 过滤器b的doFilter方法中chain.doFilter前面的内容
  5. 访问对应的web资源
  6. 过滤器b的doFileter方法中的chain.doFilter后面的内容
  7. 过滤器b的destroy方法
  8. 过滤器a的doFileter方法中的chain.doFilter后面的内容
  9. 过滤器a的destroy方法

拦截器

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Component
public class DemoInterceptor implements HandlerInterceptor {
  //目标资源方法执行前执行。 返回true:放行 返回false:不放行
  public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {
    return true; //true表示放行
  }
  // 目标资源方法执行后执行
  @Override
  public void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
  	System.out.println("postHandle ... ");
  }
  //视图渲染完毕后执行,最后执行
  @Override
  public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) throws Exception {
  	System.out.println("afterCompletion .... ");
  }
}

2、注册配置拦截器(实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class WebConfig implements WebMvcConfigurer {
  //自定义的拦截器对象
  @Autowired
  private DemoInterceptor demoInterceptor;
  
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    //注册自定义拦截器对象
    registry.addInterceptor(demoInterceptor).addPathPatterns("/**").excludePathPatterns("/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,不能匹配/emps/1

AOP

md5

使用spring提供的工具类DigestUtils可以直接使用

1
2
3
4
import org.springframework.util.DigestUtils;


pwd = DigestUtils.md5DigestAsHex(pwd.getBytes()); // 获得md5加密后的字符串

swagger

使用步骤

导入Knif4j的maven坐标

Knife4j是为java MVC框架集成Swagger生成api文档的增强解决方案

1
2
3
4
5
<dependency>
  <groupId>com.github.xiaoymin</groupId>
  <artifactId>knife4j-spring-boot-starter</artifactId>
  <version>3.0.2</version>
</dependency>

在配置类中加入knife4j相关配置

config/WebMvcConfiguration.java文件,类要增加@Configuration注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Bean
public Docket docket() {
  ApiInfo apiInfo = new ApiInfoBuilder()
    .title("苍穹外卖项目接口文档")
    .version("2.0")
    .description("苍穹外卖项目接口文档")
    .build();
  Docket docket = new Docket(DocumentationType.SWAGGER_2)
    .apiInfo(apiInfo)
    .select()
    // 扫描的控制器所在的包
    .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
    .paths(PathSelectors.any())
    .build();
  return docket;
}

设置静态资源映射,否则接口文档无法访问

与配置位于同一个文件

1
2
3
4
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
  registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
  registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}

注解

@Api

作用:作用于类上,表示对类的说明,这样在swagger文档中的一级目录名就会显示设置的内容了

1
2
@Api(tags="员工相关接口")
public class EmployeeController{}

@ApiOperation

作用与方法上,表示对方法的说明,这样在swagger文档中的二级目录名就会显示设置的内容了

1
2
3
4
5
@Api(tags="员工相关接口")
public class EmployeeController{
  @ApiOperation("员工登录")
  @PostMapping("/login")
}

@ApiModel

用在类上,例如:entiry,Dto,vo上,在请求参数上就会显示设置的名称了

1
2
@ApiModel(description="员工登录时传递的数据模型")
public class EmployeeLogin{}

@ApiModelProperty

用在类上,例如:entiry,Dto,vo上,在请求参数上就会显示设置的名称了

1
2
3
4
5
@ApiModel(description="员工登录时传递的数据模型")
public class EmployeeLogin{
  @ApiModelProperty("用户名")
  private String username;
}

线程

ThreadLocal

客户端的每次请求,后端的Tomcat服务器都会分配一个单独的线程来处理请求

是Thread的局部变量

ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问

常用方法

public void set(T value)

设置当前线程的线程局部变量的值

1
threadLocal.set(id);

public T get()

返回当前线程所对应的线程局部变量的值

1
threadLocal.get();

public void remove()

移除当前线程的线程局部变量

分页插件PageHelper

使用

导入PageHelper的maven坐标

1
2
3
4
<dependency>
	<group>com.github.pagehelper</group>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

定义分页实体类

1
2
3
4
5
6
7
8
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
    private long total; //总记录数

    private List records; //当前页数据集合
}

使用

1
2
3
4
5
6
// 1、设置分页参数
PageHelper.startPage(dto.getPage(),dto.getPageSize());
// 2、调用mapper的查询方法,并强转返回类型为page
Page<Employee> page = employeeMapper.list(dto.getName());
// 3、封装pageResult对象并返回
return new PageResult(page.getTotal(),page.getResult());

WebMvcConfigurationSupport

坑1

凡是继承了该类,都会出现日期转换成数组

为了能够将日期按照我们指定的格式显示(字符串指定格式),需要我们要去写自定义映射器

解决方法

在属性上加入注解,对日期进行格式化(不推荐)

1
2
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private LocatDateTime updateTime;

在继承了WebMvcConfigurationSupport的类中,扩展Spring MVC的消息转换器,统一对日期类型进行格式化转换

JacksonObjectMapper.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.sky.json;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

继承了WebMvcConfigurationSupport的类中,添加以下方法:

1
2
3
4
5
6
7
8
9
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
  log.info("扩展消息转换器...");
  //创建消息转换器对象
  MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
  //设置对象转换器,底层使用Jackson将Java对象转为json字符串
  messageConverter.setObjectMapper(new JacksonObjectMapper());
  //将我们自己的消息转换器对象追加到Spring mvc框架的容器中去
  converters.add(0, messageConverter);// 0最高优先级
}

坑2

当我们混合开发的时候,无法获取resources里的static静态资源,需要我们手动去映射

属性拷贝

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Override
public void saveEmp(EmployeeDTO employeedto) {
  Employee employee = new Employee();
  // 属性拷贝
  BeanUtils.copyProperties(employeedto, employee); // 第三个参数为忽略的字段,忽略的字段不参与属性拷贝
  employee.setStatus(StatusConstant.ENABLE);
  employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));

  Long empId = BaseContext.getCurrentId();

  employeeMapper.saveEmp(employee);
}

公共字段自动填充

问题

create_time,update_time公共字段在新增与更新的时候都需要进行数据填充

解决方案(aop):

  • 前置通知,增强mapper
  • 切入点表达式,选择@annotation方式

案例

1、自定义注解AutoFill,用于识别需要进行公共字段自动填充的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    OperationType value();
}
  • @Target(ElementType.METHOD)注解作用与方法上

  • @Retention(RetentionPolicy.RUNTIME)注解生命周期

    RetentionPolicy.SOURCE:注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。通常用于一些编译器相关的注解,如@Override

    RetentionPolicy.CLASS:表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。通常用于一些框架和工具的注解,如Spring的@Autowired

    RetentionPolicy.RUNTIME:表示注解存在于源代码、字节码和运行时中。这意味着在编译时、字节码中和实际运行时都可以通过反射获取到该注解的信息。通常用于一些需要在运行时处理的注解,如JUnit的@Test

  • OperationType value():注解的value的类型是枚举OperationType

2、自定义切面类AutoFillAspect,统一拦截加入AutoFill注解的方法,通过反射为公共字段赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.sky.aop;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;


@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointcut() {
    }

    @Before("autoFillPointcut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段自动填充");
        // 1、获取目标方法上的注解,并拿到注解里的属性值
        // 1.1 获取方法前面
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 1.2 获取方法对象
        AutoFill autoFill = methodSignature.getMethod().getAnnotation(AutoFill.class);
        // 1.3 获取注解的value值:insert/update
        OperationType operationType = autoFill.value();

        log.info("当前操作类型:{}", operationType);

        // 2、获取目标方法的参数对象
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) return;
        // 2.1 获取对应的实体对象
        Object entity = args[0];

        // 3、判断注解中属性值,如果是insert,就补充四个字段(创建日期、创建人、更新日期、更新人)
        switch (operationType) {
            case INSERT:
                log.info("开始进行插入操作,公共字段填充");
                try {
                    // setCreateTime
                    entity.getClass().getMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class).invoke(entity, LocalDateTime.now());
                    // setCreateUser
                    entity.getClass().getMethod(AutoFillConstant.SET_CREATE_USER, Long.class).invoke(entity, BaseContext.getCurrentId());
                    // setUpdateTime
                    entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class).invoke(entity, LocalDateTime.now());
                    // setUpdateUser
                    entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class).invoke(entity, BaseContext.getCurrentId());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                break;
            case UPDATE:
                log.info("开始进行更新操作,公共字段填充");
                try {
                    // setUpdateTime
                    entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class).invoke(entity, LocalDateTime.now());
                    // setUpdateUser
                    entity.getClass().getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class).invoke(entity, BaseContext.getCurrentId());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                break;
            default:
                break;
        }
    }
}

3、在mapper的方法上加入AutoFill注解

1
2
@AutoFill(value = OperationType.INSERT)
void insert(Category category);

涉及技术:

  1. aop
  2. 自定义注解
  3. 枚举
  4. 反射

Spring Data Redis

redis的java api有很多,常见的有:JedisLettuceSpring Data Redis

Spring Data Redis是Spring的一部分,对Redis底层开发包进行了高度封装,在Spring项目中,可以使用Spring Data Redis来简化操作。

使用

导入对应的Maven坐标

1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置redis的数据源

1
2
3
4
5
spring:
	redis:
		host: localhost
		port: 6379
		password: 123456

编写配置类,创建RedisTemplate对象(可选)

com.sky.config/RedisConfiguration.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis key的序列化器, 默认是: JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
      	// 不推荐设置redis value的序列化器
        //  redisTemplate.setValueSerializer(new StringRedisSerializer());
        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}

如果设置了value的序列化器,就无法存储java的list集合了,因为设置了value的序列化器,就只能存字符串了,不设置,RedisTemplate对象会将list集合序列化成字符串进行存储。

通过RedisTemplate对象操作redis

1
2
3
4
// redisTemplate.opsForValue()获取redis的string对象
redisTemplate.opsForValue().get("name"); 
redisTemplate.opsForValue().set("name","snailsir");
redisTemplate.delete("name");

Spring Cache

是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能

Spring Cache提供了一层抽象,底层可以切换不同的缓存实现

  • EHCache
  • Caffeine
  • Redis

1、导入Spring Cache的Maven的坐标

1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2、在启动类上添加注解@EnableCaching

3、在目标方法上使用注解

HttpClient

是Apache的一个子项目,是高效的、功能丰富的支持HTTP协议的客户端编程工具

导入HttpClient的Maven坐标

1
2
3
4
5
<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <scope>4.5.13</scope>
</dependency>

Get请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testGet() throws Exception {
  // 1、创建HttpClient对象
  CloseableHttpClient httpClient = HttpClients.createDefault();

  // 2、创建HttpGet请求对象--构造出请求路径、请求参数
  HttpGet httpGet = new HttpGet("http://localhost:8080/user/shop/status");

  // 3、调用HttpClient对象的execute方法,发送请求
  CloseableHttpResponse response = httpClient.execute(httpGet);

  // 4、解析响应数据
  int statusCode = response.getStatusLine().getStatusCode();
  System.out.println("statusCode = " + statusCode);
  HttpEntity entity = response.getEntity();
  if (entity != null) {
    String string = EntityUtils.toString(entity);
    System.out.println("entity = " + string);
  }
  // 5、释放资源
  response.close();
  httpClient.close();
}

Post请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 @Test
public void testPost() throws Exception {
  // 1、创建HttpClient对象
  CloseableHttpClient httpClient = HttpClients.createDefault();

  // 2、创建HttpPost请求对象--构造出请求路径、请求参数
  HttpPost httpPost = new HttpPost("http://localhost:8080/user/shop/status");

  // 2.1 构造请求体参数
  Map map = new HashMap();
  map.put("username", "admin");
  map.put("password", "123456");
  // 构造请求体
  StringEntity stringEntity = new StringEntity(map.toString());
  // 设置请求编码
  stringEntity.setContentEncoding("UTF-8");
  // 设置数据类型
  stringEntity.setContentType("application/json");
  // 设置请求体
  httpPost.setEntity(stringEntity);

  // 3、调用HttpClient对象的execute方法,发送请求
  CloseableHttpResponse response = httpClient.execute(httpPost);

  // 4、解析响应数据
  int statusCode = response.getStatusLine().getStatusCode();
  System.out.println("statusCode = " + statusCode);
  HttpEntity entity = response.getEntity();
  if (entity != null) {
    String string = EntityUtils.toString(entity);
    System.out.println("entity = " + string);
  }
  // 5、释放资源
  response.close();
  httpClient.close();
}

Spring Task

是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑

定时任务框架

适合于单体项目

作用:定时自动执行某段java代码

应用场景:

  1. 信用卡每月还款提醒
  2. 火车票售票系统处理未支付订单
  3. 入职纪念日为用户发送通知

cron表达式

cron表达式就是一个字符串,通过cron表达式可以定义任务触发的时间

SpringTask定时任务框架不支持年份字段,只有6个域(秒,分钟,小时,日,月,周)

1
 0 0 9 12 12 ? // 表示的是:12月12日上午9点整

cron表达式在线生成器:https://cron.qqe2.com

使用

1、导入maven坐标spring-context(已存在)

spring boot已经引入了

2、启动类上添加注解@EnableScheduling开启任务调度

1
2
@EnableScheduling
public class SkyApplication{}

3、自定义定时任务类,并添加@Component注解

com.sky.task/OrderTask.java

1
2
3
4
@Component
public class OrderTask {
  
}

4、类中定义方法,方法上加上@Scheduled(cron=“0/5 * * * * ?”)

1
2
3
4
5
6
7
@Component
public class OrderTask {
  @Scheduled(cron = "0/5 * * * * ?")
  public void outOfTimeOrder(){
		
  }
}

每隔5s执行一次outOfTimeOrder方法

WebSocket

是基于tcp的一种新的网络协议。它实现了浏览器与服务器全双工通信(浏览器和服务器只需要完成一次握手)两者之间就可以创建持久性的连接,并进行双向数据传输

Http协议与websocket协议对比

  • Http是短连接,websocket是长连接

  • http通信时单向的,基于请求响应模式

    websocket支持双向通信

  • http与websocket底层都是tcp连接

使用

1、导入Maven坐标

1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2、定义websocket类,并添加注解@ServerEndpoint与@Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * WebSocket服务
 */
@Slf4j
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        log.info("客户端:{}连接服务端成功", sid);
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        log.info("客户端:{}发送消息给服务端:{}", sid, message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        log.info("客户端:{}断开连接", sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
                log.info("服务器向客户端发送消息:{}", message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
使用 Hugo 构建
主题 StackJimmy 设计