SpringBoot
- 1、容器功能JavaConfig一些注解
- 2、SpringBoot分析
- 3、SpringBoot 和 web 开发
- 4、ORM 操作 MySQL
- 5、MyBatis逆向工程
- 6、接口架构风格——RESTful
- 7、SpringBoot 集成 Redis
- 8、SpringBoot 集成 Dubbo
- 9、SpringBoot 打包
- 10、Themeleaf 模板
- 11、总结
- 12、开发小技巧
- 练习
1、容器功能JavaConfig一些注解
为什么使用 SpringBoot ?
- Spring、SpringMVC 使用功能大量的配置文件,还需要配置各种对象,将使用的对象放入 Spring 容器中,需要了解其他框架的配置规则,较麻烦
- SpringBoot 相当于不需要配置文件的 Spring+SPringMVC ,常用的框架和第三方都配置好了
- 底层实际使用的还是 Spring+SPringMVC
什么是 JavaConfig ?
JavaConfig 是 Spring 提供的,使用 java 类替代 xml 配置文件,是配置 Spring 容器的纯 java 的方式。在这个 java 类可以创建 java 对象,把对象放入 Spring 容器中(注入到容器)
这个 java 类使用两个注解:
- @Configuration :放在类上面,表示这个类是配置类,作为配置文件使用
- @Bean :放在配置类中的方法上面,用于声明对象,并将对象注入到容器,相当于之前的bean标签
1.1、@Configuration、@Bean、@Import
pom.xml
创建 maven 模块,配置 pom 文件
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 编译插件 -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<!-- 编译级别 -->
<configuration>
<sourcr>1.8</sourcr>
<target>1.8</target>
<!-- 编码格式 -->
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
1.1.1、JavaConfig类
package com.config;
import com.vo.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration:表示当前类是作为配置文件使用的,就是用来配置容器的
* 位置:在类的上面
* MySpringConfig 这个类就相当于 beans.xml
*
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法,默认 true
* Full(proxyBeanMethods = true)【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
* 总结: 如果为true,得到的配置对象是代理对象,调用多次方法都会从容器中寻找,不会创建
如果为false,得到的配置对象是一个普通对象,调用方法就是执行代码,调用几次,就创建几个对象
* 4、@Import({User.class, DBHelper.class})
* 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名(一般是方法名)
*/
@Import({Student.class, DBHelper.class})
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MySpringConfig {
/**
* 创建方法,要求:
* 1)方法的返回值是对象
* 2)方法上面使用 @Bean
* 方法的返回值对象就注入到容器中
*
* @Bean:把返回值注入到Spring容器中,作用相当于<bean>
* 位置:在方法的上面
* 没有指定对象的名称时,默认将方法名作为对象的 id 名称
*/
@Bean
public Student createStudent(){
Student s1 = new Student();
s1.setName("陈磊");
s1.setAge(81);
s1.setSex("男");
return s1;
}
/**
* 指定对象在容器中的 id 名称(指定<bean>的id属性)
* 使用注解@Bean的name属性,指定对象的 id 名称
*/
@Bean(name = "daxiguaStudent")
public Student makeStudent(){
Student s1 = new Student();
s1.setName("大西瓜");
s1.setAge(66);
s1.setSex("男");
return s1;
}
}
测试
@SpringBootApplication
public class PublicApplication {
public static void main(String[] args) {
//1、返回我们IOC容器
ConfigurableApplicationContext run
= SpringApplication.run(PublicApplication.class, args);
//2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
1.1.2、@Bean注解中的属性
在xml配置bean的方式中,我们可以在bean标签中的id,name,init-method,destroy-method,scope等属性来完成对应的配置,在使用JavaConfig方式中我们也一样能通过相应的配置来完成同样的效果,这些效果大多封装到@Bean注解的属性中
@Bean注解中的属性
name: 对应bean标签中的name属性,用于给bean取别名
initMethod: 对应bean标签中的init-method属性,配置bean的初始化方法
destroyMethod: 对应bean标签中的destroy-method属性,配置bean的销毁方法
注意: 在配置类的方式中有许多的默认规定,比如:
@Bean注解一定存在与配置类里面(配置类:使用@Configuration注解的类)
bean的id就是当前方法名
配置多例则是在方法上添加 @Scope("prototype") 注解来实现,一般不用配,默认单例即可
@Scope注解是 Spring IOC 容器中的一个作用域,在 Spring IOC 容器中,他用来配置Bean实例的作用域对象。@Scope 具有以下几种作用域:
singleton 单实例的(单例)(默认) ----全局有且仅有一个实例
prototype 多实例的(多例) ---- 每次获取Bean的时候会有一个新的实例
reqeust 同一次请求 ----request:每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session 同一个会话级别 ---- session:每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
要指定的初始化方法、销毁方法是 SomeBean 类中定义的方法
@Configuration
public class JavaConfig {
@Bean(initMethod = "init")
public SomeBean someBean() {
return new SomeBean();
}
}
以上案例中,在配置类内部去定义方法返回bean对象交给Spring管理的方式存在一个问题,就是如果需要创建的Bean很多的话,那么就需要定义很多的方法,显示类比较累赘,使用起来不方便,在XML配置的方式中可以通过配置组件扫描器的方式来减少bean标签的定义,那么在JavaConfig的方式中也可以通过添加组件扫描器的方式来减少方法的定义
@Component
@Setter@Getter
public class SomeBean {
}
@Configuration //表示该类是Spring的配置类
@ComponentScan //开启组件扫描器,默认扫描当前类所在的包,及其子包
public class JavaConfig { }
如果需要扫描的包不是配置类所在的包时,我们可以通过注解中的value属性来修改扫描的包
注意:组件扫描的方式只能扫描我们自己写的组件,如果某个bean不是我们写的,则还是要通过在配置类中定义方法来处理,两者是可以同时存在的
1.2、@Conditional
条件装配: 满足Conditional指定的条件,则进行组件注入
演示 @ConditionalOnBean 、@ConditionalOnMissingBean
注解放在方法上面时:
注意此时 daxiguaStudent 是在 createStudent 的前面,这样才会识别到容器中有没有此组件
@Configuration //告诉SpringBoot这是一个配置类 == 配置文件
public class MySpringConfig {
//@Bean(name = "daxiguaStudent")
public Student makeStudent(){
Student s1 = new Student();
s1.setName("大西瓜");
s1.setAge(66);
return s1;
}
//当容器中有daxiguaStudent的时候,才会注入createStudent
@ConditionalOnBean(name = "daxiguaStudent")
@Bean
public Student createStudent(){
Student s1 = new Student();
s1.setName("陈磊");
s1.setAge(81);
return s1;
}
}
@SpringBootApplication
public class PublicInterfaceApplication {
public static void main(String[] args) {
// 返回IOC容器
ConfigurableApplicationContext run
= SpringApplication.run(PublicInterfaceApplication.class, args);
boolean daxiguaStudent = run.containsBean("daxiguaStudent");
System.out.println("容器中daxiguaStudent组件" + daxiguaStudent);
boolean createStudent = run.containsBean("createStudent");
System.out.println("容器中createStudent组件" + createStudent);
}
}
注解放在类上面时:
当容器中有此组件时,类中的方法才会生效
配置类本身也是一个组件,所以这和在方法上面使用区别不大
@Configuration
@ConditionalOnBean(name = "daxiguaStudent")
public class MySpringConfig {
@Bean
public Student createStudent(){
Student s1 = new Student();
s1.setName("陈磊");
s1.setAge(81);
return s1;
}
@Bean(name = "daxiguaStudent")
public Student makeStudent(){
Student s1 = new Student();
s1.setName("大西瓜");
s1.setAge(66);
return s1;
}
}
1.3、@ImportResource
@ImportResource 作用是导入其他的 xml 配置文件,因为会有其他人是使用配置文件注入bean,这边就可以使用此注解进行引用
相当于之前在配置文件中设置:
<import resources="配置文件"/>
示例:
现在有另外一个spring配置文件 catBeans.xml :
<bean id="myCat" class="com.vo.Cat">
<property name="catName" value="Tom"/>
<property name="catAge" value="2"/>
</bean>
在 JavaConfig 类上使用注解 @ImportResource
@Configuration
@ImportResource(value = {"classpath:catBeans.xml","classpath:Beans.xml"})
public class MySpringConfig {}
然后就可以创建 Cat 对象:
//使用 JavaConfig,java 类使用 @ImportResource 注解导入其他xml文件
@Test
public void test4(){
//生成Spring容器
ApplicationContext appc
= new AnnotationConfigApplicationContext(MySpringConfig.class);
Cat cat = (Cat) appc.getBean("myCat");
System.out.println("cat:"+cat);
}
1.4、@PropertyResource、@ComponentScan
@PropertyResource :作用是读取 properties 属性配置文件,使用属性配置文件可以实现外部化配置,在程序代码之外提供数据
相当于之前在配置文件中设置:
<context:property-placeholder location="classpath:conf/jdbc.properties"/>
@ComponentScan:就是组件扫描器
相当于之前在配置文件中设置:
<context:component-scan base-package="com.service"/>
@PropertyResource 使用步骤:
- 在 resources 目录下,创建 properties 文件,使用 key=value 的格式提供数据
- 在 @PropertyResource 指定 properties 文件的位置
- 使用 @Value( value=“${key}” )
示例:(使用 JavaConfig 的方式,不是配置文件的方式)
先设置一下编码格式
外部属性配置文件:
tiger.name=东北虎
tiger.age=13
声明普通类:
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@ToString //未提供属性的set方法
@Component("tiger")
public class Tiger {
@Value("${tiger.name}")
private String tigerName;
@Value("${tiger.age}")
private Integer tigerAge;
}
JavaConfig 类:
@Configuration
//该注解让Spring框架读取到该文件的内容,得到里面的键和值
@PropertySource(value = "classpath:config.properties")
//该注解让Spring扫描指定包,识别到注解去创建容器内对象
@ComponentScan(basePackages = "com.vo")
public class MySpringConfig {}
测试:
//使用 JavaConfig
//配置类使用 @PropertySource 引入外部属性配置文件,使用 @ComponentScan 扫描要创建对象的包
@Test
public void test5(){
ApplicationContext appc = new AnnotationConfigApplicationContext(MySpringConfig.class);
Tiger tiger = (Tiger) appc.getBean("tiger");
System.out.println("tiger:"+tiger);
}
1.5、@EnableConfigurationProperties、@ConfigurationProperties
在实体类上
使用 @ConfigurationProperties 方式一:和@Component配合使用
此种方式可以通过 id 获取到容器对象
注意需要有 get、set 方法
@Component //需要将此类加载到容器中,才可以使用@ConfigurationProperties
//表示使用配置文件中前缀为mystudent的属性的值初始化该实例的同名属性
@ConfigurationProperties(prefix = "mystudent")
public class Student implements Serializable {
private static final long serialVersionUID = 8778575111580687451L;
private Integer id;
private String name;
private String phone;
private Integer age;
}
配置文件内容
mystudent.name=lisi
mystudent.age=18
输出得到对象
Student(id=null, name=lisi, phone=null, age=18)
在配置类上
使用 @ConfigurationProperties 方式二:和@EnableConfigurationProperties配合使用
这里实体类 Car 上面并没有使用@Component等相关注解
@ConfigurationProperties(prefix = "car")
public class Car {
private String name;
}
@EnableConfigurationProperties放在配置类或者启动类上均可
@EnableConfigurationProperties(Car.class)
//1、开启Car配置绑定功能
//2、把这个Car这个组件自动注册到容器中
public class MyConfig {}
使用 @ConfigurationProperties 方式三:和@Bean配合使用
以数据源配置为例:
将前缀为 “spring.datasource.primary” 的属性,赋值给DataSource对应的属性值
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
}
2、SpringBoot分析
SpringBoot 是Spring全家桶中的一个成员,可以简化Spring+SpringMVC的使用,核心还是 IOC 容器
特点:
-
创建独立的 Spring 应用
-
内嵌 Tomcat、Jetty、Undertow,即可直接使用Tomcat服务器,不需要单独安装Tomcat服务器
-
提供了 starter 起步依赖,简化应用的配置
例如:之前使用Mybatis框架,需要在spring配置文件中声明SqlSessionFactoryBean、MapperScannerConfigurer
现在在SpringBoot项目中只需要在 pom.xml 文件中加入 mybatis-spring-boot-starter 依赖,即可拥有以上对象
-
尽可能自动配置好 spring 和第三方库,就是自动配置机制
就是把 spring 、第三方库中的对象都创建好,放入容器中,开发人员可以直接使用
-
提供了健康检查、统计、外部化配置功能
-
不用生成代码,不用使用xml做配置
使用 IDEA 创建 SpringBoot 项目有三种方式:
- 使用 springboot 提供的初始化器,向导的方式完成 SpringBoot 项目的创建,https://start.spring.io
- 使用springboot提供的初始化器,使用的国内地址 ,https://start.springboot.io
- 使用 maven 向导创建项目
2.1、创建 SpringBoot 项目
2.1.1、使用springboot提供的初始化器
需要联网 https://start.spring.io
创建项目:
2.1.2、使用国内地址
需要联网 https://start.springboot.io
创建项目:
其他都一样
或者在浏览器进行设置,然后下载并解压包,然后在 IDEA 导入
解压并导入模块:
2.1.3、创建 maven 项目
不需要联网
创建 maven 项目,修改 pom.xml 文件,修改文件夹、添加对应文件即可
2.2、pom 分析
2.2.1、父工程坐标
SpringBoot提供了一个名为 spring-boot-starter-parent 的工程,里面已经对各种常用依赖(并非全部)的版本进行了管理。
即 pom.xml 文件引入依赖的时候,不需要写 version 标签,只需要写位置名称坐标
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
spring-boot-starter-parent 的工程是springboot内部提供的,使用 parent 标签对该工程进行继承 POM 的部分配置
项目与模块之间就是 MAVEN 中的继承
- 在项目的 父pom 中使用 dependency 指定依赖,模块会自动引入该依赖
- 在项目的 父pom 中使用 dependencyManagement 指定依赖,模块不会自动引用,需要的时候指定依赖标签即可
- 模块的 子pom 中不需要指定版本,直接使用项目中指定的版本
- 如果不想用项目中指定的版本,模块只需要在 子pom 里面指定版本即可
继承是 Maven 中很强大的一种功能,继承可以使得子POM可以获得 parent 中的部分配置(groupId,version,dependencies,build,dependencyManagement等),可以对子pom进行统一的配置和依赖管理。
- parent项目中的dependencyManagement里的声明的依赖 , 只具有声明的作用,并不实现引入,因此子项目需要显式的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本
- parent项目中的dependencies里声明的依赖会被所有的子项目继承
2.2.2、web启动器
是SpringBoot提供的web启动器 , 是一个快速集成web模块的工具包 , 包含 springmvc、jackson 相关的依赖
以及嵌入了 Tomcat9 服务器,默认端口 8080
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
怎么更改端口号?
需要注意的是,我们并没有在这里指定版本信息。因为SpringBoot的父工程已经对版本进行了管理了。
这个时候,我们会发现项目中多出了大量的依赖:
这些都是SpringBoot根据spring-boot-starter-web这个依赖自动引入的,而且所有的版本都已经管理好,不会出现冲突。
傻瓜式配置的工具包
SpringBoot非常优秀的地方在于提供了非常多以spring-boot-starter-* 开头的开箱即用的工具包,常见工具包有以下一些:
spring-boot-starter: 核心的工具包,提供了自动配置,日志和YAML配置支持
spring-boot-starter-aop: 提供了快速集成SpringAOP和AspectJ的工具包
spring-boot-starter-freemarker: 提供了快速集成FreeMarker的工具包
spring-boot-starter-test:提供了测试SpringBoot应用的工具包
spring-boot-starter-web:提供了快速集成web模块的工具包,包括基于SpringMVC,Tomcat服务器等
spring-boot-starter-actuator:提供了生产环境中使用的应用监控工具包
spring-boot-starter-logging:提供了对日志的工具包,默认使用Logback
2.2.3、打包方式
- 之前学习 web 的时候,都是打包成 war ,并且传递给客户使用的话,需要客户拥有 jdk、Tomcat,将 war 包解压到 Tomcat 的 webapps 目录下,启动 Tomcat 服务,然后使用网址运行
- 现在springboot打包是 jar ,里面自带 tomcat 服务器,客户只需要拥有 jdk,然后在压缩包所在的目录启动 cmd 窗口,输入命令
java -jar xxx.jar
,就可以使用网址运行 - 使用 springboot 打包之后还可以更改端口号,在压缩包所在的目录启动 cmd 窗口,输入命令
java -jar xxx.jar --server.port=80
对于 SpringBoot 项目来说无论是普通应用还是web应用,其打包方式都是 jar 即可,当然web应用也能打war包,但是需要额外添加许多插件来运行,比较麻烦
默认的Maven打包方式是不能正常的打包SpringBoot项目的,需要额外的引入打包插件,才能正常的对SpringBoot项目打成jar包,以后只要拿到该jar包就能脱离IDE工具独立运行了
<!-- pom.xml中添加插件 -->
<build>
<plugins>
<!-- SpringBoot打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 使用 maven 的 package 命令进行打包
- 使用命令 java -jar xxx.jar 运行jar包 ( --server.port=80)
2.3、重要注解@SpringBootApplication
@SpringBootApplication 是复合注解,这里重点的注解有3个:
-
@SpringBootConfiguration
1、@SpringBootConfiguration @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; } 说明:使用了 @SpringBootConfiguration 注解标注的类,可以作为配置文件使用 可以使用@Bean声明对象,注入到容器
-
@EnableAutoConfiguration
2、@EnableAutoConfiguration 作用:启动自动配置,把java对象配置好,并注入到spring容器中 例如可以把mybatis的对象创建好,放入容器中
-
@ComponentScan
3、@ComponentScan 作用:组件扫描器,找到注解,根据注解的功能创建对象、给属性赋值等等 默认扫描的包:@ComponentScan 所在类的 所在包及其子包 例如在2.1.4入门案例中,HelloController类上面使用@Controller、@RequestMapping等注解, 但是没有声明组件扫描器,是因为里面的Application类上面有 @SpringBootApplication 注解, 而HelloController类所在包是Application类所在包的子包
2.4、SpringBoot自动配置原理
启动类:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args); //参数:当前类的Class,形参
}
}
了解@SpringBootApplication注解
这里重点的注解有3个:
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
@SpringBootConfiguration
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
通过这段我们可以看出,在这个注解上面,又有一个@Configuration
注解。通过上面的注释阅读我们知道:这个注解的作用就是声明当前类是一个配置类,然后Spring会自动扫描到添加了@Configuration
的类,并且读取其中的配置信息。而@SpringBootConfiguration
是来声明当前类是SpringBoot应用的配置类,项目中只能有一个。所以一般我们无需自己添加。
@ComponentScan
默认扫描的包是启动类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中。
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@AutoConfigurationPackage
自动配置包,指定了默认的包规则(启动类所在包及其子包)
@Import({Registrar.class})
public @interface AutoConfigurationPackage {}
//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来,就是 DemoApplication 所在包下
public void registerBeanDefinitions() { //这里指定是 DemoApplication 所在包内的所有组件
AutoConfigurationPackages.register(registry
, (String[])(new AutoConfigurationPackages.PackageImports(metadata))
.getPackageNames().toArray(new String[0]));
}
@Import(AutoConfigurationImportSelector.class)
在 AutoConfigurationImportSelector.class 类中
1、是利用 getAutoConfigurationEntry(annotationMetadata); 给容器中批量导入一些组件
2、在1的方法中,是调用 List<String> configurations
= getCandidateConfigurations(annotationMetadata, attributes)
来获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
得到所有的组件
classLoader.getResources("META-INF/spring.factories");
4、从META-INF/spring.factories位置来加载一个文件
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
虽然 133 个场景的所有自动配置启动的时候默认全部加载,但按照条件装配规则(@Conditional),最终会按需配置,不一定会所有都加载
总结
自动配置类会给容器中装配很多组件,例如 容器中加入了文件上传解析器
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中必须有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中必须没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
虽然 SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean //此注解是如果没有此对象就创建
public CharacterEncodingFilter characterEncodingFilter() {}
@Configuration( proxyBeanMethods = false )
@EnableConfigurationProperties({ServerProperties.class}) //从配置文件绑定指定值
@ConditionalOnWebApplication( type = Type.SERVLET )
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
prefix = "server.servlet.encoding", //用户也可以在配置文件中修改编码格式
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {}
总结:
-
SpringBoot 先加载所有的自动配置类 xxxxxAutoConfiguration.class
-
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties.class的值,xxxProperties.class和配置文件进行了绑定
-
生效的配置类就会给容器中装配很多组件
-
只要容器中有这些组件,相当于这些功能就有了
-
定制化配置
-
- 用户直接自己 @Bean 替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
@EnableAutoConfiguration
的作用,告诉SpringBoot基于你所添加的依赖,去“猜测”你想要如何配置Spring。比如我们引入了spring-boot-starter-web
,而这个启动器中帮我们添加了tomcat
、SpringMVC
的依赖。此时自动配置就知道你是要开发一个web应用,所以就帮你完成了web及SpringMVC的默认配置了!
SpringBoot内部对大量的第三方库或Spring内部库进行了默认配置,这些配置是否生效,取决于我们是否引入了对应库所需的依赖,如果有那么默认配置就会生效。
那么
- 这些默认配置是在哪里定义的呢?
- 为何依赖引入就会触发配置呢?
其实在我们的项目中,已经引入了一个依赖:spring-boot-autoconfigure,其中定义了大量自动配置类
我们来看一个我们熟悉的,例如SpringMVC,查看 mvc 的自动配置类:
打开WebMvcAutoConfiguration:
我们看到这个类上的4个注解:
-
@Configuration
:声明这个类是一个配置类 -
@ConditionalOnWebApplication(type = Type.SERVLET)
ConditionalOn,翻译就是在某个条件下,此处就是满足项目的类是是Type.SERVLET类型,也就是一个普通web工程,显然我们就是
-
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer。这里就是判断你是否引入了SpringMVC相关依赖,引入依赖后该条件成立,当前类的配置才会生效!
-
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
这个条件与上面不同,OnMissingBean,是说环境中没有指定的Bean这个才生效。其实这就是自定义配置的入口,也就是说,如果我们自己配置了一个WebMVCConfigurationSupport的类,那么这个默认配置就会失效!
接着,我们查看WebMvcAutoConfiguration该类中定义了什么:
视图解析器:
还有默认支持put请求的过滤器.
总结:
- @SpringBootApplication注解内部是3大注解功能的集成
- @ComponentScan: 开启组件扫描
- @SpringBootConfiguration: 作用等同于@Configuration注解,也是用于标记配置类
- @EnableAutoConfiguration: 内部导入AutoConfigurationImportSelector,该类中有个getCandidateConfigurations方法,加载jar包中META-INF/spring.factories文件中配置的配置对象,自动配置定义的功能,包括: AOP / PropertyPlaceholder / FreeMarker / HttpMessageConverter / Jackson / DataSource / DataSourceTransactionManager / DispatcherServlet / WebMvc 等等
- SpringApplication.run(…)的作用
-
启动SpringBoot应用
-
加载自定义的配置类,完成自动配置功能
-
把当前项目配置到嵌入的Tomcat服务器
-
启动嵌入的Tomcat服务器
-
2.5、SpringBoot核心配置文件
SpringBoot核心配置文件的名字必须以 application 开始
扩展名有两种:
- .properties文件
key=value 键值对方式
- .yml 文件
key: value 冒号前无空格,后有一个空格
注意:
当项目中同时拥有 application.properties、application.yml,默认使用 properties 格式的配置文件
2.5.1、.properties文件(默认)
使用配置文件设置端口号和上下文路径
#设置端口号
server.port=8080
#设置访问应用的上下文路径:contextpath
server.servlet.context-path=/myboot
这样访问controller的地址需要是:http://localhost:8080/myboot/hello
2.5.2、.yml 文件(默认)
这样访问controller的地址需要是:http://localhost:8080/myboot/hello
2.5.3、多环境配置
开发、上线、测试等不同的环境,需要不同的端口号、上下文路径、数据库等的配置信息,此时就需要配置多环境
使用多环境配置文件,方便切换不同的配置:
使用方式: 创建多个配置文件,命名:application-环境名称.properties/yml
例如,创建开发环境的配置文件:application-dev.properties
创建多个配置文件,然后在默认使用的配置文件中去激活使用哪一个环境配置文件
#激活使用哪个配置文件
spring.profiles.active=dev
配置文件目录优先级
SpringBoot配置文件可以放置在多种路径下,不同路径下的配置优先级有所不同。默认会扫描这几个放置目录的默认配置文件:
当前项目的根目录/config/ # 最高优先级
当前项目的根目录/ # 第二优先级
类路径(在resources目录下)/config/ # 第三优先级
类路径(在resources目录下)/ # 第四优先级
如果这四个位置都有配置文件,那么四个配置文件都会生效,只不过四个配置文件中的相同配置项,生效的是高优先级的配置文件里的配置项
即:优先级由高到底,高优先级的配置会覆盖低优先级的相同配置项并互补配置
例如,以下的优先级:8081 --> 8082 --> 8083 --> 8084
2.5.4、自定义配置
@Value 读取数据
使用 @Value 可以拿到配置文件中的 Value 值
使用情景:
如果有些数据内容并不确定,后期可能有改动,那么可以将 key 和 value 放入配置文件,在需要使用的地方用@Value引用,后期更改的时候就只需要改配置文件中的 value 值
配置文件内容:
#配置端口号
server.port=8080
#配置上下文路径,contextpath
server.servlet.context-path=/myboot
#自定义 key=value
school.name=阿菲
school.website=www.baidu.com
site=www.sohu.com
类中内容:
@Controller
public class BootController {
@Value("${server.port}")
private Integer port;
@Value("${server.servlet.context-path}")
private String myPath;
@Value("${school.name}")
private String name;
@Value("${site}")
private String site;
@RequestMapping("/hello")
@ResponseBody
public String queryData(){
return name+"==="+site+"==="+myPath+"==="+port;
}
}
@ConfigurationProperties
可以将整个文件映射成一个java对象,用于自定义配置项较多的情况
在上面使用 @Value 一个一个读取数据的时候,比较麻烦,此时可以使用 @ConfigurationProperties
配置文件内容:
#配置端口号
server.port=8080
#配置上下文路径,contextpath
server.servlet.context-path=/myboot
#自定义 key=value
school.name=阿菲
school.website=www.baidu.com
name=www.sohu.com
自定义一个类:
@Data
@Component
@ConfigurationProperties(prefix = "school")
public class Student {
private String name;
private String website;
}
测试:
@Controller
public class BootController {
@Resource
private Student s;
@RequestMapping("/hello")
@ResponseBody
public String queryData(){
return s.toString(); //Student(name=阿菲, website=www.baidu.com)
}
}
执行结果:
此时 @ConfigurationProperties 注解就会去配置文件寻找 school 开头的 key ,然后根据 key 后部分的名称来找到对应的属性名,属性名与 key 一致,则进行赋值
在使用 @ConfigurationProperties(prefix = “school”) 的时候,因为 school 是自定义的key
所以 idea 会提示,但是并不影响项目运行:
要想去掉提示,需要在 pom.xml 加入依赖:<!-- 处理 @ConfigurationProperties 有关的元数据--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
相当于配置文件中对 key 的解释
2.6、SpringBoot中使用 JSP
不推荐使用jsp,使用功能模板技术代替jsp
使用 jsp 配置步骤:
-
加入一个处理 jsp 的依赖,负责编译 jsp 文件
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency>
-
如果需要使用 servlet、jstl ,就添加以下依赖(此处示例未添加)
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency>
-
创建一个存放 jsp 的目录,webapp
需要指定 webapp 目录是存放web资源的目录,否则无法在里面创建 jsp 页面
-
需要在 pom.xml 指定 jsp 文件编译后的存放目录 META-INF/resources
<build> <!--指定 jsp 文件编译后的存放目录--> <resources> <resource> <!-- jsp原来的目录 --> <directory>src/main/webapp</directory> <!-- 指定编译后的存放目录 --> <targetPath>META-INF/resources</targetPath> <!-- 指定处理的目录和文件 --> <includes> <!--表示webapp目录下任意子目录中的任意文件--> <include>**/*.*</include> </includes> </resource> </resources> </build>
-
创建 Controller ,访问 jsp
@Controller public class MyController { @RequestMapping("/hello") public String doSome(HttpServletRequest request, Model model){ //将数据放入 Request 域中 // 等同于 request.setAttribute("data","使用jsp"); model.addAttribute("data","使用jsp"); //视图的逻辑名称,需要配置视图解析器 return "index"; } }
-
在 application.properties 文件中配置视图解析器
#配置端口号,上下文路径,contextpath server.port=8080 server.servlet.context-path=/myboot #配置视图解析器 #这里的 prefix=/ ,斜杠表示目录 src/main/webapp spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp
测试结果:
jsp 文件编译后的存放:
2.7、ApplicationContext
如果想通过代码,手动从容器获取对象,需要通过 main 方法中的 SpringApplication.run(MyApplication.class, args);
,通过此方法的返回值获取容器
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args){
return run(new Class[]{primarySource}, args);
}
说明:
ConfigurableApplicationContext:接口,是ApplicationContext的子接口
示例:
自定义类
@Service
public class MyServiceImpl implements MyService {
@Override
public void doSome(String name) {
System.out.println(name);
}
}
在 MyApplication 主配置类中:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
//获取容器对象(也可以用ConfigurableApplicationContext appc)
ApplicationContext appc = SpringApplication.run(MyApplication.class, args);
//从容器中获取对象
MyService service = (MyService) appc.getBean("myService");
service.doSome("小草莓");
}
}
2.8、CommandLineRunner接口、ApplicationRunner接口
开发中,需要在容器 启动后,结束前 执行一些操作,比如读取配置文件、数据库连接等操作
框架提供了两个接口 CommandLineRunner 、ApplicationRunner ,执行时间在容器对象创建好后,自动执行 run() 方法,可以在容器对象创建好之后自定义一些操作
底层源码:
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
创建项目,无需添加任何依赖:
service 接口:
@Service("myService")
public class MyServiceImpl implements MyService {
@Override
public void doSome(String name) {
System.out.println(name);
}
}
主配置类:
@SpringBootApplication
public class Application implements CommandLineRunner {
@Resource
private MyService service;
public static void main(String[] args) {
System.out.println("执行顺序:111111111");
SpringApplication.run(Application.class, args);
System.out.println("执行顺序:3333333333333");
}
@Override
public void run(String... args) throws Exception {
System.out.println("执行顺序:22222222");
//可进行自定义操作,如读取文件、数据库等等
service.doSome("lisi");
}
}
执行结果:
执行顺序:111111111
执行顺序:22222222
lisi
执行顺序:3333333333333
3、SpringBoot 和 web 开发
3.1、静态资源访问
1、静态资源目录
只要静态资源放在类路径下的: /static
、 /public
、 /resources
、 /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**。
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
改变默认的静态资源路径
spring:
resources:
static-locations: classpath:/haha/
2、静态资源访问前缀
默认无前缀
spring:
mvc:
static-path-pattern: /res/**
访问 : 当前项目根路径/ + res/ + 静态资源名
@RequestAttribute、@RequestBody
如果请求转发的页面,可以使用 此注解 @RequestAttribute 从域中获取数据
@Controller
public class MyController {
@GetMapping("/goto")
public String gotoPage(HttpServletRequest request){
request.setAttribute("msg","域中数据");
request.setAttribute("code",12);
return "forward:/show"; //转发请求
}
@GetMapping("/show")
@ResponseBody
public String show(@RequestAttribute("msg") String msg,
@RequestAttribute("code") Integer code){
return null;
}
}
@RequestBody
用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的)
拦截器、Servlet、Filter
3.2、拦截器
拦截器是 SpringMVC 中的一种对象,能拦截对 Controller 的请求,拦截器框架中有系统的拦截器,还有自定义拦截器,实现对请求的预先处理
3.2.1、SpringMVC 实现自定义拦截器步骤:
-
自定义类,实现 SpringMVC 框架的 HandlerInterceptor 接口
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
-
在 SpringMVC 的配置文件中,声明拦截器
<!--声明拦截器--> <mvc:interceptors> <!--声明第一个拦截器--> <mvc:interceptor> <!--指定拦截器的拦截地址 path:拦截的 url 地址,可以使用 ** 通配符 (代表多级目录和目录中的资源) 例如:path="/user/**" 路径中 user/ 开头 ,就会拦截 --> <mvc:mapping path="url"/> <bean class="拦截器类的全限定名称"/> </mvc:interceptor> </mvc:interceptors>
3.2.2、SpringBoot 实现自定义拦截器步骤:
-
自定义类,实现 HandlerInterceptor 接口,可以选择性实现拦截方法,实现拦截功能
//自定义拦截器 public class LoginInterceptor implements HandlerInterceptor { /** * 参数: * Object handler:被拦截的控制器对象 * 返回值:boolean * true:请求能被controller处理 * false:请求被截断 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器方法执行"); return true; } }
-
自定义类,实现 WebMvcConfigurer 接口,需要实现
addInterceptors()
方法并且使用注解
@Configuration
,说明是作为配置拦截器的配置类@Configuration public class MyAppConfig implements WebMvcConfigurer { //添加拦截器对象,注入到容器中 @Override public void addInterceptors(InterceptorRegistry registry) { //创建拦截器对象 HandlerInterceptor interceptor = new LoginInterceptor(); //调用方法,添加拦截器对象 //registry.addInterceptor(interceptor); //指定拦截的请求 uri 地址 String path[] = {"/user/**"}; //指定不拦截的地址 //因为拦截器true,所以此地址不会经过拦截器,但还是会被处理 String excPath[] = {"/user/login"}; registry.addInterceptor(interceptor) .addPathPatterns(path) .excludePathPatterns(excPath); } }
测试:
定义类controller,测试拦截器
@Controller
public class MyController {
@RequestMapping("/user/account")
@ResponseBody
public String userAccount(){
return "访问 /user/account 地址";
}
@RequestMapping("/user/login")
@ResponseBody
public String userLogin(){
return "访问 /user/login 地址";
}
}
-
访问:http://localhost:8080/user/account,正常访问;控制台输出:拦截器方法执行
因为 preHandle 返回是true,这个请求被拦截,不执行拦截器方法,但是可以正常被 controller 处理
-
访问:http://localhost:8080/user/login,控制台无输出
3.3、Servlet
在 SpringBoot 框架中使用 Servlet 对象
使用步骤:
-
自定义 Servlet 类,继承 HttpServlet ,重写 doGet、doPost方法
//自定义 Servlet 类 public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //使用HttpServletResponse输出数据,应答结果 resp.setContentType("text/html;charset=utf-8"); PrintWriter pw = resp.getWriter(); pw.println("执行的是 MyServlet "); pw.flush(); pw.close(); } }
-
自定义类,用于注册 Servlet ,让框架能找到 Servlet
类使用注解
@Configuration
,说明是作为配置类方法上使用注解
@Bean
,将方法返回的对象注入容器中@Configuration public class MyAppConfig{ //定义方法,注册 Servlet 对象 @Bean public ServletRegistrationBean servletRegistrationBean(){ /* 使用有参构造器 public ServletRegistrationBean(T servlet, String... urlMappings) 第一个参数是 Servlet 对象,第二个是 url 地址 */ //ServletRegistrationBean bean = // new ServletRegistrationBean(new MyServlet(),"/myservlet"); //使用无参构造 ServletRegistrationBean bean = new ServletRegistrationBean(); bean.setServlet(new MyServlet()); bean.addUrlMappings("/login","/test"); return bean; } }
3.4、Filter
过滤器 Filter 是 Servlet 规范中的过滤器,可以处理请求,对请求的参数、属性进行跳转,常常在过滤器中处理字符编码
使用步骤:
-
自定义类,作为过滤器,需要实现
javax.servlet.Filter
接口,重写 doFilter 方法//自定义过滤器 public class MyFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("执行了 MyFilter"); filterChain.doFilter(servletRequest,servletResponse); } }
-
自定义类,用于注册 Filter 过滤器对象
类使用注解
@Configuration
,说明是作为配置类方法上使用注解
@Bean
,将方法返回的对象注入容器中@Configuration public class MyAppConfig{ @Bean public FilterRegistrationBean filterRegistrationBean(){ //使用无参构造器 FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new MyFilter()); //当访问以下地址会执行过滤器的代码 bean.addUrlPatterns("/user/*"); return bean; } }
-
测试
@Controller public class MyController { @RequestMapping("/user/account") @ResponseBody public String userAccount(){ return "访问 /user/account 地址"; } @RequestMapping("/login") @ResponseBody public String login(){ return "访问 /login 地址"; } }
3.5、字符集过滤器(经典白学)
在浏览器响应数据时,默认编码是 ISO-8859-1
CharacterEncodingFilter :解决 POST 请求中乱码的问题
在 SpringMVC 框架,是在 web.xml 中注册过滤器,配置其属性,如下:
<!--声明字符集过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--指定属性值-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
SpringBoot 实现字符集过滤器步骤:
-
自定义类,作为 Servlet 类
继承 HttpServlet ,重写 doGet、doPost方法
//自定义 Servlet 类 public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //使用HttpServletResponse输出数据,响应数据时,默认编码是 ISO-8859-1 resp.setContentType("text/html"); PrintWriter pw = resp.getWriter(); pw.println("执行的是 MyServlet "); pw.flush(); pw.close(); } }
-
此处需要使用框架提供的过滤器类 characterEncodingFilter ,就不需要自定义过滤器类
但是需要在配置类中 设置 characterEncodingFilter 的三个属性值
-
自定义类,配置注册Servlet和Filter
类使用注解
@Configuration
,说明是作为配置类方法上使用注解
@Bean
,将方法返回的对象注入容器中@Configuration public class MyAppConfig{ //注册 Servlet @Bean public ServletRegistrationBean servletRegistrationBean(){ ServletRegistrationBean bean = new ServletRegistrationBean(new MyServlet(),"/myservlet"); return bean; } //注册 Filter @Bean public FilterRegistrationBean filterRegistrationBean(){ 第一步创建对象 FilterRegistrationBean bean = new FilterRegistrationBean(); 第二步,配置要使用的过滤器类的属性 //使用框架中的 过滤器类 CharacterEncodingFilter filter = new CharacterEncodingFilter(); //设置该类中的三个属性值 //指定使用的编码方式 filter.setEncoding("UTF-8"); //指定request、response使用encoding属性的编码方式 filter.setForceEncoding(true); 第三步,使用过滤器类,配置过滤器的地址 //指定过滤器对象,以及过滤的 url 地址 bean.setFilter(filter); bean.addUrlPatterns("/*"); return bean; } }
-
修改 application.properties 文件,关掉 SpringBoot 框架默认使用的字符集过滤器,让自定义的过滤器起作用
# SpringBoot 中默认已经配置了 CharacterEncodingFilter,编码默认是 ISO-8859-1 #设置 enable=false 作用是关闭系统中配置的过滤器,使用自定义的 CharacterEncodingFilter server.servlet.encoding.enabled=false
3.6、在配置文件中设置过滤器
简单的方式使用 字符集过滤器
修改 application.properties 文件
#让系统的CharacterEncodingFilter生效,也可以不写,默认值就是 true
server.servlet.encoding.enabled=true
#指定使用的编码方式
server.servlet.encoding.charset=utf-8
#强制request、response都使用charset的编码方式
server.servlet.encoding.force=true
3.7、文件上传
页面表单
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="headerfile"><br>
<input type="file" name="photos" multiple><br> //可选多张图片
<input type="submit" value="提交">
</form>
文件上传代码
@Controller
public class MyController {
/**
* MultipartFile 自动封装上传过来的文件
* @RequestParam("email"):获取请求参数,若形参名与请求参数名一致可省略
* @RequestPart("headerImg"):获取表单,表单的name属性,通过MultipartFile类型的形参获取
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerfile") MultipartFile headerfile,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerfile={},photos={}",
email,username,headerfile.getSize(),photos.length);
if(!headerfile.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerfile.getOriginalFilename();
headerfile.transferTo(new File("H:\\cache\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
//获取原生文件名
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "main";
}
}
更改文件大小配置
#单个上传的文件大小
spring.servlet.multipart.max-file-size=10MB
#单次请求的文件大小
spring.servlet.multipart.max-request-size=100MB
3.8、异常处理
自定义错误页面,需要创建目录 error ,然后定义页面
查找顺序:
‘/templates/error/500.’
‘/static/error/500.html’
‘/templates/error/5xx.’
‘/static/error/5xx.html’
4、ORM 操作 MySQL
ORM 全称:Object Relational Mapping ,对象关系映射,其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来
4.1、数据源的自动配置-HikariDataSource
导入 jdbc 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
导入数据库驱动,注意数据库版本和驱动版本对应
默认版本:<mysql.version>8.0.22</mysql.version>
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
2、重新声明版本(maven的属性的就近优先原则)
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
数据库连接池的配置,是自己容器中没有 DataSource 才自动配置的
底层自动配置好的连接池是:HikariDataSource
修改数据源相关的配置:spring.datasource
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
4.2、使用Druid数据源
引入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
修改数据源相关的配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
4.3、集成 MyBatis
-
导入mybatis官方starter
-
编写mapper接口,在接口上标注**@Mapper**注解
或在启动类上使用 @MapperScan(basePackages = {“com.dao”,“com.dao1”})
-
编写sql映射文件并绑定mapper接口
简单的 sql 语句可使用注解来代替,复杂方法编写 mapper.xml 进行绑定映射
-
在application.yaml中,使用 mapper-location 指定Mapper配置文件的位置,以及使用 mybatis.configuration 指定全局配置文件的信息
使用 MyBatis 框架操作数据,在 SpringBoot 框架集成 MyBatis
使用步骤:
-
mybatis 起步依赖,完成 mybatis 对象自动配置,对象放在容器中
-
pom.xml 指定 src/main/java 目录内的 xml 文件包含到 classpath 中
-
创建实体类
-
创建 Dao 接口
接口上使用注解@Mapper,表示此类是dao接口,需要生成代理类
且mapper文件在同一包
-
创建 Dao 接口对应的 Mapper 文件
-
创建 Service 层接口及其实现类
-
创建 Controller 对象,访问 Service
-
写配置文件 application.properties 文件,配置数据库连接信息
使用步骤:
-
mybatis 起步依赖,完成 mybatis 对象自动配置,对象放在容器中
<dependencies> <!--web起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--mybatis起步依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <!--测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
-
pom.xml 指定 src/main/java 目录内的 xml 文件包含到 classpath 中
<build> <resources> <resource> <!--所在目录--> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build>
-
创建实体类
@Data public class Student { private Integer id; private String name; private Integer age; }
-
创建 Dao 接口
/** * @Mapper:用于告诉mybatis,这是dao接口,创建此接口的代理对象 * 位置:在类的上面 */ @Mapper public interface StudentDao { Student selectById(@Param("stuId")Integer id); }
-
创建 Dao 接口对应的 Mapper 文件
<?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.dao.StudentDao"> <select id="selectById" resultType="com.model.Student"> select * from student where id = #{stuId} </select> </mapper>
-
创建 Service 层接口及其实现类
@Service public class StudentServiceImpl implements StudentService { @Resource private StudentDao dao; @Override public Student queryStudent(Integer id) { return dao.selectById(id); } }
-
创建 Controller 对象,访问 Service
@Controller public class StudentController { @Resource private StudentService service; @RequestMapping("/user/account") @ResponseBody public String queryStudent(Integer id){ return service.queryStudent(id).toString(); } }
-
写配置文件 application.properties 文件,配置数据库连接信息
#连接数据库,mysql驱动新版的驱动类 #数据库版本8用com.mysql.cj.jdbc.Driver,8以下使用com.mysql.jdbc.Driver spring.datasource.driver-class-name=com.mysql.jdbc.Driver #serverTimezone=GMT%2B8 表示+8时区 spring.datasource.url=jdbc:mysql:///book?useUnicode=true&cahracterEncoding=UTF-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=admin
4.3.1、mapper接口
第一种方式:@Mapper
@Mapper:放在 dao 接口上面,每个接口都需要使用这个注解
/**
* @Mapper:用于告诉mybatis,这是dao接口,创建此接口的代理对象
* 位置:在类的上面
*/
@Mapper
public interface StudentDao {
Student selectById(@Param("stuId")Integer id);
}
第二种方式:@MapperScan
在 dao 接口上加入 @Mapper ,需要在每个接口上都加入注解,当接口多的时候不方便
现在只需要主启动类(主配置类)上添加注解包扫描 @MapperScan(basePackages = {"com.dao","com.dao1"})
/**
* @MapperScan:找到dao接口和Mapper文件
* basePackages:dao接口所在的包名
*/
@SpringBootApplication
@MapperScan(basePackages = {"com.dao","com.dao1"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第三种方式:mapper文件和dao接口分开管理
现在把 Mapper 文件放在 resources 目录下,进行管理,这样 java 目录下只有java代码
实现步骤:
-
在 resources 目录下创建子目录(自定义),例如 mapper
-
将 Mapper 文件放在 mapper 目录中
-
在 application.properties 文件中,指定 mapper 文件的目录
#指定mapper文件的位置 mybatis.mapper-locations=classpath:mapper/*.xml #指定mybatis的日志,相当于之前mybatis主配置文件中的日志作用 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
-
在 pom.xml 中指定将 resources 目录中的文件,编译到目标目录中
一般resources目录下的文件会自动编译,编译到target目录的classes目录中,但是如果有时候没有自动编译,可以加入以下插件信息
<resources> <resource> <!--所在目录--> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> </resource> </resources>
4.3.2、配置文件
以前在mybatis主配置文件中的信息,可在 application.yaml 中指定,就是相当于改mybatis全局配置文件中的值
# 配置mybatis规则
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml 指定mybatis主配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
#指定mybatis的日志,相当于之前mybatis主配置文件中的日志作用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
可以不写mybatis主配置文件,所有全局配置文件的配置都放在configuration配置项中即可
4.3.3、sql使用注解(不推荐)
sql 语句可使用注解来代替
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(Long id);
public void insert(City city);
}
4.4、事务
回顾 Spring 框架中的事务:
-
管理事务的对象:事务管理器(接口,接口有很多实现类)
例如:使用jdbc或mybatis访问数据库,使用的事务管理器:DataSourceTransactionManager
-
声明式事务:在 xml 配置文件或者使用注解 @Transactional 说明事务控制的内容
控制事务:隔离级别、传播行为、超时时间、回滚异常等等
-
事务处理方式:
- Spring框架中的 @Transactional
- aspectj 框架可以在 xml 配置文件中,声明事务控制的内容
SpringBoot 中使用事务,上面的两种方式都可以
使用步骤:
-
在业务方法的上面加入 @Transactional ,加上注解后,方法就有事务功能了
和 Spring 中的事务注解一样,可设置注解属性
public class StudentServiceImpl implements StudentService { @Resource private StudentMapper dao; /** * @Transactional:表示方法有事务支持 * 默认使用事务,隔离级别:REQUIRED,超时时间:-1 * 抛出运行时异常就回滚事务 */ @Transactional @Override public int addStudent(Student student) { int rows = dao.insert(student); //抛出运行时异常,回滚事务 int i = 12/0; return rows; } }
-
明确的在 主启动类(主配置类)上面,加入 @EnableTransactionManager
@SpringBootApplication @EnableTransactionManagement //启动事务管理器 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
配置 application.properties 文件
#设置端口 server.port=8080 #设置上下文路径 server.servlet.context-path=/myboot #配置数据库 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql:///book?useUnicode=true&cahracterEncoding=UTF-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=admin #配置mybatis mybatis.mapper-locations=classpath:mapper/*.xml #开启日志 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
5、MyBatis逆向工程
5.1、使用 mybatis-generator 插件实现
注意:
使用 mybatis-generator 的方式实现逆向工程
这样生成的类和文件有可能是根据其他数据库中的同名表生成的
不安全
-
在 pom.xml 中加上插件
<!--mybatis代码自动生成插件--> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.6</version> <configuration> <!--配置文件的位置:在项目根目录下,和 src 平级--> <configurationFile>GeneratorMapper.xml</configurationFile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </plugin>
-
在项目根目录下创建 GeneratorMapper.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- 指定连接数据库的JDBC驱动包所在位置,指定到你本机的完整路径 --> <classPathEntry location="F:\mysql-connector-java-5.1.7-bin.jar"/> <!-- 配置table表信息内容体,targetRuntime指定采用MyBatis3的版本 --> <context id="tables" targetRuntime="MyBatis3"> <!-- 抑制生成注释,由于生成的注释都是英文的,可以不让它生成 --> <commentGenerator> <property name="suppressAllComments" value="true"/> </commentGenerator> <!-- 配置数据库连接信息 --> <!--数据库版本8用com.mysql.cj.jdbc.Driver,8以下使用com.mysql.jdbc.Driver--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql:///book?useUnicode=true&cahracterEncoding=UTF-8&serverTimezone=GMT%2B8" userId="root" password="admin"> <property name="nullCatalogMeansCurrent" value="true"/> </jdbcConnection> <!-- 生成model类,targetPackage 指定model类的包名,targetProject 指定生成的model放在IDEA的哪个项目 --> <!-- targetProject 的值就是 targetPackage 指向的包所在的java文件夹在内存中的实际盘符位置 --> <javaModelGenerator targetPackage="com.model" targetProject="F:\workspace\SpringBoot\SpringBoot\004_springboot_mapper\src\main\java"> <property name="enableSubPackages" value="false"/> <property name="trimStrings" value="false"/> </javaModelGenerator> <!-- 生成MyBatis的mapper.xml文件,targetPackage 指定mapper.xml文件的包名,targetProject 指定生成的mapper.xml文件存放位置 --> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="false"/> </sqlMapGenerator> <!-- 生成MyBatis的dao接口,targetPackage 指定dao接口的包名,targetProject 指定生成的dao接口存放位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.dao" targetProject="src/main/java"> <property name="enableSubPackages" value="false"/> </javaClientGenerator> <!-- 数据库表名及对应的 java 模型类名 --> <table tableName="student" domainObjectName="Student" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/> </context> </generatorConfiguration>
-
点击 maven——Plugins——mybatis.generator——mybatis.generator:generate
-
生成结果如下
5.2、使用 IDEA 自带的 database 实现
-
file->settings->Plugins->搜索 better-mybatis-generator插件,点击安装
或者是 Free Mybatis plugin 插件
-
连接Database: View > Tool Windows > Database,然后点击上面+号->dataSource->mysql
-
然后需要填上Host、User、Password和Database。最后还需要选择一个数据库驱动,如果你的Mysql是5.x版本以上的,那你应该用MySQL for 5.1驱动
-
如果 schemas 文件夹内没有显示表数据,点击设置
选择要显示的数据库
-
然后就看到了Database视图包含的数据库
-
点击右边的控制台小图标,我们就可以打开控制台写SQL了
-
点击schemas中需要生成model,mapper的表,右击,选中mybatis-generator会出现一个配置表,根据项目情况配置包名信息及文件名信息->点击OK
-
Setting: Tools > MyBatis generator Plugin 此处可以设置默认配置,未设置则使用程序默认配置
6、接口架构风格——RESTful
API 接口:
-
jar 形式:将功能打包成一个 jar,需要功能项目直接依赖引入 jar 即可使用功能
-
http 形式:需要功能项目发一个 http 请求,就可以实现功能调用
就是后端 Controller 暴露出一个 http链接,访问即可实现访问功能
使用 http 形式 API 接口获取功能,称之为应用接口,后续说接口,都是这种形式,前端说需要的接口,就是这种
前后端分离式开发方式图解:
需要给前端返回 JSON 格式的数据,以方便不同平台开发的需要
HTTP 响应状态码:
200 (成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。
201 (已创建) 请求成功并且服务器创建了新的资源。
202 (已接受) 服务器已接受请求,但尚未处理。
204 (无内容) 服务器成功处理了请求,但没有返回任何内容。
400
(错误请求) 服务器不理解请求的语法。
401
(未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
403 (禁止) 服务器拒绝请求。
404 (未找到) 服务器找不到请求的网页。
406 (不接受) 无法使用请求的内容特性响应请求的网页。
410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。
500
(服务器内部错误) 服务器遇到错误,无法完成请求。
设计映射方法(接口)需要考虑的问题:
-
接口映射路径
明确指定要操作资源(操作对象domain的复数),如 students
-
请求方式(GET/POST)
RESTful 设计中,使用请求方法来描述状态(数据)变化
GET:查询 POST:添加 PUT:更新 DELETE:删除
-
请求参数
-
返回值
返回数据是 JSON 格式
6.1、认识 RESTful
RESTful 设计:
用 REST 表示资源和对资源的操作
在互联网中,表示一个资源或者一个操作,使用唯一的资源定位符 URI 表示
资源是用名词表示,一般使用名词复数
资源状态转换:
CRUD 就是资源状态的转换,数据变动就是状态的变化
REST 风格介绍:★★★★★
资源使用 url 表示,通过名词表示资源以及资源的信息,在url中,使用 “ / ” 分隔对资源的信息
http://localhost:8080/myboot/students/1001
-
请求方式来代表资源状态变化:
GET:查询 POST:添加 PUT:更新 DELETE:删除
-
参数路径方式:
以前请求参数是用 ?进行连接,现在使用 “ / ” 分隔,后端控制器使用注解 @PathVariable 从 url 中获取数据
-
返回值一般都返回 JSON 格式数据
使用请求方法区分具体操作,示例:
传统请求操作使用路径来区分具体操作,RESTful 风格使用请求方法区分具体操作
@Controller
@RequestMapping("students")
public class MyController {
//查询所有学生
@RequestMapping("/list")
@ResponseBody
public Object list(){
return Arrays.asList(
new Student(1,"xiaoxi",18),
new Student(1,"daxi",18));
}
/**
* 需求:查询所有学生
* 1、确定资源,确定 url students
* 2、确定请求方式: GET
* 3、请求参数: wu
* 4、返回值: JSON 格式,集合
*/
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public Object listStudent(){
return Arrays.asList(
new Student(1,"xiaoxi",18),
new Student(1,"daxi",18));
}
}
6.2、API接口测试工具
postman、insomnia、IDEA自带工具
6.2.1、IDEA自带
可以传入参数,更改请求方式等等。。。
示例:
@Controller
@RequestMapping("students")
public class MyController {
/**
* 需求:添加一个学生
* 1、确定资源:/students
* 2、请求方法: POST
* 3、请求参数:学生的属性
* 4、返回值:JSON格式的新增学生信息
*/
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public Object save(Student student){
student.setId(2); //添加学生信息的一些代码
return student;
}
}
6.2.2、Postman
6.3、RESTful 注解
注解概览:
@PathVariable :从 url 中获取数据
@GetMapping:支持get请求方式
@PostMapping:支持POST请求方式
@PutMapping:支持put请求方式
@DeleteMapping:支持delete请求方式
@RestController:复合注解,是 @Controller + @ResponseBody 组合,在类的上面使用
在类上面使用 @RestController,表名此类是后端控制器,并且类中的所有方法都加上了 @ResponseBody
注解介绍:
@PathVariable:路径变量,用于获取url中的数据
缺点:
- 如果方法形参为对象类型,@PathVariable 注解的 value 属性无法指定到对象内的属性,所以此注解仅仅用于简单类型的方法参数
- 参数不能带中文
@Controller
@RequestMapping("students")
public class MyController {
/**
* 需求:查询所有学生
* 1、确定资源,确定 url students
* 2、确定请求方式: GET
* 3、请求参数: wu
* 4、返回值: JSON 格式,集合
*
* @PathVariable:路径变量,用于获取url中的数据
* 属性: value 路径变量名
* 若路径变量名与映射方法形参名一致,可省略该属性
* 位置:在控制器方法的形参前面
*
* {stuId}:定义路径变量,stuId是自定义路径变量名
*
*/
@RequestMapping(value = "{stuId}",method = RequestMethod.GET)
@ResponseBody
public String listStudent(@PathVariable("stuId") Integer id){
return "查询的学生 id =="+id;
}
}
注意:url 必须是唯一
如果当前有两个 get 方式的查询请求:根据学号查询、根据年龄查询
那么它们的路径分别是 /students/{id} 、 /students/{age}
这种情况下,就会出现问题,因为最后面的路径变量都是 Integer 类型,会报错
6.4、在页面、Ajax中实现put、delete请求
在 SpringMVC 中有一个过滤器,支持 POST 请求转为 put、delete
注意:如果使用 SpringMVC ,需要在 web.xml 中启用过滤器,
如果使用 SpringBoot 框架,内部已经启用此过滤器,只需要开启使用即可
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
实现步骤:
-
在 application.properties 文件中,开启使用 HiddenHttpMethodFilter 过滤器
#启用支持 put、delete spring.mvc.hiddenmethod.filter.enabled=true
-
在请求页面中,包含 _method 参数,值为 put、delete,发起这个请求用的是 POST 方式
<form action="/students" method="post"> <input type="hidden" name="_method" value="put"> <input type="submit" value="测试请求方式"> </form>
7、SpringBoot 集成 Redis
Redis是一个中间件: 是一个独立的服务器
Spring、SpringBoot中有 一个RedisTemplate(StringRedisTemplate) ,处理和redis交互
7.1、配置Windows版本的redis
Redis-x64-3.2.100.rar 解压缩到一个 非中文 的目录
- redis-server.exe:服务端, 启动后,不要关闭
- redis-cli.exe:客户端, 访问redis中的数据
RedisDesktopManager 图形界面客户端
7.2、创建工程
创建 SpringBoot 工程:
RedisTemplate 内部使用的是 lettuce 客户端库
<!--redis起步依赖: 直接在项目中使用RedisTemplate(StringRedisTemplate)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
data-redis内部使用的是 lettuce 客户端库
在程序中使用RedisTemplate类的方法 操作redis数据, 实际就是调用的lettuce 客户端的中的方法
application.properties 文件内容:
#端口号
server.port=8080
#上下文路径
server.servlet.context-path=/myredis
#指定 redis 的 host、ip、password
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
7.2.1、使用 RedisTemplate
在模板对象 redisTemplate 的类里面定义了一些对象,每个对象对应着要操作的不同类型的数据:
public class RedisTemplate<K, V>{ //操作 String 类型数据时使用 private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this); //操作 List 类型数据时使用 private final ListOperations<K, V> listOps = new DefaultListOperations(this); private final SetOperations<K, V> setOps = new DefaultSetOperations(this); private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, ObjectHashMapper.getSharedInstance()); private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this); private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this); private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this); private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this); }
然后类中定义了一些方法来获取这些定义好的对象:
public ValueOperations<K, V> opsForValue() {return this.valueOps;} public ZSetOperations<K, V> opsForZSet() {return this.zSetOps;}
简单使用 redisTemplate 向redis中添加、获取 string 类型数据
注意:需要启动 Redis 服务器(如果是Linux版本需要关闭防火墙)
@RestController
public class RedisController {
/**
* 使用 @Resource 注入 RedisTemplate
* 所以这里的属性对象名就定义为 redisTemplate,这样按名称注入才找得到
* RedisTemplate类带泛型,三种写法:
* RedisTemplate<String,String>
* RedisTemplate<Object,Object>
* RedisTemplate
*/
@Resource
private RedisTemplate redisTemplate;
//添加数据到 redis
@PostMapping("/redis/addstring")
public String addToRedis(String name, String value){
//操作redis中的string类型数据,需要先获取 ValueOperations 对象
ValueOperations valueOperations = redisTemplate.opsForValue();
//添加数据到redis
valueOperations.set("name","value");
return "向redis添加String类型数据";
}
//从 redis 获取数据
@GetMapping("/redis/getk")
public String getData(String key){
ValueOperations valueOperations = redisTemplate.opsForValue();
Object v = valueOperations.get(key);
return "key是=="+key+",它的值是=="+v;
}
}
7.2.2、使用 StringRedisTemplate
@RestController
public class RedisController {
@Resource
private StringRedisTemplate stringRedisTemplate;
//添加数据到 redis
@PostMapping("/redis/{k}/{v}")
public String addString(@PathVariable String k,
@PathVariable String v){
//使用 StringRedisTemplate 对象
stringRedisTemplate.opsForValue().set(k,v);
return "成功添加数据";
}
//从 redis 获取数据
@GetMapping("/redis/getk/{k}")
public String getString(@PathVariable String k){
String v = stringRedisTemplate.opsForValue().get(k);
return "成功获取值"+v;
}
}
从客户端查看 redis 数据:
下面是使用了 JDK 的序列化导致的
7.3、对比 StringRedisTemplate、RedisTemplate
-
StringRedisTemplate
把 k、v 都是作为String处理,使用的是String的序列化,可读性好
-
RedisTemplate
把 k、v 经过序列化处理之后存到redis,存进 redis 的是序列化后的内容, 不能直接识别
默认使用的 JDK 序列化, 可以修改为其他的序列化
设置 RedisTemplate 中 key、value 的序列化方式
// 使用RedisTemplate ,在存取值之前,设置序列化
// 设置 key 使用String的序列化
redisTemplate.setKeySerializer( new StringRedisSerializer());
// 设置 value 的序列化
redisTemplate.setValueSerializer( new StringRedisSerializer());
redisTemplate.opsForValue().set(k,v);
方法内部可以放入以下对象,作为序列化方式:
7.4、JSON 序列化
自定义类,使用 idea 自动生成序列化版本号
-
在设置中,勾选此项
-
自定义类,实现 Serializable 接口
@Data @AllArgsConstructor @NoArgsConstructor public class Student implements Serializable { private static final long serialVersionUID = -6932033740285271121L; private Integer id; private String name; private Integer age; }
-
在类名上 Alt + Enter ,生成序列化版本号
序列化:定义方法把 java对象转为 json 存储
@RestController
public class RedisController {
/**
* 使用 @Resource 注入 RedisTemplate
* 所以这里的属性对象名就定义为 redisTemplate,这样按名称注入才找得到
* RedisTemplate类带泛型,三种写法:
* RedisTemplate<String,String>
* RedisTemplate<Object,Object>
* RedisTemplate
*/
@Resource
private RedisTemplate redisTemplate;
/**
* 使用 JSON 序列化,把 java对象转为 json 存储
*/
@PostMapping("/redis/addjson")
public String addJson(){
Student student = new Student(1001,"lisi",20);
redisTemplate.setKeySerializer(new StringRedisSerializer());
//把值设置为 JSON 序列化
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
redisTemplate.opsForValue().set("mystudent",student);
return "成功添加";
}
}
序列化结果:(不正常显示)
反序列化,从 redis 中获取数据
@GetMapping("/redis/getjson")
public String getJson(){
redisTemplate.setKeySerializer(new StringRedisSerializer());
//把值设置为 JSON 序列化
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
Object mystudent = redisTemplate.opsForValue().get("mystudent");
return "成功获取" + mystudent;
}
反序列化结果:(正常显示)
补充:Redis序列化
序列化:把对象转化为可传输的字节序列过程称为序列化
反序列化:把字节序列还原为对象的过程称为反序列化
为什么需要序列化?
序列化最终的目的是为了对象可以跨平台存储、和进行网络传输。而我们进行跨平台存储和网络传输的方式就是 IO,而我们的 IO 支持的数据格式就是字节数组。我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从 IO 流里面读出数据的时候再以这种规则把对象还原回来(反序列化)
什么情况下需要序列化?
通过上面已经知道了凡是需要进行 “跨平台存储” 和 “网络传输” 的数据。都需要进行序列化
本质上存储和网络传输都需要经过把一个对象状态保存成一种跨平台识别的字节格式,然后其他的平台才可以通过字节信息解析还原对象信息
序列化的方式:
序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样,比如现在常见的序列化方式有:
JDK(不支持跨语言)、JSON、 XML、Hessian、 Kryo(不支持跨语言)、Thrift、 Protofbtuff
java的序列化:把java对象转为 byte[] 二进制数据
json 序列化: json序列化功能将对象转换为 JSON 格式或从 JSON 格式转换对象。例如把一个Student对象转换为 JSON字符串 {“name”.“李四”, “age”:29},反序列化是将 JSON 字符串 {“name’”.“李四”, “age’”:29} 转换为Student对象
补充:fastjson
fastjson是阿里巴巴的开源 JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符
串,也可以从JSON字符串反序列化到avaBean。fastjson是json的序列化和反序列化
8、SpringBoot 集成 Dubbo
8.1、公共接口
创建普通 maven 项目
pom.xml
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
实体类
@Data
public class Student implements Serializable {
private static final long serialVersionUID = 7605749485382430133L;
private Integer id;
private String name;
private Integer age;
}
接口
public interface StudentService {
Student queryStudent(Integer id);
}
8.2、提供者
新建 boot 工程,不需要选择任何依赖项
pom.xml
在创建 boot 项目的时候不选择依赖,然后在 pom.xml 文件中手动加入以下依赖
公共接口依赖、 dubbo-starter 依赖、zk 依赖
<!-- 加入公共接口 -->
<dependency>
<groupId>com.afei</groupId>
<artifactId>dubbo-interface-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- ZooKeeper依赖
因为 log4j 的依赖被加载多次,导致不唯一,运行报错。
在 ZooKeeper 的依赖里面排除 log4j 依赖
-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.8</version>
<type>pom</type>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
接口实现类
/**
* 使用 dubbo 中的注解暴露服务
* 同时还可以创建bean对象在spring容器中
* 注意此注解是dubbo2.7.8开始有的
* interfaceClass 这个属性可以省略
*/
//@Component 可以不用加
@DubboService(interfaceClass = StudentService.class,version = "1.0")
public class StudentServiceImpl implements StudentService {
@Override
public Student queryStudent(Integer id) {
Student student = new Student();
student.setId(id);
student.setName("lisi"+id);
student.setAge(id +1);
return student;
}
}
外部化配置
#配置服务名称
spring.application.name=service-provider
#配置扫描的包,扫描@DubboService
dubbo.scan.base-packages=com.service.impl
#配置dubbo协议(使用注册中心可以不配)
#dubbo.protocol.name=dubbo
#dubbo.protocol.port=20880
#注册中心
dubbo.registry.address=zookeeper://localhost:2181
启动类Application
只需要加上 @EnableDubbo 注解
/**
* @EnableDubbo:启用dubbo
* 内部包含 @EnableDubboConfig、@DubboComponentScan
*/
@SpringBootApplication
//@EnableDubboConfig :也可只使用这个注解
@EnableDubbo
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
8.3、消费者
pom.xml
新建 boot 工程,选择 web 依赖项,其他依赖与提供者一致
<!-- 加入公共接口 -->
<dependency>
<groupId>com.afei</groupId>
<artifactId>dubbo-interface-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- dubbo依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.8</version>
</dependency>
<!-- ZooKeeper依赖
因为 log4j 的依赖被加载多次,导致不唯一,运行报错。
在 ZooKeeper 的依赖里面排除 log4j 依赖
-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.8</version>
<type>pom</type>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- web 起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
后端控制器Controller
@RestController
public class DubboController {
/**
* @DubboReference:引用远程服务,把创建好的代理对象,注入给studentService
* interfaceClass 这个属性可以省略
*/
@DubboReference(interfaceClass = StudentService.class,version = "1.0")
private StudentService studentService;
@GetMapping("/query/{id}")
public String query(@PathVariable Integer id){
Student student = studentService.queryStudent(1);
return "获取对象:"+student;
}
}
外部化配置
#配置服务名称
spring.application.name=consumer
#注册中心
dubbo.registry.address=zookeeper://localhost:2181
启动类Application
只需要加上 @EnableDubbo 注解
@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
8.4、启动
-
启动 ZooKeeper 服务,启动dubbo监控配置中心
-
执行提供者的启动类
如果此处已经开启了 zk 服务,但还提示连不上 zk ,可能是默认的超时时间太短了
指定超时时间
dubbo.config-center.timeout=25000
如果连不上 Linux 版本 zk 服务,注意是不是防火墙没关
如果还是连不上zk ,可以尝试使用 Windows 版本的 zk ,此处连不上 Linux 版本的,原因未知
注册中心
dubbo.registry.address=zookeeper://localhost:2181
-
执行消费者的启动类
ZooKeeper 使用的是 Windows 版本的
-
访问地址 :http://localhost:8080/query/10
9、SpringBoot 打包
9.1、 Spring Boot 打包为 war
带有 jsp 页面,打包成 war,运行在其他 Tomcat 上,而不使用自带的 Tomcat
项目准备:
jsp 页面以及后端控制器
创建 webapp 目录,创建 index.jsp 文件
定义 controller 类,如下:
@Controller
public class JspController {
@RequestMapping("/show")
public String show(Model model){
model.addAttribute("data","这就是数据")
return "index";
}
}
配置文件中指定上下文路径、视图解析器
server.servlet.context-path=/myjsp
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
pom.xml
依赖
<!--加入处理 jsp 的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
编译插件指定编译目录
<build>
<!--指定打包后的 war 文件名称-->
<finalName>myweb</finalName>
<!--指定 jsp 文件编译目录-->
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<!--如果使用了mybatis,需要指定 mapper 文件编译目录-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!--一般情况resources目录下自动编译,但是以防万一,还是配一下-->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
发布 war 到 Tomcat
主启动类继承 SpringBootServletInitializer
/**
* SpringBootServletInitializer:启动类继承此类,才可以使用外部的 Tomcat
*/
@SpringBootApplication
public class Demo001WarApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Demo001WarApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Demo001WarApplication.class);
}
}
pom.xml 文件中指定打包方式为 war
<!--指定 packing 为 war-->
<packaging>war</packaging>
maven package 打包
发布打包后的 war 到 tomcat
启动 Tomcat 的 bin 目录下的 startup.bat
注意: 访问地址的上下文路径是 war 包的名称,端口号也是外置的 Tomcat 的端口号
http://localhost:8080/myweb/show
注意: 使用的 Tomcat 服务要与 项目中 依赖的 Tomcat 版本一致
9.2、 Spring Boot 打包为 jar
项目准备:
和上面一样
pom.xml
<dependencies>
<!--加入处理 jsp 的依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<!--指定打包后的 war 文件名称-->
<finalName>myweb</finalName>
<!--指定 jsp 文件编译目录-->
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<!--如果使用了mybatis,需要指定 mapper 文件编译目录-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!--一般情况resources目录下自动编译,但是以防万一,还是配一下-->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
创建 jsp 页面以及 后端控制器
打包为 jar
必须指定 maven 插件版本
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 打包 jar,有jsp文件时,必须指定maven-plugin插件的版本是 1.4.2.RELEASE-->
<version>1.4.2.RELEASE</version>
</plugin>
</plugins>
执行 maven package
打包好之后,到 jar 包所在的目录,启动 cmd 执行命令:java –jar mybootjar.jar
Linux 运行 jar
可以将该命令封装到一个 Linux 的一个 shell 脚本中(上线部署)
-
创建一个文件 run.sh,写入内容如下:
#!/bin/sh
java -jar xxx.jar 此种写法是jar包和文件在同一目录中,如不在同一目录就是用绝对路径
-
赋权限
chmod 777 run.sh
-
执行 sh 文件,注意关闭防火墙
./run.sh 此种写法是当前正在该文件所在目录
10、Themeleaf 模板
使用 Thymeleaf 模板 是使用 html 页面,在最上面加上 以下内容:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
模板是做视图层工作, 显示数据的
Java 生态下 的模板引擎有 Thymeleaf 、Freemaker、Velocity、Beetl(国产) 等
Thymeleaf 是使用java开发的模板技术,在服务器端运行,把处理后的数据发送给浏览器
Thymeleaf 是基于Html语言,Thymleaf语法是应用在 html标签中
SpringBoot框架集成Thymealeaf,使用Thymeleaf代替jsp
10.1、第一个例子
创建 boot 项目,选择 web、thymeleaf 引擎依赖
定义后端控制器
@Controller
public class HelloController {
@GetMapping("/hello")
public String hello(HttpServletRequest request){
//添加数据到request作用域
request.setAttribute("data","使用模板引擎");
//指定视图
return "show";
}
}
创建网页页面,在 resources / templates 目录下
<h3>例子</h3>
<p>想显示数据</p>
此时没有配置视图解析器也可以访问到 show.html
添加表达式:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<h3>例子</h3>
<p th:text="${data}">想显示数据</p>
此时经过 springboot 处理之后显示:
如果直接在 show.html 所在的目录中,将此网页页面文件拖进浏览器执行,只会显示 “ 想显示数据 ”,不经过 boot 处理,不会显示 ${data} 的内容,因为此数据是来自于后端控制器
10.2、常用设置
上面没有配置设置就可以访问到逻辑视图i,是因为模板内部的默认设置
#设置缓存,开发阶段关闭模板缓存false,让修改立即生效。上线后设置为 true
spring.thymeleaf.cache=false
#编码格式
spring.thymeleaf.encoding=UTF-8
# 模版的类型(默认是HTML)
spring.thymeleaf.mode=HTML
# 模版的前缀(默认是classpath:/templates/)
spring.thymeleaf.prefix=classpath:/templates/
# 后缀
spring.thymeleaf.suffix=.html
10.3、表达式
注意:th:text=“” 是 Thymeleaf 的一个属性,用于文本的显示
<html lang="en" xmlns:th="http://www.thymeleaf.org">
10.3.1、标准变量表达式
语法: ${key}
作用: 获取key对于的文本数据, key 是Request作用域中的key
request.setAttribute()、model.addAttribute()
在页面中的 html标签中, 使用 th:text=“${key}” 来获取Request 作用域中的数据
后端控制器:
@GetMapping("/expression1")
public String expresssion(Model model){
//添加简单类型的数据
model.addAttribute("site","www.afei.com");
//对象类型
model.addAttribute("myuser", new SysUser(1001,"李思","f",20));
return "show";
}
页面:
<p>获取普通文本</p>
<p th:text="${site}">想显示数据</p>
<p>获取对象的属性</p>
<p th:text="${myuser.id}">id</p>
<p th:text="${myuser.getName()}">姓名</p>
<p th:text="${myuser.getSex()}">性别</p>
<p th:text="${myuser.age}">年龄</p>
如果不写,显示静态资源
<p>想显示数据</p>
写了,但是没有使用 boot 去访问,直接使用浏览器访问文件,显示静态资源
<p th:text="${site}">想显示数据</p>
写了,但是写错了,访问不到,则啥也不显示
<p th:text="${s121212}">想显示数据</p>
10.3.2、选择变量表达式( 星号变量表达式)
语法: *{key}
作用: 获取这个key对应的数据, *{key}需要和 th:object 这个属性一起使用。
目的是简单获取对象的属性值
-
和 th:object 这个属性一起使用
<div th:object="${myuser}"> <p th:text="*{id}">id</p> <p th:text="*{getName()}">姓名</p> <p th:text="*{getSex()}">性别</p> <p th:text="*{age}">年龄</p> </div>
-
单独使用,类似于标准表达式
<p th:text="${myuser.id}">id</p> <p th:text="${myuser.getName()}">姓名</p>
10.3.3、链接表达式
语法: @{url}
作用: 表示链接, 可用于以下:
<script src="...">、<link href="...">、<a href="...">、<form action="...">、<img src="">
创建两个测试链接
@GetMapping("/query")
@ResponseBody
public String query(Integer id){
return "query,参数为:"+id;
}
@GetMapping("/select")
@ResponseBody
public String select(Integer id, String name){
return "select,参数:id="+id +";name="+name;
}
页面:
${userId} 获取的是 request 作用域中的数据
<h3>链接绝对路径</h3>
<a th:href="@{http://www.baidu.com}">百度</a>
<h3>链接相对路径</h3>
<a th:href="@{/tpl/query}">没有传递参数</a>
<h3>链接相对路径</h3>
<a th:href="@{'/tpl/query?id='+${userId}}">使用字符串链接传参数</a>
<p>链接到相对地址,推荐方式</p>
<a th:href="@{/tpl/query(id=${userId})}">使用小括号传递参数</a>
<a th:href="@{/tpl/select(id=${userId},name=${userName})}">使用小括号传递参数</a>
10.4、属性
大部分属性和 html 的一样,只不过前面加了一个 th 前缀,属性的作用不变。
加入上th, 属性的值由模板引擎处理了, 在属性可以使用变量表达式
例如:
<!--此处相对路径是相对于工程路径 http://localhost:8080/mytest/ -->
<form action="tpl/query" method="post"></form> 原先
<!--此处需要使用 / 代表工程路径 http://localhost:8080/mytest/ -->
<form th:action="@{/tpl/query}" th:method="${methodAttr}"></form> 模板引擎
jQuery
注意:
这里的 jQuery 在 target 文件中没有出现,将项目 clean 之后,重新启动,就可以正常使用了
Controller类名上有RequestMapping(“/aa”),src中要写两个"…"
<head>
<!--这里根目录代表了 resources/static 目录-->
<script type="text/javascript" th:src="../js/jquery-1.7.2.js"></script>
<script type="text/javascript">
function clickFun() {
alert("你点了一下?");
}
$(function () {
$("#btn").click(function () {
alert("jquery");
})
})
</script>
</head>
<body>
<form th:action="@{/tpl/query}" th:method="${methodAttr}">
<input th:type="text" th:id="username" th:name="${paramName}" th:value="${paramValue}"/>
<input type="button" th:id="btn" th:onclick="clickFun()" value="按钮 click" />
</form>
th:src 用于外部资源引入,比如<script>标签的 src 属性,<img>标签的 src 属性,常与@{}表达式结合使用
在 SpringBoot 项目的静态资源都放到 resources 的 static 目录下
放到 static 路径下的内容,写路径时不需要写上 static
<script type="text/javascript" th:src="@{/js/jquery-3.4.1.js}"></script>
th:each 遍历
此属性既可以循环遍历集合,也可以循环遍历数组及 Map
语法:
在一个html标签中,使用th:each
<div th:each="自定义循环成员名, 自定义循环的状态变量:${key}">
<p th:text="${自定义循环成员名.属性名}" ></p>
</div>
集合循环成员,循环的状态变量:两个名称都是自定义的
“循环的状态变量” 这个名称可以不定义,默认是"集合循环成员Stat"
1、循环 List
示例:
<div th:each="user,userIter:${myuser}">
<p th:text="${user.name}"/>
<p th:text="${user.age}"/>
</div>
循环的是 th:each 所在的一整个标签
interStat 是循环体的信息,通过该变量可以获取如下信息:
- index:当前迭代对象的 index(从 0 开始计算)
- count:当前迭代对象的个数(从 1 开始计算)这两个用的较多
- size:被迭代对象的大小
- current:当前迭代变量,直接获取到当前对象,相当于 ${user} ,后可跟属性名
- even/odd:布尔值,当前循环索引是否是偶数/奇数(从 0 开始计算)
- first:布尔值,当前循环是否是第一个
- last:布尔值,当前循环是否是最后一个
<div th:each="user,userIter:${myuser}">
<p th:text="${userIter.index}"/>
<p th:text="${userIter.current.name}"/> 相当于 ${user.name}
<p th:text="${user.name}"/>
<p th:text="${user.age}"/>
</div>
注意: 循环体信息 interStat 也可以不定义,则默认采用迭代变量加上 Stat 后缀,即 userStat
“循环的状态变量” 可以省略,默认是"自定义循环成员名Stat",如下:
<tr th:each="user:${myuser}">
<p th:text="${userStat.index}"/>
<p th:text="${userStat.current.name}"/>
<td th:text="${user.age}"/>
</tr>
2、遍历数组 Array
和循环 List 一模一样
<div th:each="自定义循环成员名, 自定义循环的状态变量:${key}">
<p th:text="${自定义循环成员名.属性名}" ></p>
</div>
“循环的状态变量” 这个名称可以省略,默认是 "自定义循环成员名Stat"
@GetMapping("/expression1")
public String expresssion(Model model){
SysUser[] users = new SysUser[3];
users[0]= new SysUser(1001,"马超","m",22);
users[1]= new SysUser(1002,"黄忠","m",26);
users[2]= new SysUser(1003,"赵云","m",29);
model.addAttribute("myuser",users);
return "show";
}
3、遍历 map
语法:
<div th:each="集合循环成员,循环的状态变量:${key}">
<p th:text="${集合循环成员.key}" ></p>
<p th:text="${集合循环成员.value}" ></p>
</div>
集合循环成员,循环的状态变量:两个名称都是自定义的
“循环的状态变量”这个名称可以不定义,默认是"集合循环成员Stat"
key:map集合中的key
value:map集合key对应的value值
@GetMapping("/expression1")
public String expresssion(Model model){
Map<String,SysUser> map = new HashMap<>();
map.put("user1",new SysUser(1001,"马超","m",22));
map.put("user2",new SysUser(1002,"黄忠","m",26));
map.put("user3",new SysUser(1003,"赵云","m",29));
model.addAttribute("mymap",map);
return "show";
}
示例:
<div th:each="map,userIter:${mymap}">
<p th:text="${map.key}"/>
<!--当value是对象时,直接输出就是调用对象的toString方法-->
<p th:text="${map.value}"/>
<p th:text="${map.value.name}"/>
</div>
注意:
当value是对象时,直接输出就是调用对象的toString方法
4、遍历嵌套 List-Map
遍历 List<Map<SysUser>
示例:
@GetMapping("/expression1")
public String expresssion(Model model){
Map<String,SysUser> map = new HashMap<>();
map.put("user1",new SysUser(1001,"马超","m",22));
map.put("user2",new SysUser(1002,"黄忠","m",26));
map.put("user3",new SysUser(1003,"赵云","m",29));
List<Map<String,SysUser>> listmap = new ArrayList<>();
listmap.add(map);
map = new HashMap<>();
map.put("sys1",new SysUser(2001,"曹操","m",22));
map.put("sys2",new SysUser(2002,"孙权","m",26));
map.put("sys3",new SysUser(2003,"刘备","m",29));
listmap.add(map);
model.addAttribute("mylistmap",listmap);
return "show";
}
<div th:each="list:${mylistmap}">
<div th:each="map:${list}">
<p th:text="${map.key}"/>
<p th:text="${map.value.name}"/>
</div>
</div>
th:if 条件判断
当判断成立时,显示标签内容
“th:if” : 判断语句, 当条件为true, 显示html标签体内容, 反之不显示。没有else语句
语法:
<div th:if="10 > 0"> 当条件为true显示文本内容 </div>
还有一个 th:unless ,和 th:if 相反的行为
语法:
<div th:unless="10 < 0"> 当条件为false显示标签体内容 </div>
示例: th:if
当条件为 true, 显示html标签体内容
""空字符是true、null是false
@GetMapping("/expression1")
public String expresssion(Model model){
model.addAttribute("sex","m");
model.addAttribute("isLogin",true);
model.addAttribute("age",20);
model.addAttribute("name","");
model.addAttribute("isnull",null);
return "show";
}
<p th:if="${sex=='m'}">性别是男</p>
<p th:if="${isLogin}">已经登录系统</p>
<p th:if="${age > 20}">年龄大于20</p>
<!--""空字符是true-->
<p th:if="${name}">name是“”</p>
<!--null是false-->
<p th:if="${isnull}"> isnull是null</p>
示例: th:unless
当条件件为 false 显示标签体内容
<h3>unless: 判断条件为false,显示标签体内容</h3>
<p th:unless="${sex=='f'}">性别是男的</p>
<p th:unless="${isLogin}">登录系统</p>
<p th:unless="${isnull}"> isOld是null </p>
th:switch 判断语句
类似 java 中的 switch,case
当判断成立时,显示标签内容
语法:
<div th:switch="要比对的值">
<p th:case="值1">
结果1
</p>
<p th:case="值2">
结果2
</p>
<p th:case="*">
默认结果,当以上语句都不匹配,就执行此句
</p>
以上的case只有一个语句执行
</div>
一旦某个 case 判断值为 true,剩余的 case 则都当做 false
“ * ” 表示默认的 case,前面的 case 都不匹配时候,执行默认的 case
示例:
<div th:switch="${sex}">
<p th:case="m"> 显示男 </p>
<p th:case="f"> 显示女 </p>
<p th:case="*"> 未知 </p>
</div>
th:inline
th:inline 有三个取值类型 (text, javascript 和 none)
1、内联text
可以在html标签外,获取表达式的值
可以让 Thymeleaf 表达式不依赖于 html 标签,直接使用内敛表达式 [[表达式]] 即可获取动态数据
注意:父级标签上的 th:inline = “text” 属性可以省略
语法:
<p>显示姓名是:[[${key}]]</p>
示例:
<!--之前使用 th:text-->
姓名是<span th:text="${name}"/>
<!--使用 th:inline-->
<div th:inline="text">
<p>姓名是[[${name}]],年龄是[[${isLogin}]]</p>
</div>
<!--父标签的 th:inline 可以省略-->
<div>
<p>姓名是[[${name}]],年龄是[[${isLogin}]]</p>
</div>
2、内联javascript
可以在 js 中,获取模版中的数据
示例:
<button onclick="fun()">单击按钮</button>
<script type="text/javascript" th:inline="javascript">
var name = [[${myuser.name}]];
var id = [[${myuser.id}]];
function fun() {
alert("用户名是"+name+",他的 id 是"+id);
}
</script>
10.5、字面量
-
文本字面量
用单引号’…'包围的字符串为文本字面量
使用 + 将文本和变量表达式连接在一起
<h3>文本字面量: 使用单引号括起来的字符串</h3> <p th:text="'姓名是'+${name}+',用户登录'+${isLogin}"></p>
-
数字字面量
<h3>数字字面量</h3> <p th:if="${20>5}"> 20大于 5</p>
-
boolean 字面量
<h3>boolean字面量</h3> <p th:if="${isLogin == true}">用户已经登录系统</p>
-
null 字面量
<h3>null字面量</h3> <p th:if="${myuser != null}">有myuser数据</p>
10.6、字符串连接
连接字符串有两种语法
1) 语法使用 单引号括起来字符串 , 使用 + 连接其他的 字符串或者表达式
<p th:text="'我是'+${name}+',我所在的城市'+${city}">数据显示</p>
2)语法:使用双竖线, |字符串和表达式|
<p th:text="|我是${name},我所在城市${city|">
显示数据
</p>
示例:
<h3>字符串连接方式1:使用单引号括起来的字符串</h3>
<p th:text="'我是'+${name}+',我所在的城市'+${city}">数据显示</p>
<h3>字符串连接方式2:|字符串和表达式|</h3>
<p th:text="|我是${name},所在城市${city},其他人${myuser.name}|"></p>
10.7、运算符
运算符有如下:
算术运算 : + , - , * , / , %
关系比较 : > , < , >= , <= ( gt , lt , ge , le )
相等判断 : == , != ( eq , ne )
示例:
<p th:text="${age < 10}">显示运算结果,显示true或false</p>
<p th:text="${ 20 + 30 }">显示运算结果</p>
<p th:if="${myuser == null}">myuser是null</p>
<p th:if="${myuser eq null}">myuser是null</p>
<p th:if="${myuser ne null}">条件不成立就不会显示标签</p>
<p th:text="${isLogin == true ? '用户已经登录' : '用户需要登录'}"></p>
<p th:text="${isLogin == true ?
( age > 10 ? '用户是大于10的' : '用户年龄比较小') : '用户需要登录'}"></p>
10.8、内置对象
文档地址:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#web-context-namespaces-for-requestsession-attributes-etc.
文档还有其他内置对象
#request
表示 HttpServletRequest
#session
表示 HttpSession对象
session
是#session的简单表示方式,表示Map对象的,用来获取session中指定的key的值
#session.getAttribute(“loginname”) 相当于 session.loginname
param
表示请求的参数集合
这些是内置对象,可以在模板文件中直接使用
示例:
获取作用域中的数据
获取作用域中的数据
<!--此处不会显示标签内容,只显示表达式内容-->
<p th:text="${#request.getAttribute('reqdata')}"> request 作用域</p>
<p th:text="${#session.getAttribute('sessdata')}"/>
<p th:text="${session.loginname}"/>
使用内置对象的方法
使用内置对象的方法
getRequestURL====<span th:text="${#request.getRequestURL()}"></span><br/>
getRequestURI====<span th:text="${#request.getRequestURI()}"></span><br/>
<!-- getQueryString()显示参数字符串 -->
getQueryString====<span th:text="${#request.getQueryString()}"></span><br/>
getContextPath====<span th:text="${#request.getContextPath()}"></span><br/>
getServerName====<span th:text="${#request.getServerName()}"></span><br/>
getServerPort====<span th:text="${#request.getServerPort()}"></span><br/>
getServletPath====<span th:text="${#request.getServletPath()}"></span><br/>
当访问地址 http://localhost:8080/mytest/tpl/expression1?name=lisi&age=12 时,数据如下:
getId====<span th:text="${#session.getId()}"></span>
获取请求参数的信息
获取请求参数name的值,输出 lisi
<span th:text="${param.name}"/>
获取请求参数个数,输出 2
<span th:text="${param.size()}"/>
10.9、内置工具类
内置工具类型: Thymeleaf自己的一些类,提供对string, date ,集合的一些处理方法
#dates
:处理日器的工具类,是java.util.Date 对象
#numbers
:处理数字的,格式化数字对象
#strings
:字符串对象的实用方法: contains, startsWith, prepending/appending 等
#lists
:处理list集合的
示例:
model.addAttribute("mydate",new Date());
日期格式化,默认格式<p th:text="${#dates.format(mydate)}"/>
日期格式化,指定格式<p th:text="${#dates.format(mydate,'yyyy-MM-dd')}"/>
日期格式化,指定格式<p th:text="${#dates.format(mydate,'yyyy-MM-dd HH:mm:ss')}"/>
显示年份数字<p th:text="${#dates.year(mydate)}"/>
显示月份数字<p th:text="${#dates.month(mydate)}"/>
月份中文<p th:text="${#dates.monthName(mydate)}"/>
此刻时间<p th:text="${#dates.createNow()}" />
model.addAttribute("mynum",26.695);
数字货币化,四舍五入两位小数<p th:text="${#numbers.formatCurrency(mynum)}"/>
整数5位长度,不够就补0,四舍五入两位小数<p th:text="${#numbers.formatDecimal(mynum,5,2)}"/>
model.addAttribute("mystr","hello");
转换为大写<p th:text="${#strings.toUpperCase(mystr)}"/>
显示字符串的索引位置,从0开始<p th:text="${#strings.indexOf(mystr,'lo')}"/>
截取指定索引,[2,5),从0开始<p th:text="${#strings.substring(mystr,2,5)}"/>
从指定索引开始截取,从0开始<p th:text="${#strings.substring(mystr,2)}"/>
拼接字符串<p th:text="${#strings.concat(mystr,'java')}"/>
显示指定字符串长度<p th:text="${#strings.length(mystr)}"/>
显示指定字符串长度<p th:text="${#strings.length('afei')}"/>
判空<p th:unless="${#strings.isEmpty(mystr)}"> mystring不是空字符串</p>
List<String> list = Arrays.asList("a","b","c");
显示集合成员个数<p th:text="${#lists.size(mylist)}"/>
判断是否存在此成员<p th:if="${#lists.contains(mylist,'a')}">有成员a</p>
判空<p th:if="!${#lists.isEmpty(mylist)}"> list 集合有多个成员</p>
处理 null
zoo.setDog(new Dog());
<p th:text="${zoo.dog.name}"/>
如果dog对象没有设置 name 属性,则以上句子会抛出空指针异常,为了解决这种 null 的情况,使用如下:
<p th:text="${zoo.dog?.name}"/>
加了问号之后,会判断 dog 是否为null,若是null,就不去获取name,避免空指针,若不为null,才去取name值
其他:
#calendars: 和 dates 类似, 但是 java.util.Calendar 对象
#objects:对 objects 操作的实用方法
#bools:对布尔值求值的实用方法
#arrays:数组的实用方法
#sets: set 的实用方法
#maps:map 的实用方法
#aggregates:对数组或集合创建聚合的实用方法
10.10、自定义模板
模板是内容复用, 定义一次,在其他的模板文件中多次使用。
模板使用:
1.定义模板
2.使用模板
模板定义语法:
th:fragment="模板自定义名称"
例如:
<div th:fragment="head">
<p>
阿菲你好
</p>
<p>
hello,afei
</p>
</div>
引用模板语法:
1) ~{templatename :: selector}
templatename: 文件名称
selector: 自定义模板名称
2)templatename :: selector
templatename: 文件名称
selector: 自定义模板名称
对于使用模板:有包含模板(th:include), 插入模板(th:insert)
示例:
head.html
<div th:fragment="top">
<p>模板内容</p>
</div>
show.html
<!--源码会保留div标签-->
<div th:insert="~{head :: top}">
插入模板
</div>
<!--源码直接替换整个标签-->
<div th:include="head :: top">
包含模板
</div>
注意,两个文件在同一目录下
可以引用子目录中的文件
<div th:include="common/cus :: html"/>
可以引用整个 html 页面作为模板
foot.html页面:
<div>
footer
hello,afei
</div>
show.html页面:
<div th:include="footer :: html"/>
<div th:include="footer"/>
<div th:insert="footer :: html"/>
<div th:insert="footer"/>
11、总结
创建对象的:
@Controller: 放在类的上面,创建控制器对象,注入到容器中。
@RestController: 放在类的上面,创建控制器对象,注入到容器中。
作用:是复合注解@Controller+@ResponseBody, 使用这个注解类的,里面的控制器方法的
返回值都是数据
@Service : 放在业务层的实现类上面,创建service对象,注入到容器。
@Repository : 放在dao层的实现类上面,创建dao对象,放入到容器。
没有使用这个注解,是因为现在使用MyBatis框架,dao对象是MyBatis通过代理生成的
不需要使用@Repository,所以没有使用
@Component: 放在类的上面,创建此类的对象,放入到容器中。
赋值的:
@Value : 简单类型的赋值,例如 在属性的上面使用 @Value("李四") private String name
还可以使用@Value,获取配置文件者的数据(properties或yml)
@Value("${server.port}") private Integer port
@Autowired: 引用类型赋值自动注入的,支持byName, byType, 默认是byType。
放在属性的上面,也可以放在构造方法的上面,推荐是放在构造方法的上面
@Qualifer: 给引用类型赋值,使用byName方式。
@Autowird, @Qualifer都是Spring框架提供的。
@Resource : 来自jdk中的定义, javax.annotation。 实现引用类型的自动注入, 支持byName,byType
默认是byName, 如果byName失败,再使用byType注入。在属性上面使用
其他:
@Configuration : 放在类的上面,表示这是个配置类,相当于xml配置文件
@Bean:放在方法的上面,把方法的返回值对象,注入到spring容器中。
@ImportResource : 加载其他的xml配置文件,把文件中的对象注入到spring容器中
@PropertySource : 读取其他的properties属性配置文件
@ComponentScan: 扫描器,指定包名,扫描注解的
@ResponseBody: 放在方法的上面,表示方法的返回值是数据, 不是视图
@RequestBody : 把请求体中的数据,读取出来,转为java对象使用
用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的)
@ControllerAdvice: 控制器增强,放在类的上面,表示此类提供了方法,可以对controller增强功能
@ExceptionHandler : 处理异常的,放在方法的上面
@Transcational : 处理事务的,放在service实现类的public方法上面,表示此方法有事务
SpringBoot中使用的注解:
@SpringBootApplication : 放在启动类上面,包含了@SpringBootConfiguration、
@EnableAutoConfiguration、 @ComponentScan
MyBatis相关的注解:
@Mapper : 放在类的上面,让MyBatis找到接口,创建他的代理对象
@MapperScan :放在主类的上面,指定扫描的包,把这个包中的所有接口都创建代理对象,对象注入到容器中
@Param : 放在dao接口的方法的形参前面,作为命名参数使用的
Dubbo注解:
@DubboService: 在提供者端使用的,暴露服务的,放在接口的实现类上面
@DubboReference: 在消费者端使用的,引用远程服务,放在属性上面使用。
@EnableDubbo : 放在主类上面,表示当前引用启用Dubbo功能
12、开发小技巧
12.1、Lombok
安装lombok插件
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
12.2、dev-tools热部署
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
项目或者页面修改以后:Ctrl+F9;
如果快捷键无效,执行maven的compile指令
练习
使用的技术: SpringBoot ,Dubbo, Redis, MyBatis
Student表:
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`phone` varchar(11) COLLATE utf8_bin DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
- 注册学生
phone必须唯一, 如果已经存在了手机号, 注册失败的。
int addStudent(Student student);
返回值:int
1: 注册成功
2 : 手机号已经存在
name至少两个字符,
age 必须 大于 0
2) 查询学生,根据id查询,此学生。
先到redis查询学生, 如果redis没有此学生,从数据库查询, 把查询到的学生放入到redis。
后面再次查询这个学生应该从redis就能获取到。
Student queryStudent(Integer id);
- 使用Dubbo框架, addStudent, queryStudent 是有服务提供者实现的。
消费者可以是一个Controller , 调用提供者的两个方法。 实现注册和查询。
4)页面使用html和ajax,jquery。
在html页面中提供 form 注册学生, 提供文本框输入id,进行查询。
注册和查询都使用ajax技术。
html,jquery.js都放到resources/static目录中