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方法上或类上
作用:
- 写下方法上,将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器,
- 写在类上:
- 相当于该类所有的方法中都已经添加了@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();
}
|
三层架构
分层原因
软件设计原则:高内聚低耦合
高内聚:指的是一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。
低耦合:指的是软件中各个层、模块之间的依赖关联程序越低越好
分层
-
Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
-
Service:业务逻辑层。处理具体的业务逻辑
接口层:定义各种操作接口,文件名以Service
结尾
实现层:实现接口,文件名以ServiceImpl
结尾
-
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的区别
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
- @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>
|
注意:
- namespace中的值:Mapper接口全限定名
- id:Mapper接口中的方法名称
- 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>
|
<set>
标签:动态地在行首插入 SET 关键字,并会删掉额外的逗号
<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
包:
位置:过滤器类上
作用:配置过滤器要拦截的请求路径
属性:
@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的方法上
参数:
-
cacheName: 等同于value,定义缓存的名称
-
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;
作用:将方法的返回值放到缓存中,一般不用,使用场景是:新增并有返回值的时候
位置:方法上
参数:
-
cacheName: 等同于value,定义缓存的名称
-
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);
}
}
|
步骤:
- 定义一个类,实现 Filter 接口,并重写其所有方法
- 为定义的类加 @WebFilter注解,配置拦截资源的路径
- 引导类上加@ServletComponentScan 开启Servlet组件支持
过滤器链
在一个web应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链
执行优先级是按时过滤器类名的自动排序确定的,类名排名越靠前,优先级越高
假设我们有过滤器a,过滤器b,执行顺序
- 过滤器a的init方法
- 过滤器a的doFilter方法中chain.doFilter前面的内容
- 过滤器b的init方法
- 过滤器b的doFilter方法中chain.doFilter前面的内容
- 访问对应的web资源
- 过滤器b的doFileter方法中的chain.doFilter后面的内容
- 过滤器b的destroy方法
- 过滤器a的doFileter方法中的chain.doFilter后面的内容
- 过滤器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)
设置当前线程的线程局部变量的值
public T 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);
|
涉及技术:
- aop
- 自定义注解
- 枚举
- 反射
Spring Data Redis
redis的java api有很多,常见的有:Jedis
、Lettuce
、Spring 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提供了一层抽象,底层可以切换不同的缓存实现
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代码
应用场景:
- 信用卡每月还款提醒
- 火车票售票系统处理未支付订单
- 入职纪念日为用户发送通知
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();
}
}
}
}
|