springBoot
java Record
-
java14中预览的新特性叫做Record,在java中,Record是一种特殊的类。用来创建不可变类,语法简短。
-
java Record的特点:
- 带有全部参数的构造方法
- public访问器
- toString() ,hashCode(),equals().
- 无set,get方法。没有遵循bean的命名规范。
- final类,不可继承Record,record为隐式的final类。除此之外和普通的java类一样。
- 不可变类,通过构造构建Record
- final属性,不可修改。
- 不能声明实例属性,能声明static成员。
-
创建Record对象和创建普通的java对象一样,都是通过new的方式。
-
通过公共访问器public去获取属性,只读,不能修改。 方式: 对象名.属性名().
-
Record定义方法和普通的java类创建方法一致。
-
record有三种构造方法:紧凑的,规范的和定制构造方法。
- 紧凑型构造方法没有任何参数,甚至没有括号。
- 规范构造方法是以所有的成员作为参数。
- 定制构造方法是自定义参数个数。
lombok和Record对比
- lombok提供语法的便利性,通常预装一些代码模块,根据你加入到类中的注解自动执行代码模块。这样做纯粹是为了实现pojo类,通过预编译代码。将代码模块添加到class中去。
- record是语言级别的,一种语义定义,为了建模而用,数据聚合。提供了简单通用的数据类,充当数据载体,用于在类和应用程序站之间进行数据传输。
- record可作为局部对象使用。在代码块儿中定义并使用Record.
Instanceof 判断Record类型
public Record Person(String name,Integer age) {}
public boolean isEligible(Object obj) {
if (obj instanceof Person(String name,Integer age))
}
Record总结:
- abstract类java.lang.Record是所有Record的父类
- 有对于equals().hashCode().toString()方法的定义说明
- Record类能够实现序列化或者反序列化
Switch
支持箭头语法
public static void main( String[] args )
{
int week = 2;
// 表示计算结果
String memo = "";
switch(week) {
case 1 -> memo = "星期日,休息";
case 2,3,4,5,6 -> memo ="工作日";
case 7 -> memo = "星期六,休息";
default -> throw new RuntimeException("无效日期");
}
System.out.printf("memo:" + memo);
}
yeild让switch作为表达式,能够有返回值
-
语法:
变量 = switch(value) {case v1: yield 结果值;case v2:yield 结果值;case v3,v4,v5... yield 结果值}
-
示例:
@Test public void sort() { int week = 2; // 表示计算结果 String memo = switch(week) { case 1 : yield "星期日,休息"; case 2,3,4,5,6 : yield "工作日"; case 7 : yield "星期六,休息"; default : yield "无效日期"; }; System.out.printf("memo:" + memo); }
Text Block
-
text block处理多行文本十分方便,省时省力。无需连接“+”,单引号,换行符等。
-
语法:使用三个双引号字符括起来的字符串
""" 内容 """
-
文本块定义要求:
- 文本块以三个双引号字符开始,后跟一个行结束符
- 不能将文本块放在单行上
- 文本块的内容也不能在没有中间行结束符的情况下跟随三个开头双引号。
-
Text Block使用方式与普通字符串一样,==,equals比较,调用String类的方法。
-
indent()方法
String colors = """ red \ ;换行 在文本块儿中当作为空格来处理 \"blue"\ green """; String color = colors.indent(5) // 5是空格的个数 String stripIndent(): 删除每行开头和结尾的空白 String translateEscapes(): 转义序列转换为字符串字面量
var 声明局部变量
- var是一个保留字,不是关键字
- 方法内声明的局部变量,必须有初始值
- 每次声明一个变量,不能复合声明多个变量
- var动态类型是编译器根据变量所赋的值来推断类型
- var代替显示类型,代码简洁,减少不必要的排版
Sealed密闭类
- 主要特点是限制继承,java中通过继承增强,扩展类的功能,复用某些功能。
- 用法:
- sealed class 类名 permits 子类1,子类N列表{}
- 子类的声明有三种方式:
- final 终结,依然是密封的。
- sealed 子类是密封的,需要子类实现
- non-sealed 非密封的,扩展使用,不受限。
Core
SpringBoot特性:
- 创建独立的spring应用程序
- 嵌入式tomcat,jetty容器
- 提供的Starters简化构建过程
- 尽可能自动配置spring应用和第三方库。
- 提供生产指标
- 没有代码生成,无需xml配置
单一模块:
+====== Application.java 启动类
+====== controller 控制器包
StudentController.java
ScoreController.java
+===== service 业务层包
----inter 业务层接口
----impl 接口实现类包
+===== repository 持久层包
+===== model 模型包
---- entity 实体类包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BsKRBSqG-1691557379398)(C:\Users\李润鑫\AppData\Roaming\Typora\typora-user-images\image-20230421214904873.png)]
多模块:
-
代码结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nJOvmUf3-1691557379399)(C:\Users\李润鑫\AppData\Roaming\Typora\typora-user-images\image-20230421215747637.png)]
无父项目创建springboot的方式:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
核心注解:
- @SpringBootApplication功能:
- @SpringBootConfiguration包含@Configuration注解的功能
- @Configuration:JavaConfig的功能:配置类,结合@Bean能够将对象注入到Spring的ioc容器中。
- 有@SpringBootConfiguration标注的类是配置类
- @EnableAutoConfiguration :开启自动配置,将spring和其他第三方库中的对象创建好,注入到spring容器
- 避免编写xml,去掉样例代码,需要的对象,由框架提供。
- @ComponentScan :组件扫描器
- 扫描@Controller,@Service,@Repository,@Component注解,创建他们的对象注入到容器。
- springboot约定:启动类,作为扫描包的根,@ComponentScan扫描启动类所在的包和它的子包中的所有的类
- @SpringBootConfiguration包含@Configuration注解的功能
springBoot的jar文件和普通的jar文件:
- springBoot的jar文件可以执行
- BOOT—INF :class放的是应用的类,lib中放置的是应用的依赖
- 并且可以执行。
- spring-boot-loader :执行jar的spring boot类
- 普通的jar文件:
- 没有BOOt-INF
- 没有spring-boot-loader
- 不可执行
外部化配置
- 配置文件:(配置文件名字默认为application)
- properties
- properties是java中常用的一种配置文件格式。key=value,key是唯一的。
- yaml
- 是一种配置文件的数据格式。基本的语法是key:[空格]值。yaml文件扩展名是yaml或yml
- 基本语法规则:
- 大小写敏感
- 使用缩进表示层级关系
- 缩进只能使用空格
- 缩进的空格数目不重要,相同层级的元素左侧对齐即可。
- #表示注释,只支持单行注释
- 建议编写yaml文件时只用小写和空格
- yaml支持的三种数据结构
- 对象:键值对的集合,又称为映射(mapping)/哈希/字典
- 数组:一组按次序排列的值,又称为序列(sequence)/列表(list)
- 标量:单个的,不可再分的值。例如数字,字符串,true/false等
- application.properties
- 如果项目中properties和yml文件都存在,那么优先使用的是properties。推荐使用的是yml文件配置。
- application配置文件的名称和位置都可以进行修,约定的名称为application,位置在resources目录
- properties
@Value读取配置数据
- 该注解读取单个值,语法:${key:默认值}
@Value("${key:默认值}") // 在配置文件中找不到key的时候,使用的是默认值。
yml扁平化
app:
name: lession05-yml
owner: changming
port: 8008
// 当作app.name
Environment
- environment是外部化的抽象,是多种数据来源的集合。从中可以读取application配置文件,环境变量,系统属性。使用方式在bean中注入environment。调用它的getProperty(key)方法。
import组织多文件
-
如果将配置集中到一个application文件,导致文件配置过多,不易于阅读。我们将每个框架独立一个配置文件,最后将多个文件集中到application。
# 导入其他的配置文件,多个文件使用,作为分隔符 spring: config: import: 其他配置文件所在的路径
多环境配置
-
在resource根目录下创建环境配置文件
-
开发环境
myApp: memo: 这是开发环境的配置文件 spring: config: activate: on-profile: dev
-
测试环境
myApp: memo: 测试环境配置文件 spring: config: activate: on-profile: test
-
在application.yml中激活某个环境:
app: name: 1 owner: 2 port: 8080 #导入其他配置 spring: config: import: 配置文件的路径 #激活某个环境配置文件 proifiles: active: dev
通过bean来获取配置信息
- 有多个属性
//@Component
// 创建普通bean,非spring代理
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppBean {
// key的名称和属性名相同,框架会调用setXXX方法给属性复制
// 属性必须是非静态的属性
private String name;
private String owner;
private Integer port;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
@Override
public String toString() {
return "AppBean{" +
"name='" + name + '\'' +
", owner='" + owner + '\'' +
", port=" + port +
'}';
}
}
-
yml配置信息
# 编写配置项 app: name: zhangsan owner: lisi port: 8087
嵌套Bean
-
yml配置信息
app: name: zhangsan owner: lisi port: 8087 security: username: 王五 password: 123456
启用和扫描注解
- @EnableConfigurationProperties注解:
// 启用@ConfigurationProperties,属性是类的名字、
@EnableConfigurationProperties({NestAppBean.class})
- 扫描注解的包名,其中绑定bean会被注入到spring容器中去
@ConfigurationPropertiesScan(basePackages = {"com.lirunxin.pk.pk1"})
// 参数是需要绑定的bean所在的包,一般使用在多个bean的情况
绑定第三方对象:
- 如果某个类需要在配置文件中提供数据,但是没有源代码,此时@ConfigurationProperties结合@Bean一起在方法上使用。
# 第三方库对象,没有源代码
security:
username: 123
password: 456
@Configuration
public class ApplicationConfig {
// 创建bean对象,属性值来自于配置文件
@ConfigurationProperties(prefix = "security")
public Security createSecurity() {
return new Security();
}
}
绑定Map-List-Array
-
yml中的配置
# 配置集合 # 数组 和list集合配置一样,一个"-"表示一个成员 names: - lisi - zhangsan # List<myServer> myServerList myServerList: - title: 华为 ip: 202.39.12.1 - title: 西南服务器 ip: 106.90.23.229 # Map<String,User> userMap userMap: user1: name: 张三 sex: 男 age: 22 user2: name: 李四 sex: 男 age: 26
绑定数据来源
-
指定某个文件作为数据源,@propertySource,用来加载指定的properties文件。@PropertySource与@Configuration一同使用
其他注解还有@Value,@ConfigurationProperties。
@Configuration // 表示这是一个类
@ConfigurationProperties(prefix = "group") // 绑定bean的一个注解,配置bean的属性
@PropertySource(value = "classpath:/group-info.properties") // 指定文件来源
public class Group {
private String name;
private String leader;
private Integer numbers;
@Override
public String toString() {
return "Group{" +
"name='" + name + '\'' +
", leader='" + leader + '\'' +
", numbers=" + numbers +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLeader() {
return leader;
}
public void setLeader(String leader) {
this.leader = leader;
}
public Integer getNumbers() {
return numbers;
}
public void setNumbers(Integer numbers) {
this.numbers = numbers;
}
}
创建对象的三种方式
-
将对象注入spring容器中
-
传统的xml配置文件
-
在配置类的上边加入注解@ImportResource
@ImportResource(location = {"classpath:/applicationContext.xml"}) public class Lession07ConfigApplication{ }
-
-
java config技术,@Configuration和@Bean
-
创建对象的注解,@Controller,@Service,@Repository,@Component
-
AOP
-
Aspect:表示切面。开发自己编写功能增强代码的地方。这些代码会通过动态代理加入到原有的业务方法当中去。@Aspect注解表示当前类是切面类,切面类是一个普通类。
-
Joinpoint表示连接点,连接切面和目标对象。
-
切入点(Pointcut):
-
Advice:表示增强的功能执行时间。java代码执行的单位是方法,方法是业务逻辑代码,在方法之前增加新的功能,还是方法之后增加功能。表示在方法前,后等的就是通知
- @Before:在切入点方法执行之前执行。
- @After:在切入点方法之后执行
- @AfterRunning:切入点方法返回后执行
- @AfterThrowing:切入点抛出异常执行
- @Aroud:属于环绕增强。能控制切入点执行前,后。功能最强的注解。
-
maven添加aop
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
访问数据库
jdbcTemplate
- 编写sql脚本
#data.sql : 操作表中的数据
# schema.sql : 对表的结构进行操作
-
在核心配置文件中配置数据源
# 配置数据源 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false spring.datasource.username=root spring.datasource.password=li199991212 #设置执行数据库的脚本 always : 总是执行 第一次执行之后就改为never spring.sql.init.mode=always
- jdbcTemplate归纳:
- execute方法:可以用于执行任何的sql语句,常用来执行DDL语句。
- update,bathUpdate方:用于执行新增,修改,删除等语句。
- query和queryForXXX方法:用于执行查询相关的语句。
- call方法:用于执行数据库存储过程和函数相关的语句。
- jdbcTemplate归纳:
-
使用jdbcTemplate中注入对象
lomok注解:@Data @AllArgsConstructor : 全参构造 @noArgsConstructor:无参构造 @Resource private JdbcTemplate jdbcTemplate;
-
使用模板类
@Test
void testCount() {
String sql = "select count(*) as ct from article";
Long count = jdbcTemplate.queryForObject(sql,Long.class);
System.out.ptintln("文章总数:" + count);
}
// 单行记录
@Test
void testQuery() {
// ? 作为占位符
String sql = "select * from article where id = ?";
// BeanPropertyRowMapper 将查询的结果集,列名和属性名进行名称匹配,名称完全匹配或者驼峰
ArticlePO article = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<>(ArticlePO.class),1);
System.out.println("查询到文章:" + article);
}
自定义RowMapper
# 自定义RowMapper
@Test
void testQueryRowMapper() {
// 只能查询一个记录,查询不出记录抛出异常
String sql = "select * from article where id = " + 1;
ArticlePO article = jdbcTemplate.queryForObject(sql,(rs,rownum) -> {
var id = rs.getInt("id");
var userId = rs.getInt("user_id");
var title = rs.getString("title");
var summary = rs.getString("summary");
var readCount = rs.getInt("read_count");
var createTime = new Timestamp(rs.getTimestamp("create_time").getTime()).toLocalDateTime();
var updateTime = new Timestamp(rs.getTimestamp("update_time").getTime()).getTime();
return new ArticlePO(id,userId,title,summary,readCount,createTime,updateTime);
});
System.out.println("查询文章:" + article);
}
查询多行记录
@Test
void testList() {
String sql = "select * from article order by id";
// 一个list成员一个记录,Map是列名和值
List<Map<String,Object>> listMap = jdbcTemplate.queryForList(sql);
listMap.forEach(el -> {
el.forEach((field,value) -> {
System.out.println("字段名:" + field ",列值:" + value);
});
System.out.println("================");
});
}
命名参数
@AutoWired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Test
void testNameQuery() {
// :参数名
String sql = "select sum(*) as ct from article where user_id = :uid and read_count > :num";
Map<String,Object> param = new HashMap<>();
param.put("uid",2101);
param.put("num",0);
Long count = namedParameterJdbcTemplate.queryForObject(sql,param,long.class);
System.out.println("用户阅读的文章数:" + count);
}
多表查询
- 多表查询结果映射为Java Object的常用两种方案
- 将查询结果映射为Map,列名为key,列值为Value,比较通用,适用于查询任何表。
- 根据查询结果创建相对应的实体类,属性和查询结果的列名对应。
- 将查询结果自定义为rowMapper,ResultSetExtractor映射为实体类对象。
Mybatis
单表的CRUD
-
先写实体类
-
@MapperScan(basePackage = “mapper接口所在的全路径”) : 在启动类中添加该注解
-
使用注解
@Select("""
slect * from article where id = #{articleId}
"""
)
Article selectById(@param("articleId") Integer id);
- 配置mybatis支持驼峰命名和数据库中的带下划线的字段对应
#配置mybatis,在springBoot.properties文件中配置
mybatis.configuration.map-underscore-to-camel-case=true
- @Results结果集映射
@Results(id = "BaseArticleMap",value = {
// column表示数据库中的列名,property表示实体类中的属性名
@Result(id = true,column = "id",property="id" ),
@Result(column = "user_id",property="userId")
})
使用注解开发:
- 加入mybatis的starter,mysql驱动
- 创建实体类,xxxPO,xxxEntity,xxxDomain
- 创建mapper接口,在接口中定义方法,在方法的上边使用合适的注解。
- 在启动类的上边加入@MapperScan(basePackage=“mapper接口所在的全路径”)
- 编写application.properties:
- 定义数据库连接信息
- mybatis设置,包括日志,驼峰命名等
结果映射@ResultMap
-
将查询结果中的列和实体bean的属性的对应关系。通过xml文件中的标签来定义映射关系,在其他的来完成映射。
-
注解的使用:
- @Results和@Result
- @ResultMap
-
使用了@Results之后再使用@ResultMap(value = “存放的是@Results注解中的id值”)
-
第二种映射:在xml文件中定义,在代码中使用@ResultMap(value=“xml中的id”)
-
在配置文件中指定Mapper文件所在的位置:
myabtis.mapper-location=classpath:/xml文件路径
-
SqlProvider
-
将sql语句以方法的形式定义在单独的类中,mapper接口通过引用sql提供者类中的方法名,表示要执行的sql。
-
sql提供者注解有四类:@SelectProvider,@InsertProvider,@UpdateProvider,@DeleteProvider
-
sql提供者首先创建提供者类,自定义的。类中声明静态方法,方法体是sql语句并且返回sql语句
public static String selectById() { return "select * from users where id = #{}"; }
-
其次Mapper接口的方法上面,应用@SelectProvider(type=“提供者类.class”,method=“方法名称”)
@One一对一查询
public interface ArticleOnetoOneMapper {
ArticleDetail selectDetail(Integer articleId);
@Select("""
select id,user_id,title,summary,read_count from article where id = #{id}
"""")
@Results({
@Result(id = true,column = "id",property="id" ),
@Result(column = "user_id",property="userId"),
// 这个property的值对应的是一对一中的任意一方,
// fetchType=FetchType.LAZY: 懒加载模式。需要查询的时候才去真正的查询
@Result(colum = "id",property = "articleDetail",
one=@one(select = "selectDetail方法的全限定名称",fetchType=FetchType.LAZY))
})
Article selectAllArticle(Integer id);
}
@Many
-
一对多查询使用的是@Many注解,像一个视频有多个评论
public interface ArticleOnetoOneMapper { List<Comment> selectComment(Integer articleId); @Select(""" select id,user_id,title,summary,read_count from article where id = #{id} """") @Results({ @Result(id = true,column = "id",property="id" ), @Result(column = "user_id",property="userId"), // 这个property的值对应的是一对一中的任意一方, // fetchType=FetchType.LAZY: 懒加载模式。需要查询的时候才去真正的查询 @Result(colum = "id",property = "commentList", many=@many(select = "selectDetail方法的全限定名称",fetchType=FetchType.LAZY) ) }) Article selectArticle(Integer id);
在application.properties中指定mybatis的核心配置文件
mybatis.config-location=classpath:/mybatis-config.xml
连接池设置
- 使用mysql数据库的一些建议的设置
prepStmtCacheSize
这是用来设置Mysql驱动程序将缓存每个连接的预准备语句。默认值为25.建议将其设置在250-500之间。
prepStmtCacheSqlLimit
这是驱动程序将缓存的准备好的sql语句的最大长度。mysql默认的值为256,建议2048.
cachePrepStmts
如果缓存实际上被禁用,则上述参数没有任何影响,因为默认情况下是禁用的。必须将此参数设置为:true
useServerPrepStmts: 较新的版本支持服务器端准备语句。这可以提供实质性的性能提升,将此属性设置为true
事务
声明式事务
-
事务分为全局事务与本地事务,本地事务是特定于资源的。本地事务更容易使用,但是不能跨越多个事务资源工作,比如在方法中处理连接多个数据库的事务,本地事务是无效的。
-
spring框架同时提供声明式和编程式事务管理。推荐使用声明式事务管理。
-
事务的属性:
- Propagation:传播行为。代码可以继续在现有事务中运行。也可以暂停现有事务并创建新事务。
- Isolation:隔离级别。此事务与其他事务的工作隔离的程度。
- Timeout超时时间:该事务超时和被底层事务基础结构自动回滚之间运行的时间。
- Read-only只读状态:当代码读取但是不修改数据时,可以使用只读事务。
-
声明式事务的方式:
- xml配置文件:全局配置
- @Transactional注解驱动:和代码一起提供,比较直观。和代码的耦合度高。
事务注解
// 开启事务管理器,在启动类上添加
@EnableTransactionManagement
/**
事务控制注解的使用 位置:
1,方法的上边, 2,类的上边
事务回滚:
1,默认对运行时异常进行回滚,rollback
rollbackFor: 指定回滚的异常类的列表 :当业务抛出列表中的异常时,执行rollback
*/
@Transactional(rollbackFor = {})
@Override
public boolean postNewArticle(ArticlePO article, String content) {}
-
spring事务处理是aop的环绕通知,只有通过代理对象调用具有事务的方法才能生效。类中有A方法,调用带有事务的B方法。调用A方法事务无效。同时protected,private方法是默认没有事务方法的。
- 意思就是说在一个没有事务的方法中调用具有事务的方法,会导致该事务失效。
-
第二种事务失效
- 方法在线程中运行,在同一个线程中方法具有事务功能,那么在该方法中新开启的线程的代码不具有事务的功能。
- 即新的线程和具有事务功能的线程不是同一个
- 方法在线程中运行,在同一个线程中方法具有事务功能,那么在该方法中新开启的线程的代码不具有事务的功能。
事务回滚规则
- RuntimeException的实例或子类时回滚事务
- Error会导致回滚
- 已检查异常不会回滚。默认提交事务。
- @Transactional注解的属性控制回滚
- rollbackFor
- noRollbackFor
- rollbackForClassName
- noRollbackForClassName
SpringBoot-WEB
编写Spring MVC的应用步骤
- 编写请求页面(在浏览器直接模拟请求)
- 编写Controller
- 编写视图页面
json视图
-
是一种只需要数据的视图,比如手机应用app,app的数据来自于服务器应用的处理结果。app内的数据显示和服务器无关,只需要数据。主流方式是服务器返回json格式的数据给手机app应用。
-
转换为json格式的方式
-
@RequestMapping("/exam/json") public void responseJson(HttpServletResponse response) throws IOException { String json = "{\"name\":\"李四\",\"年龄\":20}"; // 应答,通过HttpServletResponse进行输出 response.setContentType("application/json;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.println(json); writer.flush(); writer.close(); }
-
springMVC支持控制器方法返回对象,由框架将对象使用jackson转为json并输出
@ResponseBody public User getUserJson() { User user = new User(); user.setUsername("张三"); user.setAge(22); return user; // User会交给jackson工具库中的ObjectMapper对象,会将User转换为json格式的字符串,用HttpServletResponse输出 }
-
-
接收请求的注解
- @GetMapping:接收get请求,快捷的RequestMapping(method=RequestMethod.GET)
- @PostMapping: 接收post请求
- @PutMapping:接收的是put请求
- @DeleteMapping:接收的是delete请求
-
构建前-后端分离
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZC1g2p1-1691557379400)(C:\Users\李润鑫\AppData\Roaming\Typora\typora-user-images\image-20230506204407616.png)]
-
设计自己网站的logo时,进入网址设计完成,在视图的标签下边加入
控制器
-
控制器是一种spring管理的bean对象。赋予角色是"控制器"。作用是处理请求,接收浏览器发送过来的参数,将数据和视图应答给浏览器或者客户端app。
-
有@Controller,@RestController注解的类叫做控制器,项目中控制器有很多,一般相关的业务属于一个控制器。
- @RestController包含了@Controller的功能,同时加入了@ResponseBody注解的功能。表示当前类中所有的方法都会加入@ResponseBofy的功能。
- @Controller注解有@Component的功能,控制类对象是spring容器管理的。
-
定制控制器方法
- 方法上边的@RequestMapping(value=“请求的uri地址”):表示这个uri请求交给当前的方法来处理。
-
控制器方法的形参,接收请求的参数,多种方法来接收参数
-
控制器方法的返回值,表示应答结果,给请求的结果(数据,视图)
路径
匹配请求路径到控制器方法(uri定义方法)
-
在springBoot3中推荐使用的是PathPatternParser,在核心配置文件中指定
spring.mvc.pathmatch.matching-strategy=path_pattern_parser=
-
PathPatternParser中有关uri的定义
-
通配符:
-
?:一个字符
public class ExamPathController { // ? 表示一个字符 @GetMapping("/file/t?st.html") public String path1(HttpServletRequest request) { return "path:" + request.getRequestURI(); } }
-
*:0或多个字符。在一个路径段中匹配字符,不能匹配路径 /
-
**:匹配0个或者多个路径段,相当于所有
public class ExamPathController { // GET http://localhost:8080/pic/ 符合这样的才能正确运行 @GetMapping("/pic/**") public String path1(HttpServletRequest request) { return "path:" + request.getRequestURI(); } }
-
正则表达式:支持正则表达式
-
-
-
RestFul风格
@GetMapping("/order/{*id}") {*id} 匹配/order开始的所有的请求,id表示order后面直到路径末尾的所有内容。id自定义路径变量名称。与@PathVariable一样使用。 使用@GetMapping("/order/{*id}/{*name}") 是不允许的
参数接收
-
接收参数方式:
-
请求参数和形参一一对应,适用于简单类型。形参可以有合适的数据类型,比如String,Integer,int等。
- 形参名和请求中的名字保持一致。
- 前端传过来的参数都是String类型的,如果控制器方法中定义的是其他类型的数据,那么是由框架将String转换为其他类型的数据的。
-
对象类型,控制器方法形参是对象,请求的多个参数名和属性名相对应。
// 使用对象接收参数,要求,属性名和请求中的参数名相同,属性具有set,get方法,类有无参数的构造方法 @GetMapping(”/param/p2“) public String p2(Person person) { return "参数:" + person.toString(); }
-
@RequestParam注解,将查询参数,form表单数据解析到方法参数,解析multipart文件上传。参数必须是基本数据类型的。
public String p4(@RequestParam(value="name") String name) { }
-
@RequestBody,接收前端传递的json格式参数。
// @RequestBody:从请求体中读取json数据,将数据转为形参对象的属性值 // 框架创建User对象,将username,agekey的值赋值给对象的同名属性 @PostMapping("/param/json") public String getJsonData(@RequestBody User user) { return "json转为User对象" + user.toString(); }
-
HttpServletRequest使用request对象接收参数,request.getParameter(“…”);
-
@RequestHeader,从请求header中获取某项值
-
使用Reader,InputStream读取post请求体中的数据
// 使用Reader,InputStream读取post请求体中的数据 @PostMapping("/param/json2") public String p6(Reader reader) { StringBuffer content = new StringBuffer(""); try(BufferedReader bin = new BufferedReader(reader)) { var line = ""; while ((line = bin.readLine()) != null) { content.append(line); } } catch (IOException e) { System.out.println(e); } return content.toString(); }
-
数组参数接收多个值
@GetMapping("/param/vals") public String getMultival(Integer[] id) { List<Integer> idList = Arrays.stream(ids).toList(); return idList.toString(); }
-
参数验证
-
服务端的程序,Controller在方法中接收了参数,这些参数是由用户提供的,使用之前校验参数是否合法。
-
两种方法:
- 编写代码,手工验证,主要是if语句
- Java Bean Validation:叫做Bean Validation,是一个运行时的数据验证规范,为javaBean验证定义了相应的元素模型和api。
-
常用注解:
- @Null,被注释的元素必须为空
- @AssertTrue:被注释的元素必须是true
- @AssertFalse:被注释的元素必须是false
- @Min(Value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- @Max(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- @DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- @DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- @Size(max,min):被注释的元素的大小必须在指定的范围内。
- @Digits(Integer,fraction):被注释的元素必须是一个数字,其值必须在一个可接受的范围内
- @Pattern(value):被注释元素必须符合指定的正则表达式
- @Email:被注释的元素必须是电子邮箱地址
- @NotEmpty:被注释的字符串必须非空
-
例子:
-
添加依赖
<!-- Bean Validation : bean验证--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
创建类,添加属性约束注解
@Data public class ArticleVo { private Integer id; @NotNull(message = "必须有作者") private Integer userId; // 不能为空 @NotBlank(message = "文章必须有标题") // @Size认为null是有效值。 @Size(min = 3,max = 30,message = "标题必须在3个字符以上") private String title; @NotBlank(message = "必须得有副标题") @Size(min = 5,max = 30,message = "副标题在5个字以上") private String summary; @DecimalMin(value="0",message = "阅读数量不能小于0") private Integer readCount; @Email(message = "邮箱不符合规则") private String Email; }
-
在Controller中使用
// @Validated 验证Bean BindingResult:包含Bean的验证结果 @PostMapping("/article/add") public Map<String,Object> addReticle(@Validated @RequestBody ArticleVo article, BindingResult result) { // servce处理文章的业务 Map<String,Object> map = new HashMap<>(); if (result.hasErrors()) { // 返回没有通过的属性值 List<FieldError> fieldErrors = result.getFieldErrors(); fieldErrors.forEach(fieldError -> { // 第一个拿到出错的属性值,第二个拿到出错的提示信息 map.put(fieldError.getField(),fieldError.getDefaultMessage()); }); } // 返回结果数据 return map; }
-
分组验证
-
在创建新的文章时,id值为空,但是在修改文章时,id的值必须存在
-
使用步骤:
-
@Data public class ArticleVo { // 组就是接口名 public static interface AddArticleGroup{} public static interface EditArticlegroup{} @NotNull(message = "文章主键必须有值",groups = {EditArticlegroup.class}) @Min(value = 1,message = "id必须大于0",groups = {EditArticlegroup.class}) private Integer id; @NotNull(message = "必须有作者",groups = {EditArticlegroup.class,AddArticleGroup.class}) private Integer userId; // 不能为空 @NotBlank(message = "文章必须有标题",groups = {EditArticlegroup.class,AddArticleGroup.class}) // @Size认为null是有效值。 @Size(min = 3,max = 30,message = "标题必须在3个字符以上",groups = {EditArticlegroup.class,AddArticleGroup.class}) private String title; @NotBlank(message = "必须得有副标题") @Size(min = 5,max = 30,message = "副标题在5个字以上",groups = {EditArticlegroup.class,AddArticleGroup.class}) private String summary; @DecimalMin(value="0",message = "阅读数量不能小于0",groups = {EditArticlegroup.class,AddArticleGroup.class}) private Integer readCount; @Email(message = "邮箱不符合规则",groups = {EditArticlegroup.class,AddArticleGroup.class}) private String Email; }
-
controller中使用
@RestController public class valiController { // @Validated 验证Bean BindingResult:包含Bean的验证结果 @PostMapping("/article/add") public Map<String,Object> addReticle(@Validated({ArticleVo.AddArticleGroup.class}) @RequestBody ArticleVo article, BindingResult result) { // servce处理文章的业务 Map<String,Object> map = new HashMap<>(); if (result.hasErrors()) { // 返回没有通过的属性值 List<FieldError> fieldErrors = result.getFieldErrors(); fieldErrors.forEach(fieldError -> { // 第一个拿到出错的属性值,第二个拿到出错的提示信息 map.put(fieldError.getField(),fieldError.getDefaultMessage()); }); } // 返回结果数据 return map; } }
-
-
ResponseEntity
@GetMapping("/json2")
@ResponseBody
public ResponseEntity<User> responseEntity () {
User user = new User();
user.setAge(12);
user.setUsername("张三");
ResponseEntity<User> response = new ResponseEntity<>(user, HttpStatus.OK);
return response;
}
配置服务器
# 配置服务器
server.port=8080
# 应用访问路径
server.servlet.context-path=/api
# 编码方式
server.servlet.encoding.charset=utf-8
# 强制响应对象和应答对象都是utf-8的形式
server.servlet.encoding.force=true
配置日志信息
# 配置日志信息
# 日志路径
server.tomcat.accesslog.directory=D:/logs
# 启动访问日志
server.tomcat.accesslog.enabled=true
# 日志文件名前缀
server.tomcat.accesslog.prefix=access_log
# 日志文件的日期信息
server.tomcat.accesslog.file-date-format=yyyy-MM-dd
# 日志文件名后缀
server.tomcat.accesslog.suffix=.log
# post请求的最大容量,默认为2M
server.tomcat.max-http-form-post-size=2000000
# 服务器最大连接数
server.tomcat.max-connections=8192
配置DispatcherServlet
# 配置DispatcherServlet
spring.mvc.servlet.path=/course
spring.mvc.servlet.load-on-startup=0
# 全局的日期格式
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
总结:
- HandlerMapping:根据请求的uri地址,找到处理此请求的Controller对象
- HandlerAdapter:使用适配器模式,调用执行具体的控制器的方法。
- ViewResolver:处理视图,创建视图对象View。
- HandlerExpectionResolver:异常处理器,处理请求中的异常。
@ServletComponentScan注解
-
在启动类中使用,目的是告诉servlet所在的包
@ServletComponentScan(basePackages = "")
用代码方式创建servlet
@Bean
public ServletRegistrationBean servletRegistration() {
// 创建对象ServletRegistrationBean,登录一个或者多个servlet
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(new LoginServlet());
// 登记该servlet的访问路径
registrationBean.addUrlMappings("/user/login");
// 设置创建时间
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
过滤器
-
@WebFilter(urlPatterns = “/*”) : 访问web的什么请求会被拦截
-
使用该方式需要在类上面添加注解
// 注解方式使用Filter @WebFilter(urlPatterns = "/*") public class LogFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String uri = ((HttpServletRequest) servletRequest).getRequestURI().toString(); System.out.println("过滤器执行了,uri" + uri); filterChain.doFilter(servletRequest,servletResponse); }
-
代码方式使用filter,不需要子类上添加注解@WebFilter(urlPatterns=“”)
@Bean public FilterRegistrationBean filterRegistrationBean() { // 登记Filter对象 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new LogFilter()); // 指定需要被过滤的请求路径 filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; } // 在程序中访问某一个资源的时候,就会通过编码方式中注册的过滤器进行过滤
过滤器排序
-
多个Filter对象如果要进行排序,有两种途径
-
过滤器名称,按字典顺序排序,AuthFilter > LogFilter
-
FilterRegistraionBean登记Filter,设置order顺序,数值越小,就越先执行
@Bean public FilterRegistrationBean filterRegistrationBean() { // 登记Filter对象 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new LogFilter()); // 指定需要被过滤的请求路径 filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.setOrder(2); // 值越小,越先执行 return filterRegistrationBean; }
-
在框架中使用Filter
-
需要通过FilterRegistrationBean注册Filter对象。
-
例子:
-
在配置文件中配置
# 设置日志为debug logging.level.web=debug
-
代码使用
public FilterRegistrationBean addCommoneLogFilter() { FilterRegistrationBean commens = new FilterRegistrationBean(); CommonsRequestLoggingFilter commonsRequestLoggingFilter = new CommonsRequestLoggingFilter(); commens.setFilter(commonsRequestLoggingFilter); // 拦截地址 commens.addUrlPatterns("/*"); return commens; }
-
监听器
-
@WebListener用于注释监听器,监听器必须实现某个接口
-
另一种方式使用ServletListenerRegistrationBean登记Listener对象。
-
监听器的创建
@WebListener("listener的描述信息") public class MySessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { HttpSessionListener.super.sessionCreated(se); } }
WebMvcConfigurate
- WebMvcConfigurer作为配置类,采用javaBean的形式代替了传统的xml配置文件形式进行针对框架个性化定制,就是Spring mvc xml配置文件的javaConfig(编码)实现方式。
- WebMvcConfigurer是一个接口,需要自定义某个对象,实现并覆盖某个方法
页面跳转控制器
-
要跳转到某个页面,必须通过controller对象,我们需要创建一个Controller,转发到一个视图才行。addViewControllers()完成从请求到视图的跳转。
/** * SpringMvc配置:使用的是javaConfig的方式配置,代替xml配置文件 */ @Configuration public class MvcSettings implements WebMvcConfigurer { /** * 页面的跳转控制器,从请求直达视图页面(无需controller) * @param registry ViewControllerRegistry 视图控制器的注册器 */ @Override public void addViewControllers(ViewControllerRegistry registry) { /** * addViewController("请求的uri地址") * setViewName("目标视图") : 指定视图 */ registry.addViewController("/welcome").setViewName("index"); } }
数据格式化
-
Formatter是数据转化接口,将一种数据类型转换为另一种数据类型。与Formatter功能类型的还有Converter<S,T>.因为WEB的请求的参数都是String,有时需要将String转为Integer,Long,Date等待。
-
Formatter只能将String类型的数据转为其他数据类型。
-
使用步骤:
-
创建数据类
import lombok.Data; @Data public class DeviceInfo { private String item1; private String item2; private String item3; private String item4; private String item5; }
-
自定义Formatter
/** * Formatter<DeviceInfo> : 表示最终要将字符串转换为DeviceInfo对象 * */ public class DeviceFormatter implements Formatter<DeviceInfo> { /** * * @param text 代表发送请求时的参数 * @param locale * @return * @throws ParseException */ @Override public DeviceInfo parse(String text, Locale locale) throws ParseException { DeviceInfo info = null; if (StringUtils.hasLength(text)) { String[] items = text.split(";"); info = new DeviceInfo(); info.setItem1(items[0]); info.setItem2(items[1]); info.setItem3(items[2]); info.setItem4(items[3]); info.setItem5(items[4]); } return info; } @Override public String print(DeviceInfo object, Locale locale) { StringJoiner stringJoiner = new StringJoiner("#"); stringJoiner.add(object.getItem1()); stringJoiner.add(object.getItem2()); stringJoiner.add(object.getItem3()); stringJoiner.add(object.getItem4()); stringJoiner.add(object.getItem5()); return stringJoiner.toString() ; } }
-
登记自定义的DeviceFormatter
@Configuration public class MvcSettings implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter(new DeviceFormatter()); } }
-
拦截器
-
HandlerIntercepter接口和它的实现类称为拦截器。拦截器是spingMVC框架的对象与servler无关。拦截器能够预先处理发给Controller的请求。可以决定是否被Controller处理。用户请求是先由DispatcherServlet接收后,在Controller之前执行拦截器请求。
-
拦截器使用场景:
- 权限验证
- 记录日志
- 过滤字符
- 登录的token处理都可以使用拦截器。
-
拦截器的定义步骤:
- 声明类实现HandlerInterceptor接口,重写三个方法,需要哪个重写哪个
- 登记拦截器
-
需求:张三操作员用户,只能查看文章,不能删除,修改
-
代码实例:
-
创建ArticleController
@RestController public class ArticleController { @PostMapping("/article/add") public String addArticle() { return "发布新的文章"; } @PostMapping("/article/edit") public String editArticle() { return "修改文章"; } @GetMapping("/article/query") public String quertArticle() { return "查询文章"; } }
-
实现拦截器接口
public class AuthInterceptor implements HandlerInterceptor { private static final String COMMON_USER = "zhangsan"; // 判断登录的用户,能否执行相应操作 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("验证身份权限拦截器"); // 获取登录用户 String loginUser = request.getParameter("loginUser"); // 获取请求的uri地址 String requestURI = request.getRequestURI(); // 判断用户和操作 if (COMMON_USER.equals(loginUser) && ( requestURI.startsWith("/article/add") || requestURI.startsWith("/article/edit") || requestURI.startsWith("/article/remove") )) { return false; } return true; } }
-
注册拦截器
/** * SpringMvc配置:使用的是javaConfig的方式配置,代替xml配置文件 */ @Configuration public class MvcSettings implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { AuthInterceptor authInterceptor = new AuthInterceptor(); registry.addInterceptor(authInterceptor) // 拦截以article开头的所有的请求 .addPathPatterns("/article/**") // 不想拦截的地址 .excludePathPatterns("/article/query"); } }
-
配置多个拦截器
-
两个拦截器登录的拦截先执行,权限拦截器后执行,order()方法设置顺序,整数值越小就越先执行。
/** * SpringMvc配置:使用的是javaConfig的方式配置,代替xml配置文件 */ @Configuration public class MvcSettings implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { AuthInterceptor authInterceptor = new AuthInterceptor(); registry.addInterceptor(authInterceptor) .order(2) // 拦截以article开头的所有的请求 .addPathPatterns("/article/**") // 不想拦截的地址 .excludePathPatterns("/article/query"); // 登录拦截器 LoginInterceptor login = new LoginInterceptor(); registry.addInterceptor(login) // 数值越小越先执行 .order(1) // 拦截所有对controller请求 .addPathPatterns("/**") // 排序需要拦截的请求 .excludePathPatterns("/article/query") } }
文件上传
-
StandardServletMultipartResolver内部封装了读取POST其中体的请求数据,也就是问价内容,我们只需要在Controller的方法上加入加入@RequestParam MultipartFile。MutipartFile表示上传的文件,提供了方便的方法保存文件到磁盘。
-
MutiopartFile API
- getName() : 参数的名称
- getOriginalFileName() : 上传文件的原始名称。
- isEmpty() :上传文件是否为空
- getSize() : 上传文件的字节大小。
- getInputStream:文件的InputStream,可用于读取文件的内容
- transferTo(File dest) : 保存上传文件到目标dest
-
上传文件的表单
- 使用重定向是为了防止文件的重复上传
<form action="uploadFile" enctype="multipart/form-data" method="post"> 选择文件:<input type="file" name="upFlie"> <br> <br> <input type="submit" value="上传"></input> </form> // 上传文件的要求 1,method方法为post 2,enctype一定是multipart/form-data 3,并且输入框的type一定为file
-
controller类
@RestController public class UploadFileController { // 上传文件 @PostMapping("/uploadFile") public String uploadFile(@RequestParam("upFlie") MultipartFile multipartFile) { System.out.println("开始处理上传文件"); Map<String,Object> map = new HashMap<>(); try { // 判断是否上传文件 if ( !multipartFile.isEmpty()) { map.put("上传文件的参数名称",multipartFile.getName()); map.put("文件内容类型",multipartFile.getContentType()); var ext = "unknown"; // 获取原始的文件名称 var fileName = multipartFile.getOriginalFilename(); if (fileName.indexOf(".") > 0) { ext = fileName.substring(fileName.indexOf(".") + 1); } // 生成服务器使用的文件名的名称 var newFileName = UUID.randomUUID().toString() + "." + ext; var path = "D://upload//" + newFileName; // 存储服务器的文件 // 把文件保存到path目录 multipartFile.transferTo(new File(path)); map.put("文件名称",newFileName); } } catch (Exception e) { e.printStackTrace(); } // 重定向到index页面 return "redirect:/index.html"; } }
-
springboot中默认单个文件最大支持1M,一次请求最大10M。改变默认值,需要修改application配置项。
# 设置单个文件的大小 spring.servlet.multipart.max-file-size=800B # 一次请求最大 spring.servlet.multipart.max-request-size=5MB # 当文件超过了指定大小,直接写文件到磁盘, 不在内存中处理 0KB性能会好一些 spring.servlet.multipart.file-size-threshold=0KB # 配置上传文件的输出目录 spring.servlet.multipart.location=D://upload
-
在处理文件请求的时候,既要注意到所使用服务器的请求大小,又要注意springboot中的请求的大小。
servlet规范
- servlet3.0中定义了Part接口处理Mulitipart/form-data POST请求中接收到表单数据。有了part对象,其write()方法将上传文件保存到服务器本地磁盘目录。
- 在HttpServletRequest接口中引入的新方法:
- getParts():返回part对象的集合
- getPart(字符串名称):检索具有给定的名称的单个Part对象。
@Controller
public class UploadAction {
@PostMapping("/files")
public String upload(HttpServletRequest request) {
try {
for (Part part : request.getParts()) {
String fileName = extractFileName(part);
// 将文件写入到服务器的目录
part.write(fileName);
}
} catch (Exception e) {
e.printStackTrace();
}
return "redirect:/index.html";
}
private String extractFileName(Part part) {
String contentDisp = part.getHeader("content-disposition");
String[] items = contentDisp.split(".");
for (String s:items) {
if (s.trim().startsWith("filename")) {
return s.substring(s.indexOf("=") + 2,s.length() - 1);
}
}
return "";
}
}
多文件上传
-
多文件上传,在接收文件参数部分时有所变换MultiPartFile [] files.循环遍历数组解析每个上传的文件。
-
前端请求页面中选择文件的name都是一致的。在处理逻辑上,出现上传错误的文件就让用户重新上传。
@Controller public class UploadFileController { // 上传文件 @PostMapping("/uploadFile") public String uploadFile(@RequestParam("upFlie") MultipartFile[] multipartFiles) { System.out.println("开始处理上传文件"); Map<String,Object> map = new HashMap<>(); try { for (MultipartFile multipartFile :multipartFiles) { // 判断是否上传文件 if ( !multipartFile.isEmpty()) { map.put("上传文件的参数名称",multipartFile.getName()); map.put("文件内容类型",multipartFile.getContentType()); var ext = "unknown"; // 获取原始的文件名称 var fileName = multipartFile.getOriginalFilename(); if (fileName.indexOf(".") > 0) { ext = fileName.substring(fileName.indexOf(".") + 1); } // 生成服务器使用的文件名的名称 var newFileName = UUID.randomUUID().toString() + ext; var path = "D://upload//" + newFileName; // 存储服务器的文件 // 把文件保存到path目录 multipartFile.transferTo(new File(path)); map.put("文件名称",newFileName); } } } catch (Exception e) { e.printStackTrace(); } // 重定向到index页面 return "redirect:/index.html"; } }
异常处理
-
在controller处理请求的过程中发送了异常,DispatcherServlet将异常处理委托给异常处理器(处理异常的类)。实现HandlerExceptionResolver接口都是处理异常的类。
-
项目中的异常一般集中处理,定义全局异常处理器。在结合框架提供的注解,诸如:@ExceptionHandler,@ControllerAdvice,
@RestControllerAdvice一起完成异常处理。
全局异常处理
- 建议:建议全局处理异常,把项目中所有产生的异常集中到一个类中进行处理。
/**
* 1. 在类上加入 @ControllerAdvice 或者
* @RestControllerAdvice:返回值都是数据
* 2, 在类中定义方法,处理各种异常。
*
* 方法的定义和Controller类中的方法一致
*/
// 控制器增强,给Controller增强异常处理功能。类似AOP的思想
@ControllerAdvice
public class GlobalExceptionHandler {
// 定义方法,处理数学异常
// @ExceptionHandler 表示该方法是用来处理异常的
// ArithmeticException 控制器中抛出的异常传递给它
@ExceptionHandler
public String handlerArtithemeticException(ArithmeticException arithmeticException, Model model) {
String error = arithmeticException.getMessage();
model.addAttribute("error",error);
return "exp"; // 视图
}
}
-
@ExceptionHandler表示该方法是用来处理异常的 位置在方法的上面,属性是:异常类的class数组,
如果系统抛出的异常类型与@ExceptionHandler声明的相同,由当前的方法来处理异常 -
处理异常的方法最好是具体的异常类型。以减少异常类型和原因异常之间不匹配的问题。考虑创建多个@ExceptionHandler方法的,每个方法通过其签名匹配单个特定的异常类型。最后增加一个根异常,考虑没有匹配的其他情况。
-
@ControllerAdvice public class GlobalExceptionHandler { // 定义方法,处理数学异常 // @ExceptionHandler 表示该方法是用来处理异常的 位置在方法的上面,属性是:异常类的class数组, // 如果系统抛出的异常类型与 @ExceptionHandler声明的相同,由当前的方法来处理异常 // // ArithmeticException 控制器中抛出的异常传递给它 @ExceptionHandler({ArithmeticException.class}) public String handlerArtithemeticException(ArithmeticException arithmeticException, Model model) { String error = arithmeticException.getMessage(); model.addAttribute("error",error); return "exp"; // 视图 } @ExceptionHandler(Exception.class) public String handlerDefaultException(ArithmeticException exception,Model model) { String error = exception.getMessage(); model.addAttribute("error",error); return "exp"; // 视图 } }
-
异常处理返回的数据
@ExceptionHandler({ArithmeticException.class}) @ResponseBody public Map<String,String> handlerArtithemeticException(ArithmeticException arithmeticException) { String error = arithmeticException.getMessage(); Map<String,String> map = new HashMap<>(); map.put("msg",error); map.put("tips","被除数不能为0"); return map; }
BeanValidator异常处理
- 添加JSR-303依赖
<!-- 处理异常的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 处理异常的java实现
// 处理jsr303验证参数的异常
@ExceptionHandler({BindException.class})
public Map<String,Object> handlerJSR303Excrption(BindException e) {
Map<String,Object> map = new HashMap<>();
BindingResult result = e.getBindingResult();
if (result.hasErrors()) {
List<FieldError> errors = new ArrayList<>();
for (int i = 0; i < errors.size(); i++) {
FieldError fieldError = errors.get(i);
map.put(i+"-"+fieldError.getField(),fieldError.getDefaultMessage());
}
}
return map;
}
}
ProBlemDetail
- 定义了http应答错误的处理细节,增强了响应错误的内容。包含标准和非标准的字段,同时支持json和xml两种格式。
- springboot默认返回的错误 信息不够细致。
- ProBlemDetail中的标准字段:
- type:标识错误类型的URI。在浏览器中加载这个URI应该转向这个错误的文档。
- title:问题类型的简短,易读的摘要。
- detail:错误信息的详细描述,对title的进一步阐述。
- instance:标识该特定故障实例的URi,它可以作为发生这个错误的ID
- status:错误使用的HTTP状态代码,它必须与实际状态匹配。
problemDetail
@ExceptionHandler({自定义的异常类})
public ProblemDetail handlerBookNotFoundException(自定义的异常类 变量名) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND,变量名.getMessage());
// type:异常类型,是一个uri,uri找到解决问题的途径
problemDetail.setType(URI.create("/api/problems/not-found"));
problemDetail.setTitle("图书异常");
// 自定义字段
problemDetail.setProperty("时间",Instant.now());
problemDetail.setProperty("客服邮箱","service@lirunxin.com");
return problemDetail;
}
ErrorResponse
-
springboot识别ErrorResponse类型作为异常的应答结果,可以直接使用ErrorResponse作为异常处理方法的返回值。ErrorResponseException是ErrorResponse的基本实现类。
// 返回值为ErrorResponse @ExceptionHandler({BookNotFoundException.class}) public ErrorResponse handlerException(BookNotFoundException e) { ErrorResponse response = new ErrorResponseException(HttpStatus.NOT_FOUND,e); return response; }
-
自定义异常
// 自定义的异常类。让springMVC的异常处理器使用 public class IsbnNotFoundException extends ErrorResponseException { public IsbnNotFoundException(HttpStatus status,String detail) { super(status,createProblemDetail(status,detail),null); } // 创建ProBlemDetail对象的方法 private static ProblemDetail createProblemDetail(HttpStatus status,String detail) { // 封装ProblemDetail字段 ProblemDetail problemDetail = ProblemDetail.forStatus(status); problemDetail.setType(URI.create("/api/problems/not-found")); problemDetail.setTitle("图书异常"); problemDetail.setDetail(detail); // 自定义字段 problemDetail.setProperty("严重程度","低"); problemDetail.setProperty("客服邮箱","service@qq.com"); return problemDetail; } }
-
在核心配置文件中配置
# 开启支持7807
spring.mvc.problemdetails.enabled=true
远程访问@HttpExchange
-
远程访问是开发的常用技术,一个应用能够访问其他应用的功能。springboot3提供了新的http的访问能力,通过接口简化Http远程访问,类似于Feign功能。Spring包装了底层HTTP客户的访问细节。
-
springboot中定义接口提供HTTP服务。生成的代理对象实现此接口,代理对象实现HTTP的远程访问。需要理解:
- @HttpExchange
- WebClient : 用来做远程的http服务的
-
WebClient特性:
- 非阻塞,异步请求
- 它的响应式编程的基于Reactor
- 高并发,硬件资源少
- 支持java 8 函数式编程。
访问
-
创建接口
public interface TodoService { // 一个方法就是一个远程调用 /** * @PathVariable 表示将id传入注解中的id * @param id * @return */ @GetExchange("/todos/{id}") Todo getTodoById(@PathVariable("id") Integer id); // }
-
创建代理对象
@Configuration(proxyBeanMethods = false) public class HttpConfiguration { // 创建服务器接口的代理对象,基于WebClient @Bean public TodoService requestService() { // 创建WebClient对象 WebClient webClient = // 远程访问的基地址 WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com").build(); // 创建代理工厂 HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build(); // 通过工厂创建某个接口的代理服务 return httpServiceProxyFactory.createClient(TodoService.class); } }
Http服务接口的方法定义
- @HttpExchange注解用于声明接口作为HTTP远程服务。在方法,类级别识别。通过注解属性以及方法的参数设置HTTP请求的细节。
- 快捷注解简化不同的请求方式
- @GetExchange
- @PostExchange
- @PutExchange
- @PatchExchange
- @DeleteExchange
- 作为HTTP服务接口中的方法允许使用的参数列表
- URI :设置请求的uri,覆盖注解的uri属性。
- HttpMethod:请求方式,覆盖注解的method属性
- @RequestHeader: 添加请求中header.
- @PathVariable:uri中的占位符,参数可为单个值或者Map<String,?>
- @RequestParam : 请求参数,单个值或者Map<String,?>.
- @RequestPart:发送文件时使用
- @CookieValue:向请求中添加cookie
- javaRecord可以作为返回值类型,由框架的Http代理转换内容为Record对象。
定制HTTP请求服务
-
设置HTTP远处的超时时间,异常处理。
-
在创建接口代理对象前,先设置WebClient的有关配置。
// 定制HTTP服务 @Bean public TodoService requestService() { // 超时设置 HttpClient httpClient = HttpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,30000) // 连接时间 .doOnConnected(connection -> { connection.addHandlerLast(new ReadTimeoutHandler(10)); // 读超时 connection.addHandlerLast(new WriteTimeoutHandler(10)); // 写超时 }); // 设置异常的提示 WebClient webClient = WebClient.builder() // 相当于让上面的连接和异常提示连接起来了 .clientConnector(new ReactorClientHttpConnector(httpClient)) .defaultStatusHandler(HttpStatusCode::isError,clientResponse -> { System.out.println("*****webclient请求异常******"); return Mono.error(new RuntimeException("请求异常" + clientResponse.statusCode().value())); }).build(); // 创建代理工厂 HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build(); // 通过工厂创建某个接口的代理服务 return httpServiceProxyFactory.createClient(TodoService.class); }
thymeleaf技术
-
表达式
- ${…} 变量表达式,可以获取后台传过来的值
- @{…} 链接网址表达式
-
链接表达式传递参数:
<a th:href="@{/link(参数名=值,参数名=值)}"
-
if语句
th:if="boolean表达式" :当条件满足的时候,显示代码片段
-
for语句
<tr th:each="成员变量:${表达式}" <td th:text="${成员}">列</td> </tr> 例子: <table> <tr th:each="item:${list}"> <td th:text="${item}"></td> </tr> </table>