一、JDK新特性
1.有用的新特性
JDK8-19新增了不少新特性:
- Java Record
- Swich 开关表达式
- Text Block 文本块
- var 声明局部变量
- sealed 密封类
2.Java Record
java14中预览的新特性叫做Record,是一种特殊类型的java类。Record相当于一个内置的,语言级别的Lombok,可用来创建不可变类,语法简短。
当创建java类,都会创建大量的样板代码如set,get,构造方法,重新hashcode,tostring,equals方法。
java Record避免上述样板代码,如下特点:
- 带有全部参数的构造方法
- public访问器
- toString(),hashCode(),equals()
- 无get,set方法。没有遵循Bean的命名规范
- final类,不能继承Record,Record为隐士的final类。除此之外与普通类一样
- 不可变类,通过构造创建Record类
- final属性,不可修改
- 不能声明实例属性,能声明static成员
1.创建Record类
public record Student(Integer id,String name,String email,Integer age) {
}
//测试类
public class StudentTest {
@Test
public void test01() {
//创建Record对象
Student zhixun = new Student(1001,"zhixun","zhixun@qq.com",22);
//这里的zhixun 其实调用了zhixun.toString()方法
System.out.println("zhixun=" + zhixun);
//通过public访问器,获取属性,只读。没用get,set方法
Integer id = zhixun.id();
String name = zhixun.name();
String email = zhixun.email();
//值不可变 再应用上更安全
System.out.println("id = " + id);
System.out.println("name = " + name);
System.out.println("email = " + email);
Student lisi = new Student(1002,"lisi","lisi@qq.com",22);
System.out.println(lisi.equals(zhixun));
}
}
2.Record类定义方法
Record与普通java类一样的方式定义方法。
public record Student(Integer id,String name,String email,Integer age) {
//创建方法
public String concat(){
return String.format("姓名是%s,年龄是%d",this.name,this.age);
}
//静态方法
public static String emailToUpperCase(String email){
return Optional.ofNullable(email).orElse("no email").toUpperCase();
}
}
@Test
public void test02(){
//创建Record对象
Student zhixun = new Student(1001,"zhixun","zhixun@qq.com",22);
//使用对象,调用方法
String str = zhixun.concat();
System.out.println("str=" + str);
}
@Test
public void test03(){
//使用类,静态方法
String email = Student.emailToUpperCase("zhixun@qq.com");
System.out.println("email=" + email);
}
3.Record构造方法
Record分为三种类型,分别为:紧凑的,规范的和定制的构造方法
- 紧凑型构造方法没有任何参数,甚至没用括号。
- 规范构造方法是以所有成员作为参数。
- 定制构造方法是自定义参数个数。
public record Student(Integer id, String name, String email, Integer age) {
//紧凑型
public Student {
if (id < 1) {
throw new RuntimeException("id < 1");
}
System.out.println(id);
}
//定制构造
public Student(Integer id,String name) {
this(id,name,null,null);
}
}
@Test
public void test04(){
//使用类,静态方法
Student zhixun = new Student(1001,"zhixun");
//会先执行紧凑
System.out.println("zhixun=" + zhixun);
}
输出结果:1001
zhixun=Student[id=1001, name=zhixun, email=null, age=null]
4.Record类实现接口
Record与普通java类一样的方式实现接口,重写接口。
//商品接口
public interface PrintInterface {
//输出自定义信息
void print();
}
//record方法实现商品接口
public record ProductRecord(Integer id, String name, Integer qty) implements PrintInterface {
//重写接口方法
@Override
public void print() {
StringJoiner joiner = new StringJoiner("-");
String s = joiner.add(id.toString()).add(name).add(qty.toString()).toString();
System.out.println("商品信息 = " + s);
}
}
//测试类
public class ProductTest {
@Test
public void test01(){
//创建Record对象
ProductRecord productRecord = new ProductRecord(1001,"手机",22);
//测试print方法
productRecord.print();
}
}
输出结果:商品信息 = 1001-手机-22
5.Record可做局部对象使用
@Test
public void test03(){
//定义局部的Local Record 别忘了{}
record SaleRecord(Integer saleId,String productName,Double money){};
//创建对象
SaleRecord saleRecord = new SaleRecord(001,"显示器",3000.00);
System.out.println("saleRecord=" + saleRecord);
}
输出结果:saleRecord=SaleRecord[saleId=1, productName=显示器, money=3000.0]
6.嵌套Record
多个Record可以组合定义,一个Record能够包含其他的Record。
public record Address(String city,String address,String zipCode) {
}
public record PhoneNumber(String areaCode,String number) {
}
public record Customer(String id,String name,PhoneNumber phoneNumber,Address address) {
}
@Test
public void test04(){
Address address = new Address("厦门","集美区","36001");
PhoneNumber phoneNumber = new PhoneNumber("121","10086");
Customer customer = new Customer("1001","zhixun",phoneNumber,address);
System.out.println(customer);
}
输出结果:Customer[id=1001, name=zhixun, phoneNumber=PhoneNumber[areaCode=121, number=10086], address=Address[city=厦门, address=集美区, zipCode=36001]]
10086
7.instanceof判断Record类型
instanceof能够和java Record一起使用。编译器能够知道Record类型还是普通java类型。
public record Person(String name,Integer age) {
}
public class SomeService {
//定义业务方法
public boolean isEligible(Object obj){
if(obj instanceof Person(String name,Integer age)){
return age >= 18;
}
return false;
}
}
@Test
public void test05(){
Person person = new Person("zhixun",22);
SomeService someService = new SomeService();
System.out.println(someService.isEligible(person));
}
输出结果:true
总结
3.switch
1.箭头表达式
switch新语法 case label -> 表达式|throw 语句|block
@Test
public void test01(){
int week = 7;
String memo = "";
switch (week){
case 1 -> memo = "星期日 休息";
case 2,3,4,5,6 -> memo = "工作日";
case 7 -> memo = "休息日 休息";
default -> throw new RuntimeException("无效的日期");
}
System.out.println("week: " + memo);
}
输出结果:week: 休息日 休息
2.支持yied返回值
yied让switch作为表达式,能够返回值
@Test
public void test02(){
int week = 7;
//注意写法 与case匹配,匹配成功后,将返回值赋给变量 并结束switch
String memo = switch (week){
case 1: yield "星期日 休息";
case 2,3,4,5,6 : yield "工作日";
case 7 : yield"休息日 休息";
default : yield ("无效的日期");
};
System.out.println("week: " + memo);
}
输出结果:week: 休息日 休息
代码块形式:
使内容更丰富
@Test
public void test03(){
int week = 7;
String memo = switch (week){
case 1 -> {
yield "星期日 休息";
}
case 2,3,4,5,6 ->{
yield "工作日";
}
case 7 -> {
yield "休息日 休息";
}
default -> {
yield ("无效的日期");
}
};
System.out.println("week: " + memo);
}
输出结果:week: 休息日 休息
3.支持java Record
switch表达式中使用Record,结合case标签 -> 表达式,yield实现复杂的计算
//先准备三个record
public record Line(int x,int y) {
}
public record Rectangle(int width,int height) {
}
public record Shape(int width,int height) {
}
@Test
public void test04(){
//创建对象
Line line = new Line(1,1);
Rectangle rectangle = new Rectangle(10,20);
Shape shape = new Shape(20,30);
Object obj = line;
int result = switch (obj){
case Line(int x,int y) ->{
System.out.println("图形为Line x:" + x + " y:" + y);
yield x + y;
}
case Rectangle(int w,int h) -> 2 * (w+h);
case Shape(int x,int y) -> {
System.out.println("图形为shape x:" + x + " y:" + y);
yield x * y;
}
default -> throw new IllegalStateException("Unexpected value: " + obj);
};
}
输出结果:图形为Line x:1 y:1
4.var的使用
var的特点
- var是一个保留字,不是关键字(可以声明var变量)
- 方法内声明的局部变量,必须有初值
- 每声明一次变量,不可复合声明多个变量。 var s1 = a,b //Error
- var动态类型是编译器根据变量所赋的值来判断类型
- var代替显示类型,代码简洁,减少不必要的排版,混乱。
缺点:
- 减低了代码的可读性
@Test
public void test05(){
//创建对象
var a = new Line(1,1);
System.out.println(a);
}
输出结果:Line[x=1, y=1]
5.sealed
1.sealed密闭类
sealed的特点:
- 限制继承,java中通过继承增强,扩展了类的能力,复用某些功能。当这种能力不受控制与原有类的设计相违背,导致不预见的异常逻辑。
sealed作为关键字可在class和interface上使用,结合permits关键字。定义限制继承的密封类
permits:表示允许的子类,一个或者多个
子类的声明有三种”
- final 终结,依然是密封的
- sealed 子类是密封的,需要子类实现
- non-sealed 非密封类,扩展使用,不受限制
//创建密闭类 permit允许三个子类Circle,Square, Rectangle
public sealed class Shape permits Circle,Square, Rectangle {
private Integer width;
private Integer height;
public void draw(){
System.out.println("画一个图形...");
}
}
//final 到此类就不会再有子类了
public final class Circle extends Shape {
}
//sealed 还是密闭类 他得有允许的子类RoundSquare
public sealed class Square extends Shape permits RoundSquare {
public void draw(){
System.out.println("画一个Square图形...");
}
}
//non-sealed非密闭类 可以被扩展,不受密闭限制
public non-sealed class Rectangle extends Shape {
}
public final class RoundSquare extends Square {
}
2.sealed interface密闭接口
用法和密闭类一样
//创建密闭接口
public sealed interface SomeService permits SomeServiceImpl {
void doThing();
}
//创建接口实现类
public final class SomeServiceImpl implements SomeService {
@Override
public void doThing() {
System.out.println("doThing...");
}
}
@Test
public void test06(){
SomeService someService = new SomeServiceImpl();
someService.doThing();
}
输出结果:doThing...
二、Spring Boot基础篇
1 SpringBoot是什么?
1.SpringBoot与SpringCloud关系
2.SpringBoot3的新特性
2.代码结构
1.单一模块
2.包和主类
3.spring-boot-starter-parent
4.pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- parent:表示父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- 当前项目坐标 -->
<groupId>com.example</groupId>
<artifactId>day_02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>day_02</name>
<description>day_02</description>
<!-- jdk版本 -->
<properties>
<java.version>17</java.version>
</properties>
<!-- 依赖列表 -->
<dependencies>
<!-- SpringWeb依赖
带有starter单词被叫做启动器(启动依赖)
spring-boot-starter开头的 是spring官方推出的启动器
xxx-starter 非spring推出的 有其他组织提供的
starter:是一组依赖描述,应用包含starter,可以获取spring相关技术的一站式依赖和版本。
通过starter能够跟快速的启动运行项目。
* 依赖坐标,版本
* 传递依赖坐标,版本
* 配置类,配置项
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- 打包名称 myWeb.jar -->
<finalName>myWeb</finalName>
<plugins>
<plugin>
<!-- springboot项目插件 负责打包,编译等 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.core注解
/*
* @SpringBootApplication 源码!
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
....
}
/*
核心注解
@SpringBootConfiguration :包含@Configration注解的功能,所一有这个注解标注的类是配置类
@Configration:javaConfig的功能,主要用于配置类,结合@Bean能够将对象注入到SpringIoc的容器。
@EnableAutoConfiguration:包含@AutoConfigurationPackage
@AutoConfigurationPackage:开启自动配置,将spring和第三方库中的对象创建好并注入到容器中。避免写xml去掉样例代码,需要使用的对象由框架提供。
@ComponentScan:组件扫描器:<context:component-scan base-package="xxx"/>
主要用来扫描 @Controller,@Service,@Repository,@Component注解,创建他们的对象注入到容器
springboot约定:启动类作为扫描包的根(起点),@ComponentScan:扫描当前Day02Application类存在的包与此包的子包中所有的类。
*/
@SpringBootApplication
public class Day02Application{
@Bean
public void myDate(){
return new Date();
}
public static void main(String[] args) {
//run方法第一个参数 源(配置类),从这里加载bean,找到bean注入到容器
//run源码 可以执行多个配置类
//return run(new Class<?>[] { primarySource }, args);
//也就是说 首先通过run找到配置类Day02Application,找到配置类就能找到@SpringBootApplication注解从而实现以上注解的功能
SpringApplication.run(Day02Application.class, args);
//run方法的返回值是容器对象
/*源码
*
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
**ConfigurableApplicationContext继承了ApplicationContext**
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
....
}
也就是说 可以用ApplicationContext来接收run方法的返回值
*/
ApllicationContext ctx = SpringApplication.run(Day02Application.class, args);
//然后可以通过容器获取对象
Date date = ctx.getBean(Date.class);
}
}
4.如何打包
1.配置pom文件的build属性
通过pom.xml的build属性
<build>
<!-- 打包名称 myWeb.jar -->
<finalName>myWeb</finalName>
<plugins>
<plugin>
<!-- springboot项目插件 负责打包,编译等 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2.点击maven工具
先点击clean 然后点击package进行打包。
3.运行打包文件
打包成功后会生成jar包,再该路径下运行cmd,输入java -jar day_02-0.0.1-SNAPSHOT.jar
5.springboot 的jar有哪些区别
5.外部化配置
应用程序 = 代码 + 数据(数据库,文件,url)
应用程序的配置文件:Spring Boot允许在代码块外,提供应用程序运行的数据,以便在不同环境中使用相同的应用程序代码。避免硬编码,提供系统的灵活性。可以使用各种外部配置源,包括java属性文件,yaml文件,环境变量和命令行参数。
在项目中经常使用yaml和properties文件,其次是命令行参数。
1.配置文件格式
2.application
1.获取properties文件属性
首先现在properties文件中创建值
#默认的配置文件
app.name = day_02
app.owner = zhixun
app.port = 8001
创建SomeService类 通过@Value("${key:默认值}")将属性注入
@Service
public class SomeService {
//使用@Value("${key:默认值}")
/*
* @Value只能获取当个值
* 如果默认值不写的话,找不到对应的key将会报错。写了则会将默认值赋值给属性。
*/
@Value("${app.name}")
private String name;
@Value("${app.owner}")
private String owner;
@Value("${app.port}")
private String port;
public void printValue(){
System.out.println(name + "," + owner + "," + port);
}
在测试方法中,创建SomeService变量并通过@Autowire进行自动注入。
@Autowired
private SomeService someService;
@Test
void test01() {
someService.printValue();
}
输出结果: day_02,zhixun,8001
2.获取yml文件属性
app:
#name:空格value
name: day
owner: zhixun
port: 9001
运行:采用properties的测试模块
输出结果: day_02,zhixun,8001
2.Environment
Environment是外部化的抽象,是多种数据来源的集合。从中可以读取application配置文件,环境变量,系统属性。使用方式在Bean中注入Environment。调用它的getProperty(key)方法。
创建ReadConfig类,并注入Environment对象
@Service
public class ReadConfig {
//注入环境对象
@Autowired
private Environment environment;
public void print(){
//获取某个key的值
String name = environment.getProperty("app.name");
//判断key是否存在
if (environment.containsProperty("app.owner")){
System.out.println("app.owner");
}
//读取key的值,转为期望的类型,同时提供默认值。
Integer port = environment.getProperty("app.port", Integer.class, 9001);
String format = String.format("读取的key值,name=%s, port=%s", name, port);
System.out.println(format);
}
}
//测试方式
@Autowired
private ReadConfig config;
@Test
void test02() {
config.print();
}
输出结果:app.owner
读取的key值,name=day, port=9001
3.组织多文件
大型集成的第三方框架,中间件较多,配置细节复杂。如果配置在同一个application文件不易阅读。需每个框架独立一个配置文件,最后通过导入功能,将多个配置文件集中于application。
首先先创建两个yml文件
#db.yml
spring:
dataSource:
url: jdbc:mysql://localhost:3306/db
username: admin
password: 123456
#redis.yml
spring:
redis:
host: 192.168.1.1
port: 6379
password: 123456
//MultiConfigService.class
@Service
public class MultiConfigService {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.dataSource.url}")
private String jdbcUrl;
public void print(){
System.out.println("redisHost = " + redisHost + "," + "jdbcUrl = " + jdbcUrl);
}
}
//测试方法
@Test
void test03() {
multiConfigService.print();
}
输出结果:redisHost = 192.168.1.1,jdbcUrl = jdbc:mysql://localhost:3306/db
4.多环境配置
创建两个yml文件,一个开发环境,一个测试环境
#application-dev.yml
myapp:
memo: 这是开发环境
#指定环境名称
spring:
config:
activate:
on-profile: dev
#application-test.yml
myapp:
memo: 这是测试的配置文件
#指定环境名称
spring:
config:
activate:
on-profile: test
并在application中激活环境
#编写配置 key:value
app:
name: day
owner: zhixun
port: 9001
#导入其他的配置文件,多个文件可使用逗号当分割符。
spring:
config:
import: config/db.yml,config/redis.yml
#激活某个配置文件(环境)
profiles:
active: dev
EnvService.class
@Service
public class EnvService {
@Value("${myapp.memo}")
private String memo;
public void print(){
System.out.println(memo);
}
}
//测试方法
@Test
void test04() {
envService.print();
}
输出结果:这是开发环境
5.总结:
文件格式: properties(优先),yml(yaml)
内容: key = value
yml: key: value
文件名称: 默认application
环境对象: Environment: 表示抽象的所有key和value。方法getProperty(key)
多文件: 自定义独立的配置文件,使用spring.config.import = 文件路径。
导入多个文件。
多环境: 开发环境,测试环境,上线,特性,bug等
名称: application-profile.yml(properties),可以用多个环境文件
创建环境文件: 必须得通过spring.config.activate.on-profile指定文件名
spring:
config:
activate:
on-profile: test
激活环境: spring.profiles.active:环境名称
#激活某个配置文件(环境)
profiles:
active: dev
读取数据: @Value("${key:默认值}"),使用Environment.getProperty(key) 获取的是单个值。
3.绑定Bean:
1.简单的属性Bean绑定
创建AppBean获取 application以app开头的属性
/*
*
* @ConfigurationProperties: 表示使用Bean对象读取配置项
* prefix:表示配置文件中多个key的公共开始部分。
*
* */
//需要加入Component/Configration 因为ConfigurationProperties不会将类注入到容器中
@Component
//匹配相同的前缀
@ConfigurationProperties(prefix = "app")
public class AppBean {
//key的名称于属性名相同,调用属性setXXXX方法给属性赋值
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 +
'}';
}
}
@Autowired
private AppBean appBean;
@Test
void test05() {
System.out.println(appBean.toString());
System.out.println(appBean.getClass());
}
输出结果:AppBean{name='day', owner='zhixun', port=9001}
如果用@Configration 通过反射获取的类信息是经过容器处理过的代理对象,并不是对象本身。
输出结果:class com.example.day_02.pk5.AppBean$$SpringCGLIB$$0
如果需要变为普通bean 不使用代理需要修改为 @Configuration(proxyBeanMethods = false)
2.嵌套Bean
Bean中包含其他Bean作为属性,将配置文件中的配置项绑定到Bean以及引用类型的成员。
@Component
@Data
public class Security {
private String username;
private String password;
}
@Data
@Configuration(proxyBeanMethods = false)
@ConfigurationProperties(prefix = "app")
public class NestAppBean {
private String name;
private String owner;
private Integer port;
private Security security;
}
app:
name: day
owner: zhixun
port: 9001
#新属性
security:
username: root
password: 123456
@Test
void test06() {
System.out.println(nestAppBean.toString());
}
输出结果:NestAppBean(name=day, owner=zhixun, port=9001, security=Security(username=root, password=123456))
3.扫描注解
@ConfigurationProperties注解起作用,还需要@EnableConfigrationProperties或者@ConfigrationPropertiesScan。这个注解是专门找@ConfigurationProperties注解,将其注入到spring容器中。在启动类上使用扫描注解。
//@ConfigurationPropertiesScan扫描到@ConfigurationProperties会将其注入到spring容器中
@ConfigurationPropertiesScan("com.example.day_02.pk6")
@SpringBootApplication
public class Day02Application {
public static void main(String[] args) {
SpringApplication.run(Day02Application.class, args);
}
}
4.处理第三方库对象
上面的例子都是在源代码中使用@ConfigurationProperties注解,如果是第三方库的对象是没有源代码的。此时需要@ConfigurationProperties于@Bean一起在方法上面使用。
@Data
public class Security {
private String username;
private String password;
}
@Configuration(proxyBeanMethods = false)
public class ApplicationConfig {
//创建bean对象,属性值来自配置文件
@ConfigurationProperties(prefix = "security")
@Bean
public Security createSecurity(){
return new Security();
}
}
#第三方库对象,没有源代码
security:
username: root
password: 123456
@Autowired
private Security security;
@Test
void test07() {
System.out.println(security);
}
输出结果:Security(username=root, password=123456)
5.集合Map,List以及Array
创建一个保存数据的Bean
@Data
public class MyServer {
private String title;
private String ip;
}
@Data
public class User {
private String name;
private String sex;
private Integer age;
}
@Configuration
@ConfigurationProperties
@Data
public class CollectionConfig {
private List<MyServer> servers;
private Map<String,User> users;
private String[] names;
}
配置yml 添加其对应数据
#配置集合
#数组 和 List一样的,使用“-” 一个成员
names:
- lisi
- zhixun
#List<MyServer> server
servers:
- title: 华北
ip: 202.1.25.33
- title: 西南
ip: 226.6.5.32
#Map<String,User> users
users:
user1:
name: zhixun
sex: nan
age: 22
user2:
name: lisi
sex: nan
age: 55
测试方法
@Autowired
private CollectionConfig collectionConfig;
@Test
void test08() {
System.out.println(collectionConfig);
}
输出结果:CollectionConfig(servers=[MyServer(title=华北, ip=202.1.25.33), MyServer(title=西南, ip=226.6.5.32)], users={user1=User(name=zhixun, sex=nan, age=22), user2=User(name=lisi, sex=nan, age=55)}, names=[lisi, zhixun])
6.指定数据源文件
application做配置是经常使用的,我们可以指定某个文件作为数据来源。@PropertySource是主力,用于加载指定的properties文件。也可以是xml文件。@PropertySource与@Configuration一同使用,其他注解还有@Value,@ConfigrationProperties。
创建properties文件
#默认的配置文件
group.name = ZHIXUN
group.leader = zz
group.members = 20
@Configuration
@ConfigurationProperties(prefix = "group")
@PropertySource(value = "classpath:/group.properties")
@Data
public class Group {
private String name;
private String leader;
private Integer members;
}
@Test
void test09() {
System.out.println(group);
}
输出结果:Group(name=ZHIXUN, leader=zz, members=20)
7.总结
绑定Bean: 用于多个属性
注解:@ConfigurationProperties
位置:1) 在类的上面,需要有源代码
2) 方法的上面,使用第三方对象。配合@Bean注解
数据来源 application文件
指定数据的来源:@PropertySource(value = "classpath:/group.properties")
注意:1) 类中有无参构造方法
2) 属性有setxxx方法
3) static属性是无效的
@ConfigurationProperties使用需要配合其他注解
1) @Configration
2) @EnableConfigrationProperties
3) @ConfigurationPropertiesScan
配置文件application 名称和位置都是可以改变的
application配置的文件位置:
1) 项目的根目录下
2) 项目根目录下的/config目录
3) resource/config
4) resource目录下
4.创建对象的三种方式
1.传统的xml配置文件
@Data
public class Person {
private String name;
private Integer age;
}
#applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--声明对象-->
<bean id="myPerson" class="com.example.day_02.pk10.Person">
<property name="name" value="zhixun"/>
<property name="age" value="22"/>
</bean>
</beans>
//要导入传统的xml文件,需要在配置类中使用@ImportResource
//在配置类加入注解@ImportResource
@ImportResource(locations = {"classpath:/applicationContext.xml"})
//@ConfigurationPropertiesScan扫描到@ConfigurationProperties会将其注入到spring容器中
@ConfigurationPropertiesScan("com.example.day_02.pk6")
@SpringBootApplication
public class Day02Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Day02Application.class, args);
Person bean = context.getBean(Person.class);
System.out.println(bean);
}
}
输出结果:Person(name=zhixun, age=22)
5.AOP
在使用AOP之前,首先得先导入相对应的依赖!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
//创建service接口,并对其实现接口
public interface SomeService {
void query(Integer id);
void save(String name,Integer age);
}
@Service
public class SomeServiceImpl implements SomeService {
@Override
public void query(Integer id) {
System.out.println("-----SomeService的query方法-----");
}
@Override
public void save(String name, Integer age) {
System.out.println("-----SomeService的save方法-----");
}
}
编写切面类
@Component
@Aspect
public class LogAspect {
//功能增强的方法
//切点 execution(* com.example.day_02_aop.service..*.*(..))
@Before("execution(* com.example.day_02_aop.service..*.*(..))")
public void sysLog(JoinPoint jp) {
StringJoiner log = new StringJoiner("|", "{", "}");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
log.add(formatter.format(LocalDateTime.now()));
//当前执行的业务方法的名称
String methodName = jp.getSignature().getName();
log.add(methodName);
//方法的参数
Object[] args = jp.getArgs();
for (Object arg : args) {
log.add(arg == null ? "-" : arg.toString());
}
System.out.println("日志:" + log);
}
}
测试方法
@Autowired
private SomeServiceImpl someService;
@Test
public void testLog(){
someService.query(1);
someService.save("zhixun",22);
}
输出结果:
日志:{2023-10-13 15:24:47|query|1}
-----SomeService的query方法-----
日志:{2023-10-13 15:24:47|save|zhixun|22}
-----SomeService的save方法-----
三、自动配置
四、访问数据库
1.DataSource
2.轻量的jdbc Template
导入对应依赖
<!-- JdbcTemplate 连接池: HikariCP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
在application中配置mysql
spring:
datasource:
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
#设置执行脚本 always:总是执行 never:不执行
sql:
init:
mode: never
1.JdbcTemplate访问Mysql
创建article类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
private Integer id;
private String title;
private String content;
private String creat;
private Integer uId;
private String category;
}
注入jdbcTemplate,计算article表中的有多少行数据
//注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
void test01() {
String sql = "select count(*) as ct from article";
Long count = jdbcTemplate.queryForObject(sql,Long.class);
System.out.println("article表中有 " + count + "条记录");
}
输出结果:article表中有 21条记录
//查询结果为单行记录,使用?作为参数的占位符
@Test
void test02() {
String sql = "select * from article where id = ?";
//queryForObject方法必须得有查询结果,如果没找到会抛异常
Article article = jdbcTemplate.queryForObject(sql,
new BeanPropertyRowMapper<>(Article.class),3);
System.out.println("查询结果为 " + article);
}
输出结果:查询结果为 Article(id=3, title=求助万能墙的微信, content=有没有漂亮妹妹, creat=2023-05-23 22:38:06, uId=1, category=111)
测试自定义RowMapper
@Test
void testRowMapper() {
String sql = "select * from article where id = 3";
Article article1 = jdbcTemplate.queryForObject(sql, (rs, rownum) -> {
System.out.println("rs=" + rs);
var id = rs.getInt("id");
var title = rs.getString("title");
var content = rs.getString("content");
var creat = rs.getString("creat");
var uId = rs.getInt("uId");
var category = rs.getString("category");
return new Article(id, title, content, creat, uId, category);
});
System.out.println("查询结果为 " + article1);
}
输出结果:查询结果为 Article(id=3, title=求助万能墙的微信, content=有没有漂亮妹妹, creat=2023-05-23 22:38:06, uId=1, category=111)
测试List集合
@Test
void testList() {
String sql = "select * from article order by id";
//一个list成员为一行记录,map为列名和值
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
maps.forEach( el -> {
el.forEach((filed,value)->{
System.out.println("字段名称:" + filed + " 值:" + value);
});
});
}
输出结果:
字段名称:id 值:3
字段名称:title 值:求助万能墙的微信
字段名称:content 值:有没有漂亮妹妹
字段名称:creat 值:2023-05-23 22:38:06
字段名称:uid 值:1
字段名称:category 值:111
更新数据
@Test
void testUpdate() {
String sql = "update article set title = ? where id = ? ";
int result = jdbcTemplate.update(sql, "Java实现编程", 3);
System.out.println(result);
}
2.NamedParameterJdbcTemplate
@Test
void testNamedParameter() {
String sql = "select * from article where id>:id and uid=:uid";
//将参数封装到map中
Map<String,Integer> map = new HashMap<>();
map.put("id",5);
map.put("uid",1);
List<Map<String, Object>> maps = namedParameterJdbcTemplate.queryForList(sql, map);
maps.forEach( el ->{
el.forEach((filed,value)->{
System.out.println("字段名:" + filed + " 值 " + value);
});
System.out.println("----------------");
});
}
3.多表查询
4.总结
3.Mybatis
1.增删改查
1.@Select查询
先导入对应依赖
<!-- mybatis启动器(由mybatis团队提供的) -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>3.0.2</version>
<scope>test</scope>
</dependency>
配置yml文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
#mybatis
mybatis:
configuration:
#支持驼峰命名
map-underscore-to-camel-case: true
#日志 控制台输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
创建实体类,mapper接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article {
private Integer id;
private String title;
private String content;
private String creat;
private Integer uId;
private String category;
}
public interface ArticleMapper {
//按主键查询
@Select("""
select * from article where id = #{articleId};
""")
Article selectById(@Param("articleId") Integer id);
//(当属性名与字段名匹配不上,比如数据库叫Tid,属性叫id)可使用ResultSet 和 对象的属性进行映射
@Results(id = "BaseArticleMap" ,value = {
//@Result(id = true(是否是主键),column = "数据库的字段名",property = "实体类的属性名"),
@Result(id = true,column = "id",property = "id"),
@Result(column = "title",property = "title"),
@Result(column = "content",property = "content"),
@Result(column = "creat",property = "creat")
@Result(column = "uid",property = "uId"),
@Result(column = "category",property = "category")
})
Article selectById(@Param("articleId") Integer id);
}
在启动类上添加mapperScan,进行mapper扫描
//扫描Mapper接口的位置
@MapperScan(basePackages = "com.zhixun.day_03_mybatis.mapper")
@SpringBootApplication
public class Day03MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(Day03MybatisApplication.class, args);
}
}
测试方法
@Autowired
private ArticleMapper mapper;
@Test
void test01() {
Article article = mapper.selectById(3);
System.out.println(article);
}
输出结果:Article(id=3, title=Java实现编程, content=有没有漂亮妹妹, creat=2023-05-23 22:38:06, uId=1, category=111)
2.@Insert插入
@Insert("""
insert into article(id, title, content, creat, uid, category)
values (#{id},#{title},#{content},#{creat},#{uId},#{category})
""")
Integer insertArticle(Article article);
@Test
void test02() {
Article article = new Article(59,"Java编程开发","qqqqqq","qqqqqq",1,"qqqqqq");
System.out.println(mapper.insertArticle(article));
}
输出结果:
==> Preparing: insert into article(id, title, content, creat, uid, category) values (?,?,?,?,?,?)
==> Parameters: 60(Integer), Java编程开发(String), qqqqqq(String), qqqqqq(String), 1(Integer), qqqqqq(String)
<== Updates: 1
3.@Update
@Update("""
update article set content = #{content} where id=#{id};
""")
Integer updateArticleContentById(Integer id,String content);
@Test
void test03() {
System.out.println(mapper.updateArticleContentById(60,"java是一种面向对象的编程"));
}
输出结果:
==> Preparing: update article set content = ? where id=?;
==> Parameters: java是一种面向对象的编程(String), 60(Integer)
<== Updates: 1
4.@Delete
@Delete("""
delete from article where id = #{id};
""")
Integer deleteArticleById(Integer id);
@Test
void test04() {
System.out.println(mapper.deleteArticleById(60));
}
输出结果:
==> Preparing: delete from article where id = ?;
==> Parameters: 60(Integer)
<== Updates: 1
5.注解总结
MyBatis注解开发
1.加入mybatis的starter, mysql驱动(8.0以上)
2.创建实体类
3.创建Mapper接口,在接口定义方法,并使用合适的注解。
1.@Select: 查询 (可使用@Results做结果映射)
2.@Insert:新增
3.@Update:更新
4.@Delete:删除
4.在启动器上面,加入MapperScan
//扫描Mapper接口的位置
@MapperScan(basePackages = "com.zhixun.day_03_mybatis.mapper")
5.在application中配置mybatis
-
定义数据库链接
-
mybatis设置:日志,驼峰命名
2.结果映射ResultMap
1.@ResultMap
public interface ArticleDao {
//1.查询某个用户的所有文章
@Select("""
select * from article where uid = #{uid}
""")
@Results(id = "BaseArticleMap" ,value = {
//@Result(id = true(是否是主键),column = "数据库的字段名",property = "实体类的属性名"),
@Result(id = true,column = "id",property = "id"),
@Result(column = "title",property = "title"),
@Result(column = "content",property = "content"),
@Result(column = "creat",property = "creat"),
@Result(column = "uid",property = "uId"),
@Result(column = "category",property = "category")
})
List<Article> selectList(Integer uid);
@Select("""
select * from article where id = #{id}
""")
//为了不必要的频繁写Results进行结果映射
//在同一个类中
//可通过@ResultMap(ResultsID) 调用之前已经写好的Results结果映射
@ResultMap("BaseArticleMap")
//2.查询某个文章
Article selectArticleById(Integer id);
}
@Autowired
private ArticleDao articleDao;
@Test
void test05() {
List<Article> articles = articleDao.selectList(1);
articles.forEach(System.out::println);
System.out.println(articleDao.selectArticleById(5));
}
输出结果:
Article(id=3, title=Java实现编程, content=有没有漂亮妹妹, creat=2023-05-23 22:38:06, uId=1, category=111)
Article(id=4, title= 大家都会迷茫吗?对自己有什么规划, content=如题。以后要做什么工作,自己擅长什么?毕业意味着失业的问题你怎么看待?有没有什么是现在可以准备的?, creat=2023-05-23 22:38:06, uId=1, category=python)
Article(id=5, title=福建工程学院更名, content=福建工程学院更名成福建理工大学, creat=2023-05-23 22:38:06, uId=1, category=springboot)
2.通过XML文件
当Result映射很多的时候,可以使用xml文件来对其进行映射
首先先在资源文件下创建mappers/XXX.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhixun.day_03_mybatis.mapper.ArticleDao">
<!-- 定义ResultMap -->
<resultMap id="ArticleMapper" type="com.zhixun.day_03_mybatis.pojo.Article">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="creat" property="creat"/>
<result column="uid" property="uId"/>
<result column="category" property="category"/>
</resultMap>
</mapper>
并在application中配置路径
#mybatis
mybatis:
configuration:
#支持驼峰命名
map-underscore-to-camel-case: true
#日志 控制台输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#指定自定义mapper文件的位置
#设置执行脚本 always:总是执行 never:不执行
#classpath:/mappers/**/*.xml
#表示mappers任意子目录下的xml文件
mapper-locations: classpath:/mappers/**/*.xml
并在接口中引用 resultMap id="ArticleMapper"的id,并进行测试
@Select("""
select * from article where id = #{id}
""")
//为了不必要的频繁写Results进行结果映射
//在同一个类中
//可通过@ResultMap(ResultsID) 调用之前已经写好的Results结果映射
@ResultMap("ArticleMapper")
//2.查询某个文章
Article selectArticleById(Integer id);
测试
@Autowired
private ArticleDao articleDao;
@Test
void test05() {
System.out.println(articleDao.selectArticleById(5));
}
输出结果:
Article(id=5, title=福建工程学院更名, content=福建工程学院更名成福建理工大学, creat=2023-05-23 22:38:06, uId=1, category=springboot)
3.SQL的提供者
创建提供者类
public class SqlProvider {
//定义静态方法
public static String selectArticle(){
return """
select * from article where id = #{articleId};
""";
}
public static String updateArticleContentById(){
return """
update article set content = #{content} where id=#{id};
""";
}
public static String insertArticle(){
return """
insert into article(id, title, content, creat, uid, category)
values (#{id},#{title},#{content},#{creat},#{uId},#{category})
""";
}
public static String deleteArticleById(){
return """
delete from article where id = #{id};
""";
}
}
创建接口
public interface ArticleRepository {
//使用提供者
@SelectProvider(type = SqlProvider.class,method = "selectArticle")
Article selectArticle(Integer id);
@UpdateProvider(type = SqlProvider.class,method = "updateArticleContentById")
Integer updateArticleContentById(Integer id,String context);
@InsertProvider(type = SqlProvider.class,method = "insertArticle")
Integer insertArticle(Article article);
@DeleteProvider(type = SqlProvider.class,method = "deleteArticleById")
Integer deleteArticleById(Integer id);
}
测试方法
@Autowired
private ArticleRepository articleRepository;
@Test
void test06() {
System.out.println(articleRepository.selectArticle(5));
}
4.@One一对一
创建一对一关系的两个类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer userId;
private String name;
private String gender;
private Integer age;
private String school;
private String identity;
private String userAccountUsername;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserAccount {
private String userAccountUsername;
private String password;
private String email;
//一对一的关系
private User user;
}
public interface UserOneToOne {
//查询账户信息
@Select("""
select * from user_account where user_account_username = #{username}
""")
@Results({
@Result(id = true,column = "user_account_username",property = "userAccountUsername"),
@Result(column = "password",property = "password"),
@Result(column = "email",property = "email"),
//将column字段的值传给selectUserInfo方法
//等selectUserInfo方法查询完之后 在将查询的值赋给user
@Result(column = "user_account_username",property = "user",
//一对一的关系 select是 查询user方法的全类名
one = @One(select = "com.zhixun.day_03_mybatis.mapper.UserOneToOne.selectUserInfo"
//fetchType检索 FetchType.LAZY懒加载模式 当需要了在加载
,fetchType = FetchType.LAZY))
})
UserAccount selectUserAccount(@Param("username") String username);
//查询用户信息
@Select("""
select * from user where user_account_username = #{username}
""")
@Results({
@Result(id = true,column = "user_id",property = "userId"),
@Result(column = "name",property = "name"),
@Result(column = "gender",property = "gender"),
@Result(column = "age",property = "age"),
@Result(column = "school",property = "school"),
@Result(column = "identity",property = "identity"),
@Result(column = "user_account_username",property = "userAccountUsername")
})
User selectUserInfo(@Param("username") String username);
}
测试
@Test
void test07() {
UserAccount userAccount = userOneToOne.selectUserAccount("zzx");
System.out.println(userAccount);
}
输出结果:
<== Columns: user_id, name, gender, age, school, identity, user_account_username
<== Row: 6, 张志勋, 男, 32, 北京大学, 教师, zzx
<== Total: 1
UserAccount(userAccountUsername=zzx, password=$2a$10$WUw/JV88.r98LHS6W46KRurAtC4GDf9sQMnBWnQE2h0ZWWb62gRQC, email=4@qq.com, user=User(userId=6, name=张志勋, gender=男, age=32, school=北京大学, identity=教师, userAccountUsername=zzx))
5.@Many一对多
创建一个一对多的关系类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer userId;
private String name;
private String gender;
private Integer age;
private String school;
private String identity;
private String userAccountUsername;
private List<Article> articleList;
}
一对多用法其实跟一对一一样,只是一个是对应一条数据,一个是对应多条数据。
public interface UserOneToMany {
@Select("""
select * from user where user_id = #{id}
""")
@Results({
@Result(id = true,column = "user_id",property = "userId"),
@Result(column = "name",property = "name"),
@Result(column = "gender",property = "gender"),
@Result(column = "age",property = "age"),
@Result(column = "school",property = "school"),
@Result(column = "identity",property = "identity"),
@Result(column = "user_account_username",property = "userAccountUsername"),
@Result(column = "user_id",property = "articleList",
many = @Many(select = "com.zhixun.day_03_mybatis.mapper.UserOneToMany.selectByUid")
,fetchType = FetchType.LAZY)
})
User selectUserById(Integer id);
@Select("""
select * from article where uid = #{id}
""")
List<Article> selectByUid(Integer id);
测试
@Autowired
private UserOneToMany userOneToMany;
@Test
void test08() {
User user = userOneToMany.selectUserById(1);
System.out.println(user);
}
输出结果:
User(userId=1, name=行太官方, gender=男, age=30, school=行太学堂, identity=教师, userAccountUsername=xt,
articleList=[
Article(id=3, title=Java实现编程, content=有没有漂亮妹妹, creat=2023-05-23 22:38:06, uId=1, category=111),
Article(id=4, title= 大家都会迷茫吗?对自己有什么规划, content=如题。以后要做什么工作,自己擅长什么?毕业意味着失业的问题你怎么看待?有没有什么是现在可以准备的?, creat=2023-05-23 22:38:06, uId=1, category=python),
Article(id=5, title=福建工程学院更名, content=福建工程学院更名成福建理工大学, creat=2023-05-23 22:38:06, uId=1, category=springboot)
6.MybatisAutoConfigration
7.连接池
Hikari 连接池 默认的
连接池分三种:Tomcat,DBCP,Hikari
mysql优化设置
4.声明式事务
@Service
public class UserAccountServiceImpl implements UserAccountService {
//注入mapper
@Autowired
private userMapper userMapper;
/**
*
* @Transactional:事务控制注解
* 位置: 方法上/类上
* 事务回滚:
* 1.默认对运行时异常,执行回滚 rollback
* @Transactional(rollbackFor = {IOException.class})
* 2.rollbackFor: 是需要回滚异常类的列表
* 3.spring框架中发现是运行时异常时,如果有事务注解时,此行为会被取消,事务将会执行回滚
* */
@Transactional
@Override
public boolean insertUserAccount(UserAccount userAccount) {
int row = userMapper.userAccountInsert(userAccount);
//抛出异常
if (row < 1){
throw new RuntimeException("账号添加失败");
}
User user = new User();
user.setUserAccountUsername(userAccount.getUserAccountUsername());
int userRow = userMapper.userInsert(user);
return (row + userRow) >= 2;
}
}
// 开启事务管理器 可加可不加
@EnableTransactionManagement
@MapperScan("com.example.day_03_trans.mappers")
@SpringBootApplication
public class Day03TransApplication {
public static void main(String[] args) {
SpringApplication.run(Day03TransApplication.class, args);
}
}
事务的回滚原则
5.总结
=====================
提供sql语句,使用提供者类
@SelectProvider
@InsertProvider
@UpdateProvider
@DeleteProvider
提供者类:定义一个普通类,定义一个静态方法,放的放回置是要执行的sql语句
=====================
关系 @One一对一 @Many一对多
=====================
MybatisAutoConfiguration 自动配置类
1. SqlsessionFactory 能够获取Sqlsession
2. SqlSessionTemplate 用于执行sql语句 mybatis和spring整合时,使用的模板对象
3. MapperFactoryBean:创建dao接口的代理对象
=====================
连接池,连接的大小控制
connections = ((cpu 核心数 * 2) + 磁盘数量) 近似值。 默认10
=====================
事务:spring的事务管理
采用声明式事务:注解方式@Transaction,在public方法上面加入注解
控制事务: 1.传播行为 2.隔离级别 3.只读 4.超过时间
回滚规则:
1.业务方法发送RunTimeException 和 Error 将会回滚事务
2.@Transactional的rollbackFor控制事务的回滚类型
五、Web服务
1.页面视图
导入对应依赖
<!-- ThymeLeaf视图依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Web依赖 包含:SpringMVC,Tomcat服务器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
创建一个controller类
//@Controller:创建控制对象,控制器能够接收请求,响应结果给浏览器
@Controller
public class QuickController {
//定义方法处理请求,方法叫做控制器方法(处理器方法)
//Model表示模型,存储数据。这个数据最后放在request作用域
//HttpServletRequest这个作用域中
@RequestMapping("/quick")
public String quick(Model model){
//调用Service,处理请求,获取数据
model.addAttribute("title","quick");
model.addAttribute("time", LocalDateTime.now());
//指定视图显示数据
return "quick";
}
}
在templates文件夹下创建对应的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${title}"></p>
<p th:text="${time}"></p>
</body>
</html>
启动类运行
浏览器显示结果:
浏览器请求:http://localhost:8080/quick
quick
2023-10-15T10:14:21.671589300
2.Json视图
创建一个User类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}
创建Controller测试
@Controller
public class JsonViewController {
//显示Json视图
@RequestMapping("/json")
public void responseJson(HttpServletResponse response) throws IOException {
String json = "{\"name\":\"lisi\"}";
//应答,通过HttpServletResponse输出
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.println(json);
out.flush();
out.close();
}
//SpringMVC支持控制器放回对象,由框架将对象使用jackson转为json,并输出
// @ResponseBody 返回json数据
//User -> Jackson工具库的ObjectMapper对象,将数据转为json格式
@ResponseBody
@RequestMapping("/user")
public User getUserJson(){
return new User("zhixun",22);
}
}
浏览器显示结果:{"name":"zhixun","age":22}
3.SpringMVC
1.Controller控制器
2.匹配请求路径到控制器方法
在application中配置
#路径匹配的策略
spring:
mvc:
pathmatch:
matching-strategy: path_pattern_parser
创建controller进行测试
@RestController
public class ExamPathController {
//?可代表1个字符
@GetMapping("/uz?.html")
public User path1(){
return new User("uz",12);
}
//* 可代表0-多个字符
@GetMapping("/u*er.html")
public User path2(){
return new User("zhixun",12);
}
//** 可代表0-多个字符
//代表以pic开头的请求都是正确的
@GetMapping("pic/**")
public User path3(){
return new User("pic",12);
}
//路径变量
//{*myname}:匹配多个路径一直到url的结尾
//注意:@GetMapping("order/{*id}/{*date}")无效的,{*..}后面不能在有匹配规则了
@GetMapping("order/{*id}")
public String path4(@PathVariable("id") String orderId, HttpServletRequest request){
return "path4: " + request.getRequestURI() + ", id = " + orderId;
}
请求:GET http://localhost:8080/order/1001/2023-10-15
输出结果:path4: /order/1001/2023-10-15, id = /1001/2023-10-15
//正则表达式
//必须以.log结尾
@GetMapping("/pages/{fname:\\w+}.log")
public String path5(@PathVariable("fname") String fName, HttpServletRequest request){
return "path5: " + request.getRequestURI() + ", fName = " + fName;
}
请求:GET http://localhost:8080/pages/req.log
输出结果:path5: /pages/req.log, fName = req
请求:GET http://localhost:8080/pages/###.log
输出结果:报错 404
}
3.@RequestMapping
4.控制器方法参数类型与可用返回值类型
5.接收请求
创建@Controller测试简单数据,和对象如何接收参数。
@RestController
public class ParameterController {
//GET http://localhost:8080/param?name=lisi&age=20&sex男
//一一对应,适合接收简单类型数据String,int,long,double,float,参数数量较少情况下
@GetMapping("/param")
public String p1(String name,Integer age,String sex){
return "接收参数:" + name + "," + age + "," + sex;
}
//GET http://localhost:8080/param/p2?name=lisi&age=20
//使用对象接收参数,要求对象的属性名称和请求中的参数名一样,属性有set方法,类有无参构造方法
@GetMapping("/param/p2")
public String p2(User user){
return user.toString();
}
//使用HttpServletRequest接收参数
@GetMapping("/param/p3")
public String p3(HttpServletRequest request) {
String name = request.getParameter("name");
String age = request.getParameter("age");
String sex = request.getParameter("sex");
return "name = " + name + ", age = " + age + ", sex = " + sex;
}
//使用@RequestParam接收参数
//@RequestParam(value = "name",required = true) 表示请求中name必须存在
//如果required = false 则可以写默认值当不存在时 将使用默认值来进行传值
@GetMapping("/param/p4")
public String p4(@RequestParam(value = "name",required = true) String name,
@RequestParam(value = "age",required = false,defaultValue = "22")String age,
@RequestParam(value = "sex",required = true) String sex) {
return "name = " + name + ", age = " + age + ", sex = " + sex;
}
/**
* 当前端数据为json格式的时候
* @RequestBody:从请求体中读取json数据,将数据转为形参对象的属性值
*/
@PostMapping("/param/json")
public String p5(@RequestBody User user) {
return "json user:" + user.toString();
}
注意传的时候 需要添加头
POST http://localhost:8080/param/json
Content-Type: application/json
{"name":"张三","age": "22"}
}
6.数组接收多个参数
POST http://localhost:8080/param/vals?id=11&id=12&id=31
POST http://localhost:8080/param/vals?id=1,2,3,4,5
@PostMapping("/param/vals")
public String p6(Integer[] id) {
List<Integer> list = Arrays.stream(id).toList();
return list.toString();
}
2.Java Bean Validation
1.快速上手
创建一个类,并用注解进行验证
import jakarta.validation.constraints.*;
import lombok.Data;
/**
* @author zhixun
* @date 2023/10/16 9:06
* @describe
*/
@Data
public class ArticleVo {
private Integer id;
//不能为空 否则提示message
@NotNull(message = "必须有作者")
private Integer userId;
//不能为空串
@NotBlank(message = "文章必须有空格")
//@Size 认为 null是有效值 所以需要加一个@NotBlank
@Size(min = 3,max = 30,message = "文章标题必须在3个字符以上")
private String title;
@NotBlank(message = "必须有副标题")
//@Size 认为 null是有效值 所以需要加一个@NotBlank
@Size(min = 5,max = 30,message = "文章副标题必须在5个字符以上")
private String summary;
//阅读数量不能小于零,@DecimalMin支持小数
@DecimalMin(value = "0",message = "阅读数量不能小于零")
private Integer readCount;
@Email(message = "邮箱不符合规则")
private String email;
}
创建controller类进行测试
@RestController
public class ArticleController {
//发布文章
//加入@Validated注解表示 验证后面这个BeanArticleVo article
//BindingResul 存储的就是验证Bean的结果数据
@PostMapping("/article/add")
public Map<String, Object> addArticle(@Validated @RequestBody ArticleVo article, BindingResult br) {
Map<String, Object> map = new HashMap<>();
if (br.hasErrors()) {
//获取没有通过属性的字段
List<FieldError> fieldErrors = br.getFieldErrors();
for (int i = 0; i < fieldErrors.size(); i++) {
FieldError field = fieldErrors.get(i);
map.put(i + "-" + field.getField(), field.getDefaultMessage());
}
}
return map;
}
}
测试数据
###
POST http://localhost:8080/article/add
Content-Type: application/json
{
"id": 0,
"userId": 10,
"title": "title_bba28cf2ad9e",
"summary": "summary_46964894e1fd",
"readCount": 1,
"email": "aaa163.com"
}
输出结果:
{
"0-email": "邮箱不符合规则"
}
2.分组校验
@Data
public class ArticleVo {
//组就是接口名
public static interface AddArticleGroup {
}
;
public static interface EditArticleGroup {
}
;
//当是编辑组的时候 这段注解才生效
@NotNull(message = "文章id必须有值", groups = {EditArticleGroup.class})
@Min(value = 1, message = "id大于0", groups = {EditArticleGroup.class})
private Integer id;
//不能为空 否则提示message
@NotNull(message = "必须有作者", groups = {AddArticleGroup.class, EditArticleGroup.class})
private Integer userId;
//不能为空串
@NotBlank(message = "文章必须有空格", groups = {AddArticleGroup.class, EditArticleGroup.class})
//@Size 认为 null是有效值 所以需要加一个@NotBlank
@Size(min = 3, max = 30, message = "文章标题必须在3个字符以上", groups = {AddArticleGroup.class, EditArticleGroup.class})
private String title;
@NotBlank(message = "必须有副标题", groups = {AddArticleGroup.class, EditArticleGroup.class})
//@Size 认为 null是有效值 所以需要加一个@NotBlank
@Size(min = 5, max = 30, message = "文章副标题必须在5个字符以上", groups = {AddArticleGroup.class, EditArticleGroup.class})
private String summary;
//阅读数量不能小于零,@DecimalMin支持小数
@DecimalMin(value = "0", message = "阅读数量不能小于零", groups = {AddArticleGroup.class, EditArticleGroup.class})
private Integer readCount;
@Email(message = "邮箱不符合规则", groups = {AddArticleGroup.class, EditArticleGroup.class})
private String email;
}
测试
###
POST http://localhost:8080/article/add
Content-Type: application/json
{
"id": null,
"userId": 10,
"title": "title_bba28cf2ad9e",
"summary": "summary_46964894e1fd",
"readCount": 1,
"email": "aaa@163.com"
}
###
POST http://localhost:8080/article/edit
Content-Type: application/json
{
"id": 2,
"userId": 10,
"title": "title_bba28cf2ad9e",
"summary": "summary_46964894e1fd",
"readCount": 1,
"email": "aaa@163.com"
}
3.ValidationAutoConfiguration
3.Model
//@Controller:创建控制对象,控制器能够接收请求,响应结果给浏览器
@Controller
public class QuickController {
//定义方法处理请求,方法叫做控制器方法(处理器方法)
//Model表示模型,存储数据。这个数据最后放在request作用域
//HttpServletRequest这个作用域中
@RequestMapping("/quick")
public String quick(Model model){
//调用Service,处理请求,获取数据
model.addAttribute("title","quick");
model.addAttribute("time", LocalDateTime.now());
//指定视图显示数据
return "quick";
}
}
4.视图 View
5.总结
4.SpringMVC请求流程
1.DispatcherServlet是一个Servlet
2.SpringMVC 完整流程
5.自动配置
1.服务器配置
server:
#服务器端口
port: 8088
#上下文访问路径
servlet:
context-path: /api
#字符编码
encoding:
charset: utf-8
#强制 request,response设置charset字符编码
force: true
#Tomcat日志路径
tomcat:
accesslog:
#日志路径
directory: D:/logs
#访问日志权限
enabled: true
#日志文件名前缀
prefix: access_log
#日志文件信息
file-date-format: yyyt-MM-dd
#日志名称后最
suffix: .log
#post 请求内容最大值,默认2M
max-http-form-post-size: 2000000
#服务器最大连接
max-connections: 8192
2.DispatcherServlet
#路径匹配的策略
spring:
mvc:
pathmatch:
matching-strategy: path_pattern_parser
#配置DispatcherServlet
servlet:
path: /course
load-on-startup: 0
#全局的日期格式 但是日期格式必须为2023-10-17这种
format:
date-time: yyyy-MM-dd HH:mm:ss
3.总结
6.Servlets,Filters,and Listeners
1.@WebServlet
创建类
/*
* @WebServlet:等同于web.xml中 有关servlet的声明
* <servlet>
* <servlet-name>HelloServlet</servlet-name>
* <servlet-class>xxxx</servlet-class>
* </servlet>
*
* <servlet-mapping>
* <url-pattern>/helloServlet</url-pattern>
*</servlet-mapping>
*
* */
@WebServlet(urlPatterns = "/helloServlet",name = "HelloServlet")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;character=utf-8");
PrintWriter out = resp.getWriter();
out.println("这是Sptingboot中的servlet");
out.flush();
out.close();
}
}
在启动项配置扫描信息
//扫描@WebServlet注解
@ServletComponentScan(basePackages = "com.example.day_05_servletfilter")
@SpringBootApplication
public class Day05ServletFilterApplication {
public static void main(String[] args) {
SpringApplication.run(Day05ServletFilterApplication.class, args);
}
}
测试请求:http://localhost:8080/helloServlet
1.编码创建servlet
创建类
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;character=utf-8");
PrintWriter out = resp.getWriter();
out.println("这是login的servlet");
out.flush();
out.close();
}
}
创建一个配置类
@Configuration
public class webAppConfig {
@Bean
public ServletRegistrationBean servletRegistrationBean(){
//创建ServletRegistrationBean对象 登记一个或者多个Servlet
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(new LoginServlet());
registrationBean.addUrlMappings("/user/login");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
}
测试请求:http://localhost:8080/user/login
2.创建Filter (过滤器)
Filter对象是使用频率比较高,比如日志记录,权限验证,敏感字符过滤等。Web框架中包含内置的Filter,SpringMvc中也包含较多的内置Filter,比如CommonsRequestLoggingFilter(记录请求日志),CorsFilter(全局跨域处理),FormContentFilter(用来支持put,delete)…
1.@WebFilter
创建Filter类
// “/*” 访问web应用请求时都要经过过滤器
@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String url = ((HttpServletRequest)request).getRequestURI().toString();
System.out.println("过滤器-------" + url);
chain.doFilter(request,response);
}
}
在启动器上加入扫描注解
@ServletComponentScan(basePackages = "com.example.day_05_servletfilter")
@SpringBootApplication
public class Day05ServletFilterApplication {
public static void main(String[] args) {
SpringApplication.run(Day05ServletFilterApplication.class, args);
}
}
因为设置的是‘/ *’ 所以当浏览器不管发送扫描请求都会经过过滤器
控制台输出:过滤器------- /user/login
2.FilterRegistrationBean
FilterRegistrationBean和ServletRegistrationBean用法相同,无需注解。
创建WebConfig类
@Configuration
public class webAppConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
//登记filter对象
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
因为设置的是‘/ *’ 所以当浏览器不管发送扫描请求都会经过过滤器
控制台输出:过滤器------- /user/login
3.Filter排序
如果不在WebConfig中设置的话,是通过类名按顺序执行
WebConfig.class
@Bean
//登录Filter 指定顺序
public FilterRegistrationBean addLogFilter(){
FilterRegistrationBean logFilter = new FilterRegistrationBean<>();
logFilter.setFilter(new LogFilter());
logFilter.addUrlPatterns("/*");
//设置顺序
logFilter.setOrder(2);
return logFilter;
}
@Bean
public FilterRegistrationBean addAuthFilter(){
FilterRegistrationBean authFilter = new FilterRegistrationBean<>();
authFilter.setFilter(new LogFilter());
authFilter.addUrlPatterns("/*");
//设置顺序 顺序越小 越先执行
authFilter.setOrder(1);
return authFilter;
}
4.使用Filter框架
@Bean
//登记框架内置的Filter
public FilterRegistrationBean addCommonLogFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CommonsRequestLoggingFilter commonsRequestLoggingFilter = new CommonsRequestLoggingFilter();
//记录请求的url地址
commonsRequestLoggingFilter.setIncludeClientInfo(true);
registrationBean.setFilter(commonsRequestLoggingFilter);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
因为commons是debug级别,所以需要在配置文件中进行级别配置。
#设置日志为debug
logging.level.web=debug
3.Listeners (监听器)
7.WebMvcConfigurate
1.页面控制器
/**
* @author zhixun
* @date 2023/10/17 10:43
* @describe SpringMVC的配置类:使用JavaConfig框架的方式配置SpringMVC,代替原来的xml配置文件
*/
@Configuration
public class MvcSettings implements WebMvcConfigurer {
//页面跳转控制器,从请求直达视图页面(无需controller)
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//页面配置控制:addViewController("请求uri地址"),指定他的视图setViewName("目标视图")
//请求/welcome 文件路径index.html
registry.addViewController("/welcome").setViewName("index");
}
}
请求:http://localhost:8080/welcome
2.数据格式化
创建DeviceInfo类
@Data
public class DeviceInfo {
private String item1;
private String item2;
private String item3;
private String item4;
private String item5;
}
创建转换器 当控制器接收到device类型的数据要进行解析时,会先找有没有相对应转换器。如果有就将拿去到值,去转换器调用parse方法,对其进行解析。
public class DeviceFormatter implements Formatter<DeviceInfo> {
//text表示请求参数的值
@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 joiner = new StringJoiner("#");
joiner.add(object.getItem1()).add(object.getItem2()).add(object.getItem3())
.add(object.getItem4()).add(object.getItem5());
return joiner.toString();
}
}
创建controller
@RestController
public class DeviceController {
@PostMapping("/device/add")
public String addDeviceInfo(@RequestParam("device")DeviceInfo info){
return "接收的设备信息" + info.toString();
}
}
测试:
###
POST http://localhost:8080/device/add
Content-Type: application/x-www-form-urlencoded
device = 1111; 2222; 333,NF; 4; 561
输出结果:接收的设备信息DeviceInfo(item1=1111, item2= 2222, item3= 333,NF, item4= 4, item5= 561)
3.拦截器
一个拦截器
创建controller
@RestController
public class ArticleController {
@PostMapping("/article/add")
public String addArticle(){
return "发布新的文章";
}
@PostMapping("/article/edit")
public String editArticle(){
return "编辑文章";
}
@GetMapping("/article/query")
public String queryArticle(){
return "查询文章";
}
@DeleteMapping("/article/remove")
public String removeArticle(){
return "删除文章";
}
}
创建拦截器
//拦截器
public class AuthInterceptor implements HandlerInterceptor {
private static final String COMMON_USER = "ZhiXun";
//判断登录的用户能否执行相应的动作
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("==========AuthInterceptor权限拦截器");
//登录的用户
String loginUser = request.getParameter("loginUser");
//获取请求的uri地址
String requestURI = request.getRequestURI();
//判断用户和操作
//当用户是zhixun时 只能使用查询功能
if (COMMON_USER.equals(loginUser) &&
(requestURI.startsWith("/article/add")||
requestURI.startsWith("/article/edit")||
requestURI.startsWith("/article/remove"))){
return false;
}
return true;
}
}
在实现WebMvcConfiguere中重写addInterceptors方法
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
AuthInterceptor authInterceptor = new AuthInterceptor();
registry
.addInterceptor(authInterceptor) //添加拦截器
.addPathPatterns("/article/**") //添加拦截器地址
.excludePathPatterns("/article/query"); //排除拦截地址
}
多个拦截器
在创建一个拦截器类
public class LoginInterceptor implements HandlerInterceptor {
private List<String> permitUser = new ArrayList<>();
public LoginInterceptor(){
this.permitUser = Arrays.asList("zhangsan","lisi","admin");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("===========登录拦截器");
//获取的用户名
String loginUser = request.getParameter("loginUser");
//只有三个用户能够访问系统
if (StringUtils.hasText(loginUser) && permitUser.contains(loginUser)){
return true;
}
return false;
}
}
在一个拦截器的基础上,再加一个拦截器
//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//权限拦截器
AuthInterceptor authInterceptor = new AuthInterceptor();
registry
.addInterceptor(authInterceptor) //添加拦截器
.addPathPatterns("/article/**") //添加拦截器地址
.excludePathPatterns("/article/query"); //排除拦截地址
//登录拦截器
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") //拦截所有controller
.addPathPatterns("/article/query");
}
}
8.文件上传
1.MultipartResolver
创建文件上传的html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>文件上传成功</h1>
<form action="uploadFile" enctype="multipart/form-data" method="post">
选择文件:<input type="file" name="upfile"><br>
<input type="submit" value="上传">
</form>
</body>
</html>
创建Controller
Controller
public class UploadFileController {
//上传文件的控制器方法
@PostMapping("/uploadFile")
public String uploadFile(@RequestParam("upfile")MultipartFile multipartFile){
System.out.println("开始处理上传文件");
Map<String,Object> info = new HashMap<>();
try {
//判读上传了文件
if (!multipartFile.isEmpty()){
info.put("上传文件的参数名称",multipartFile.getName()); //upfile
info.put("内容类型",multipartFile.getContentType());
var ext = "unknown";
var filename = multipartFile.getOriginalFilename(); //原始文件的名称,例如a.gif
if (filename.indexOf(".") > 0 ){
ext = filename.substring(filename.lastIndexOf("."));
}
//生成服务器使用的文件名称
var newFileName = UUID.randomUUID().toString() + ext;
var path = "D://upload//" + newFileName; //存储服务器的文件
//把文件保存到path目录
multipartFile.transferTo(new File(path));
info.put("文件名称",newFileName);
}
}catch (Exception e){
e.printStackTrace();
}
//重定向到index
return "redirect:/index.html";
}
}
再springboot中还能创建5xx.html,4xx.html等,默认就是当发送5xx错误会自动跳转到5xx页面。
2.Servlet规范
现在使用的是基于Servlet6
创建controller
@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 item : items) {
if (item.trim().startsWith("filename")) {
return item.substring(item.indexOf("=") + 2, item.length() - 1);
}
}
return "";
}
}
需要再application配置存储路径
#配置上传文件的输出目录
spring.servlet.multipart.location=D://upload
3.多文件上传
多文件上传,再接收文件参数部分有所改变MultipartFile[] files。循环遍历数组解析每个上传的文件。
创建html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>文件上传成功</h1>
<form action="files" enctype="multipart/form-data" method="post">
选择文件:<input type="file" name="upfile"><br>
选择文件:<input type="file" name="upfile"><br>
选择文件:<input type="file" name="upfile"><br>
<input type="submit" value="上传">
</form>
</body>
</html>
创建controller
@Controller
public class UploadFileMultiController {
//上传文件的控制器方法
@PostMapping("/uploadFile")
public String uploadFile(@RequestParam("upfile") MultipartFile[] multipartFiles) {
System.out.println("开始处理上传文件");
Map<String, Object> info = new HashMap<>();
try {
for (MultipartFile multipartFile : multipartFiles) {
//判读上传了文件
if (!multipartFile.isEmpty()) {
info.put("上传文件的参数名称", multipartFile.getName()); //upfile
info.put("内容类型", multipartFile.getContentType());
var ext = "unknown";
var filename = multipartFile.getOriginalFilename(); //原始文件的名称,例如a.gif
if (filename.indexOf(".") > 0) {
ext = filename.substring(filename.lastIndexOf("."));
}
//生成服务器使用的文件名称
var newFileName = UUID.randomUUID().toString() + ext;
var path = "D://upload//" + newFileName; //存储服务器的文件
//把文件保存到path目录
multipartFile.transferTo(new File(path));
info.put("文件名称", newFileName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
//重定向到index
return "redirect:/index.html";
}
}
9.全局异常
1.全局异常处理器
创建controller
@RestController
public class NumberController {
@PostMapping("/divide")
public String some(Integer n1,Integer n2){
int result = n1/n2;
return "n1 / n2 = " + result;
}
}
创建异常处理器
/*
* 1. 在类的上面加入@ControllerAdvice,@RestControllerAdvice
* 灵活组合@ControllerAdvice,@ResponseBody
* 2. 在类中自定义方法,处理各种异常
* 方法定义同Controller类中的方法的定义
*
*/
//Advice在AOP中 叫增强
//控制器增加,给Controller增强异常处理功能。类似AOP的思想
@ControllerAdvice
public class GlobalExceptionHandle {
//定义方法处理:数学异常
/*
*
*@ExceptionHandler 指定处理异常的方法
* 位置:在方法上面
* 属性:是异常类型class数组,如果你的系统抛出的异常类型与@ExceptionHandler声明的相同,由当前的方法来处理异常
* */
@ExceptionHandler({ ArithmeticException.class})
public String handlerArtithmeticException(ArithmeticException e, Model model){
String error = e.getMessage();
model.addAttribute("error",error);
return "exp";//视图
}
}
创建html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/divide" method="post">
除数:<input type="text" name="n1"><br>
被除数:<input type="text" name="n2"><br>
<input type="submit" value="计算">
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>显示异常信息</h1>
<p th:text="${error}"></p>
</body>
</html>
2.异常处理返回json数据
@ExceptionHandler({ ArithmeticException.class})
@ResponseBody public Map<String,String> handlerArtithmeticException(ArithmeticException e, Model model){
Map<String,String> error = new HashMap<>();
error.put("msg",e.getMessage());
error.put("tips","被除数不能为0");
return error;//json 数据
}
3.BeanValidator异常处理
创建实体类
@Data
public class OrderVo {
@NotBlank(message = "订单名称不能为空")
private String name;
@NotNull(message = "商品必须有数量")
@Range(min = 1,max = 99,message = "一个订单的商品数量在{min} -- {max}")
private Integer amount;
@NotNull(message = "用户不能为空")
@Min(value = 1,message = "从1开始")
private Integer userId;
}
创建controller类
@RestController
public class OrderController {
@PostMapping("/order/new")
public String createOrder(@Validated @RequestBody OrderVo orderVo){
return "订单信息:" + orderVo.toString();
}
}
//处理JSR303 验证参数的异常
@ExceptionHandler({BindException.class})
@ResponseBody public Map<String, Object> handlerJSR303Exception(BindException e){
System.out.println("===============J303=============");
//BindException是MethodArgumentNotValidException父类
Map<String,Object> map = new HashMap<>();
BindingResult result = e.getBindingResult();
if (result.hasErrors()){
List<FieldError> fieldErrors = result.getFieldErrors();
for (int i = 0; i < fieldErrors.size(); i++) {
FieldError field = fieldErrors.get(i);
map.put(i + "-" + field.getField(),field.getDefaultMessage());
}
}
return map;
}
测试方法
###
POST http://localhost:8080/order/new
Content-Type: application/json
{
"name": "每日订单",
"amount": 0,
"userId": 10
}
输出结果:
{
"0-amount": "一个订单的商品数量在1 -- 99"
}
4.ProblemDetail
1.RFC 7807
2.自定义异常处理器ProblemDetail
10.远程访问@HttpExchange
六、AOT