路线
javase: OOPmysql:持久化
html+css+js+jquery+框架:视图,框架不熟练,css不好;javaweb:独立开发MVC三层架构的网站了∶原始
ssm :框架:简化了我们的开发流程,配置也开始较为复杂;
war: tomcat运行
spring再简化: SpringBoot - jar:内嵌tomcat;微服务架构!服务越来越多: springcloud;
什么是SpringBoot?
什么是Spring
Spring是一个开源框架,2003年兴起的一个轻量级的Java开发框架,作者: Rod Johnson 。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
Spring是如何简化Java开发的
为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
1、基于POJO的轻量级和最小侵入性编程;个
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模版减少样式代码;
SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can "just run",能迅速的开发web应用,几行代码开发一个http接口。
所有的技术框架的发展似乎都遵循了一条主线规律:从个复杂应用场景衍生一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
是的这就是Java企业级应用->J2EE-> spring-> springboot的过程。
随着Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用Spring、更容易的集成各种常用的中间件、开源软件;
Spring Boot基于Spring 开发,Spirng Boot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,它并不是用来替代Spring 的解决方案,而是和Spring框架紧密结合用于提升Spring 开发者体验的工具。SpringBoot以约定大于配置的核心思想,默认帮我们进行了很多设置,多数Spring Boot应用只需要很少的Spring 配置。同时它集成了大量常用的第三方库配置(例如Redis、MongoDB、Jpa、
RabbitMQ、Quartz等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用,Spring Boot出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot已经当之无愧成为Java领域最热门的技术。
Spring Boot的主要优点:
·为所有Spring开发者更快的入门
·开箱即用,提供各种默认配置来简化项目配置·内嵌式容器简化Web项目
·没有冗余代码生成和XML配置的要求
什么是微服务架构?
MVC、MVVM、
微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成―系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构。
所谓单体应用架构(all in one)是指,我们将一个应用的中的所有应用服务都封装在一个应用中。
无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。
·这样做的好处是,易于开发和测试;也十分方便部署;当需扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
·单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
这样做的好处是:
1.节省了调用资源。
2.每个功能元素的服务都是一个可替换的、可独立升级的软件代码。
如何构建微服务
一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了一个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。
但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:
·构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用;.大型分布式网络服务的调用,这部分由spring cloud来完成,实现分布式;
·在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow。. spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案。
快速入门和基础
创建项目
官方提供了一个快速构建项目的网站: Idea也集成了
https://start.spring.io/
创建项目可勾选依赖,web的依赖勾选上。官网下载zip解压后idea中项目中import module导入pom文件或导入根目录选maven即可然后pom文件中右键add as maven项目即可。或idea在项目中直接创建一个spring initoliar相同方式,选maven和jar和java版本springboot版本和依赖。
写代码
在引导类同级目录下创建包写代码
启动
直接运行引导类即可
Tomcat started on port(s): 8080 (http) with context path '' 访问8080即可,且默认无项目虚拟路径。
springboot项目内置了tomcat可独立运行:
或者使用maven的打包后,找到该jar包的根目录,然后敲cmd,然后使用java -jar 运行即可,如下:
C:\Users\kongdeyi\Desktop\IdeaProject666\spring-boot\quickstart\target>java -jar .\quickstart-0.0.1-SNAPSHOT.jar
引导类和pom文件
引导类本身就是一个bean ,@SpringBootApplication中有@Config注解。
<parent>有一个父项目 ,gav坐标(grout、artifict,verson),spring-boot-starter依赖为启动器
所有的springboot的依赖都是以spring-boot-starter-开头的。
. dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring MVC构建Web (包括RESTful)
应用程序的入门者,使用Tomcat作为默认嵌入式容器。集成了tomcat.dispatcherServLet, xmL......; spring-boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用我们将在后面逐步展开。
o build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。
application配置文件
properties和yml和yaml都可
改端口号
server.prot
改banner
resource根目录下键banner.txt文件,复制下面网站选择好看的bannercopy后粘贴即可。Ascii艺术字实现个性化Spring Boot启动banner图案,轻松修改更换banner.txt文件内容,收集了丰富的banner艺术字和图,并且支持中文banner下载,让你的banner好玩儿更有意思。-bootschool.net
1、spring-boot-dependencies核心依赖在父工程中,这就是在引入很多依赖时为什么不用写版本号。
2、pom的parent(spring-boot-starter-parent)点进去,里面还有一个parent(spring-boot-dependencies)再点进去,这里管理了大量的jar包版本。
3、spring-boot-starter-paren里面配置了资源过滤application.yml. . .还有很多东西
pom中的springboot启动器 如spring-boot-start,可自己去写。说白了就是Springboot的启动场景;. springboot会将所有的功能场景,都变成一个个的启动器。我们要使用什么功能,就只需要找到对应的启动器就可以了。可参考下面网址的start启动器:
https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/using-spring-boot.html#using-boot-starter
自动装配原理
启动类
@SpringBootApplication点进去,除了@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited四个标准注解外还有@ComponentScan扫描的注解外,就剩下了两个核心组合注解:@SpringBootConfiguration @EnableAutoConfiguration。
对于@SpringBootConfiguration注解中有一个@Configuration,说明引导类为配置类,点进去@Configuration,只有一个@Component,说明引导类也是一个组件。
对于@EnableAutoConfiguration,见名知意自动配置。点进去,发现除了4个元注解外,有@AutoConfigurationPackage自动配置包 和@Import({AutoConfigurationImportSelector.class})导入选择器 两个注解。
其中@AutoConfigurationPackage中点进去有个@Import({Registrar.class})导入自动注册配置包。
其中@Import({AutoConfigurationImportSelector.class})才是核心,我们跟进参数中的AutoConfigurationImportSelector.class自动导入选择器类跟进,有个selectImports选择组件的方法,这里加载了一些元数据。
往下还有个getAutoConfigurationEntry获得自动配置实体方法中有行代码为:
List<String> configurations = this.getCandidateConfigurations(annotationMetad ata, attributes); //这个getCandidateConfigurations为获取候选的配置,
我们跟进这个getCandidateConfigurations方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //可在左侧依赖中,找到Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.4中META-INF中的spring-factories中搜索WebMvcAutoConfiguration后跟进这个类,里面有很多配置,还能找到如WebMvcProperties跟进这个类 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; //标柱了的类,就可获取其类下面的所有配置,比如引导类。 }
其中的loadFactoryNames方法,获取所有的加载配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoader == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
其中又有个loadSpringFactories方法位于同Java文件下,下面三行代码的位置,这个方法有个 classLoader.getResources("META-INF/spring.factories");来获取系统资源和项目资源。然后使用了一个while(urls.hasMoreElements()) {}从这些资源中遍历了所有的nextElement元素,自动配置,遍历完后封装为Properties供我们使用。
可在左侧依赖中,找到Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.4中META-INF中的spring-factories中搜索WebMvcAutoConfiguration后跟进这个类,里面有很多配置,还能找到如WebMvcProperties跟进这个类
思考:这么多自动配置为什么有的没有生效,需要导入对应的start才能有作用?
因为spring-factories中的配置的这些类中基本都使用@ConditionalOnXXX来判断,判断条件成立才会加载这个类,才会生效:详见https://www.cnblogs.com/zjdxr-up/p/15685923.html
package org.springframework.boot.autoconfigure.condition
@ConditionalOnBean: 当容器中有指定的Bean的条件下
@ConditionalOnClass:当类路径下有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean:当容器中没有指定Bean的情况下
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径下是否有指定的资源
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean
@ConditionalOnWebApplication:当前项目是Web项目的条件下
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效;我们可以通过启用debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类
debug=true然后启动项目,就会打印出三种:
Positive matches:代表已经启用且生效的
Negative matches:代表没有匹配成功的类没生效的 可能没引依赖
Unconditional classes:代表没有条件的
结论
springboot所有自动配置都是在启动的时候扫描并加载: spring. factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
- springboot在启动的时候,从类路径下/META-INF/ spring.factories获取指定的值;
- 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置!
- 以前我们需要自动配置的东西,现在springboot帮我们做了!
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在非常多的xxxAutoConfiguration的文件(内部很多的方法加了@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration, JavaConfig!
- 有了自动配置类,免去了我们手动编写配置文件的工作!
这就是自动装配的原理!
1) . SpringBoot启动会加载大量的自动配置类
2)、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件xxxxProperties:封装配置文件中相关属性;
主启动类怎么运行?
@SpringBootApplication
public class QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(QuickstartApplication.class, args);
}
}
SpringApplication
这个类主要做了以下四件事情
1.推断应用的类型是普通的项目还是Web项目在其有参构造方法中判断改项目是不是web项目。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
2.查找并加载所有可用初始化器,设置到initializers属性中
3.找出所有的应用程序监听器,设置到listeners属性中
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = Collections.emptySet(); this.isCustomEnvironment = false; this.lazyInitialization = false; this.applicationContextFactory = ApplicationContextFactory.DEFAULT; this.applicationStartup = ApplicationStartup.DEFAULT; this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories(); this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }
4.推断并设置main方法的定义类,找到运行的主类
可参考下面博客的springboot启动流程图
springboot启动流程源码解析(带流程图)_springboot启动流程图_wuweixianzheng的博客-CSDN博客
yaml语法讲解
springboot的所有配置可参考下面官网:
Spring Boot Reference Guide
之前:
标记语言
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xmlyaml配置:server: prot: 8080 server: servlet: context-path: /spring-hello #需改掉默认/的虚拟目录
xml配置:
<server> <port>8081<port> </server>
创建application.yml文件
官方推荐yml而不是properties,且properties只能保存键值对而且还容易乱码,我们删除application.properties,resource下新建application.yml文件
yml语法
可参见SpringBoot基础认识_阳光明媚UPUP的博客-CSDN博客
key冒号 空格value ,层级缩进为点的作用。
# 自定义yml属性
country: 英国
birthday: 2002-11-15
user:
name: 王六
age: 22
user2:{name: "张三",age: 22} #行内写法
# 单个数组
fruit:
- apple
- banana
maps: {k1: v1, k2: v2}
# 单个数组
fruit2: [apple,banana,pain apple]
#对象数组
userList:
- name: zhangsan
age: 18
- name: lisi
age 17
#对象数组
userlist3: [{name:zhangsan,age:18},{name:lisi,age:17}]
#对象数组
userList2:
-
name: zhangsan
age: 18
-
name: lisi
age 17
yml中写数据的作用?
可以给实体类赋值
@ConfigurationProperties赋值yml给类
可参考SpringBoot基础认识_阳光明媚UPUP的博客-CSDN博客
1、通过注解对一个实体类有set方法的属性映射
application.yml
person:
name: 张三
age: 18
happy: yes
birthday: 2021/02/01
maps: {k1: v1, k2: v2}
lists: [hello,hi,helloworld]
dog:
name: wangwang
age: 3
Person.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "person") //加上这个注解,就会把yml中person打头的配置数据直接和提供set方法的属性对号入座,spring容器中的该bean就是属性有值的了
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
测试用例
@SpringBootTest
class QuickstartApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(dog);
System.out.println(person);
}
}
所以,我们看springboot的自动装配如jdbc.DataSourceAutoConfiguration这个类中就有@EnableConfigurationProperties({DataSourceProperties.class}),继续跟进DataSourceProperties.class就有@ConfigurationProperties( prefix = "spring.datasource" )
所以我们在yml中配置的以spring-datasource打头的一些数据,就能够被这个文件读取并set方式注入给其属性。
1、这在properties文件的赋值需要使用@PropertySource("classpath:application.properties")
2、无论是yml还是properties文件中的数据,在实体类中通过@Value("${name}")这种el表达式可获得
yml还可使用一些占位符
person:
id: ${random.uuid}
random: ${random.int}
hello: ${person.name}
name: 张三
age: 18
happy: yes
birthday: 2021/02/01
maps: {k1: v1, k2: v2}
lists: [hello,hi,helloworld]
dog:
name: ${person.hello:默认值}后面的值 #如果没有person.hello这个配置就会使用默认值 ,结果就是默认值后面的值
age: 3
对比@ConfigurationProperties和@Value
@ConfigurationProperties 功能:批量注入配置文件中的属性
@Value 功能:一个个指定
@ConfigurationProperties 支持松散绑定(松散语法) 不支持SpEL 支持JSR303数据校验 支持复杂类型封装
@Value 不支持松散绑定(松散语法) 支持SpEL 不支持JSR303数据校验 不支持复杂类型封装
所谓松散绑定就是如yml中last-name,但我们Person中的属性为lastName并提供set方法,也可用@ConfigurationProperties绑定过来
yml或properties配置文件中怎么配
然后找Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.4中META-INF中的spring-factories中的XXAutoConfiguration。这个类中可能包含很多类。找到相应类的@EnableConfigurationProperties(XXProperties.class)中的参数点进去,找@ConfigurationProperties( prefix = "spring.info" )这就是yml中需要配置的前缀,然后本类的成员属性且提供set方法的就是可配置的内容
JSR303数据校验@Validated
spring-boot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式
关于JSR 303的一些注解可参考:JSR-303 - 简书
或者引入spring-boot-starter-validation依赖后找这个包javax.validation.constraints
Person中我们给name字段加上一个@Email校验
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "person") //加上这个注解,就会把yml中person打头的配置数据直接和提供set方法的属性对号入座,spring容器中的该bean就是属性有值的
@Validated
public class Person {
@Email(message = "邮箱格式不合法") //也可加上message参数,validation中的注解验证赋给该字段的值是否为邮箱
private String name;
private Integer age;
private Boolean happy;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
还是引用上面的yml中的内容,由于yml中的name不是邮箱类型的,所以启动报错。
多环境配置及配置文件的位置
可参考SpringBoot运维_阳光明媚UPUP的博客-CSDN博客
三个配置工程优先级properties>yml>yaml
不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留
yml文件可写的位置:
1.jar包同级的config目录下的application.yml,打包前也可写在src的我同级目录下创建config文件夹中创建application.yml文件。
2.jar包同级目录下创建的application.yml,打包前也可写在src同级目录下创建application.yml文件
3.resource下创建config文件夹,里面写application.yml
4.resource下的application.yml
上面优先级1最高,递进到4最低
也可多环境开发在yml中选择要使用那一套配置
resource下的application.yml将这个配置文件进行copy,复制3份到resource目录下
一定要以application-(环境名)命名,dev\pro\test。,且要保留那个application命名的配置文件。删除配置中其他东西,只留一些配置相关的:
spring:
profiles:
active: dev
也可就一个application.yml中使用多文档模块
使用---分割多环境
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8081
spring:
profiles: test
默认使用第一个,如果加上spring.profiles.active指定就以指定的优先
Springboot Web开发
1、静态资源分析
WebMvcAutoConfiguration中的WebMvcAutoConfigurationAdapter有个方法如下:
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
1、对于WebJars静态资源
官网:WebJars - Web Libraries in Jars
可以以maven的形式引入如jequery的坐标。引入后,就会在jar包依赖中看到/META-INF/resources/webjars/,上面代码中意思就是你只需浏览器输入80:webjars/**,就可替代80://META-INF/resources/webjars/**。
2、对其他静态资源
还配置了一个静态资源在getStaticPathPattern(properties文件中的值为/**,当前目录下所有内容都可识别了)以后只要url写/**就可找静态资源内容
然后就是WebMvcAutoConfiguration中的WebMvcAutoConfigurationAdapter类中的public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,...)有参构造上有个ResourceProperties,我们跟进这个类ResourceProperties extends Resources,再跟进这个父类,父类Resources有个属性,又这4个路径classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
所以静态资源可放到:resource下的目录resources目录、static目录、public目录下和META-INF/resources/这个webjar的东西。
优先级
resource>static >public
一般public放公共资源,static放静态资源图片等等,resource放一些upload上传的文件。
测试
直接在resource下的目录resources目录、static目录、public目录下这三个目录下创建一个a.html,访问80/a.html即可访问到。
3、但,如果yml中配置静态资源路径,上面就不生效了,因为上面源码中写着呢
配置文件中这么配spring.mvc.static-path-pattern=/hello/** 。上面源码中默认会失效。 但一般不在配置文件中这么写。没意义。
总结:
1.在springboot,我们可以使用以下方式处理静态资源
webjars loca7host:8080/webjars/
public,static,/**,resources1ocalhost:8080/2.优先级:resources>static(默认) >public
2、首页
WebMvcAutoConfiguration中有个类EnableWebMvcConfiguration,类中有方法
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
里面有个getWelcomePage又找了getIndexHtml
private Resource getIndexHtml(Resource location) {
try {
Resource resource = location.createRelative("index.html");
if (resource.exists() && resource.getURL() != null) {
return resource;
}
} catch (Exception var3) {
}
return null;
}
找的是resource下的index.html
测试
resource下的目录resources目录、static目录、public目录下随便创建index.html。直接访问http://localhost:8080/
3、Thymeleaf模板引擎
resource下的templates目录下的所有页面,只能通过controller来跳转!这个需要模板引擎的支持!
改静态资源只需Build->Build Project一下即可。
1、Thymeleaf官网: Thymeleaf
2、Thymeleaf在Github 的主页: GitHub - thymeleaf/thymeleaf: Thymeleaf is a modern server-side Java template engine for both web and standalone environments.3、Spring官方文档:Spring Boot Reference Guide,找到我们对应的版本
从上方第三个网址找到官网找到spring-boot-starter-thymeleafhttps://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-starters/spring-boot-starter-thymeleaf/pom.xml
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,我们来组装一些数据,我们把这些数据找到。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。
双击shift直接搜索这个类ThymeleafProperties,就是其配置类。里面有如下配置
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
所以默认配置,html需要放到template目录下才会生效
测试:
resource下的template目录下创建test.html,然后controller跳转
@RestController public class HelloController { @RequestMapping("/test") public String index(){ return "test"; } }
使用方法:
详见下方的网页中的使用方法
Tutorial: Using Thymeleaf
基本使用
1、html中引入命名空间
2、使用th打头的属性
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
test test test
<br>
<div th:text="${msg}"></div><br>
<h3 th:each="user:${users}" th:test="${user}"></h3>
</body>
</html>
o Variable Expressions: ${...} 普通如request域
o Selection Variable Expressions: *{...}o Message Expressions: #{...} 国际化的properties文件中取值
o Link URL Expressions: @{...} 路径如src和href等[[#{login.remember}]]也可这样写行内的,使用两个中括号
${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}
th:if="${not #strings.isEmpty(msg)}
所有的htmL元素都可以被thymeLeaf替换接管:th:元素名 如果是图片路径等需th:href="@{/}或th:src="@{/}"且/代表根路径
3、controller使用sprigmvc的那一套如HttpServletRequest或ModuleAndView或Modle去向request域中放数据
@Controller
public class HelloController {
@RequestMapping("/test")
public String index(HttpServletRequest request){
request.setAttribute("msg","你好啊");
String [] users = new String[]{"zhangsan","wangwu"};
request.setAttribute("users",users);
return "test";
}
}
也可配置如关闭thymeleaf缓存
spring:
thymeleaf:
cache: false
4、MVC配置原理
先查看官网Spring MVC Auto-configuration部分:29. Developing Web Applications
扩展springmvc
双击shift查找ContentNegotiatingViewResolver,有个resolveViewName方法
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
获取候选的视图得到最好的视图,跟进这个getCandidateViews来遍历所有的视图解析器,并封装成对象,添加到候选的视图
拓展springmvc
//扩展springMvc
//如果。你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//ViewResolver 实现了视图解析器接口的类,我们就可以把它看做视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
测试
测试一下自定义的这个配置的视图解析器有没有生效:
因为所有的请求都需要经过DispatcherServlet,我们双击shift搜索一下这个类,其中有个doService调用了doDispatch,进入这个doDispatch方法,方法头打个断点,然后debug运行启动类。
运行一个请求http://localhost:8080/test
进入断点后,点开debug中的this,点开viewResolvers,发现有以下几个:除了默认的1和2,还有引入thymeleaf的,还有第四个为刚刚上面自定义的MyViewResolver。
1 = {BeanNameViewResolver@5654} 0 = {ContentNegotiatingViewResolver@5653} 2 = {ThymeleafViewResolver@5655} 3 = {MyMvcConfig$MyViewResolver@5656} 4 = {ViewResolverComposite@5657} 5 = {InternalResourceViewResolver@5658}
5、扩展SpringMvc
先查看官网Spring MVC Auto-configuration部分:29. Developing Web Applications
接着上面的MVC原理中的写的MyMvcConfig
先双击shift搜一下DispatcherServletAutoConfiguration,其中的EnableWebMvcConfiguration类中,有个mvcConversionService方法
@Bean
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService((new DateTimeFormatters()).dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
this.addFormatters(conversionService);
return conversionService;
}
默认先查找mvc的propoerties中的配置,也就是可在配置文件中配置一些规则。
我们跟进上面getFormat(),再跟进方法的返回值类型WebMvcProperties.Format就跟到了WebMvcProperties中,就有Format这个类和其属性,yml配置文件就可以写了,且yml有提示。
修改SpringBoot的默认配置
方式一
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
我们扩展到额springmvc,由于继承了,所以可以写很多重写方法,可generate看一下
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
}
如加一个视图跳转的
//如果我们要扩展springmvc.官方建议我们这样去做!
@Configuration
//@EnableWebMvc // 获取容器中所有的webmvcConfig 这个注解就是导入一个类@Import({DelegatingWebMvcConfiguration.class}),官方不让我们加这个注解
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("test");//路径hello就会跳转到test.html了
}
}
至于为什么不能加@EnableWebMvc
这个注解跟进后导入了一个类@Import({DelegatingWebMvcConfiguration.class})
我们双击shift还是搜WebMvcAutoConfiguration,搜索WebMvcAutoConfigurationAdapter,这个类上方注解也导入了一个类@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
EnableWebMvcConfiguration这个类跟进后发现继承了DelegatingWebMvcConfiguration
跟进这个父类,发现里面有个
@Autowired( required = false )
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
setConfigurers获取容器中所有的webmvcConfig
还有一点就是DelegatingWebMvcConfiguration这个类继承了WebMvcConfigurationSupport
因为WebMvcAutoConfiguration上方加了下面这个注解@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
所以如果在我们自己拓展的mvc配置类MyMvcConfig上方加上@EnableWebMvc,就会在容器中加上这个DelegatingWebMvcConfiguration这个类继承了WebMvcConfigurationSupport父类,就会导致WebMvcAutoConfiguration文件整个大类失效。整个这个mvc配置的大类中所以东西不起作用了
另:自己写start的做法就是写一个configuration和一个properties,放到Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.4中的org.springframework.boot.autoconfigure包下即可。写configuration里一定要存在conditionOn
在springboot中,有非常多的xxxx Configuration帮助我们进行扩展配置,只要在源码中或公司源码中看见了这个东西,我们就要关注一下这个类拓展了什么东西。
项目:员工管理系统
1、先用模拟数据
引入了thymeleaf后,html要放到resource下的template目录下。静态资源放到resource下的static下
注意点,所有页面的静态资源都需要使用thymeleaf接管;@
实体类
com.kdy.pojo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //0女 1男
private Department department;
private Date birth;
}
Dao层:模拟数据:不连数据库,先用内存中的假数据:
//部门Dao
@Repository
public class DepartmentDao {
//模拟数据库的数据
private static Map<Integer, Department> departments = null;
static { //该类加到堆的静态区时,静态代码块执行唯一的一次
departments = new HashMap<Integer, Department>(); //创建一个部门表
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
public Collection<Department> getDepartments(){
return departments.values();
}
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
//员工Dao
@Repository
public class EmployeeDao {
//模拟数据库的数据
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static { //该类加到堆的静态区时,静态代码块执行唯一的一次
employees = new HashMap<Integer, Employee>(); //创建一个部门表
employees.put(1001,new Employee(1001,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1002,new Employee(1002,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1003,new Employee(1003,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1004,new Employee(1004,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1005,new Employee(1005,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
}
private static Integer intId = 1005;
private static Integer getIntId(){
return intId++;
}
public void save(Employee employee){//假设参数传递了员工name,emial,性别,和部门id,员工id不知道传没传
if (employee.getId()==null){
Integer newID = getIntId();
employee.setId(newID);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
public void deleteByEmployeeId(Integer id){
employees.remove(id);
}
}
2、国际化
先确保Idea的setting的File Encodings配置都是UTF-8
需setting的plugin加一个resource bundle edit的插件并开启。
1、国际化英文international ,先在resource目录下创建 i18n 目录
2、在i18n目录下创建login.properties文件和login_zh_CN.properties 文件创建后会多了一个Resource Bundle 'login'的虚拟文件夹把这两个配置包裹起来,右击这个虚拟文件夹Resource Bundle 'login',可new -》Add Property File,点击右侧加号,可输入如en_us后点击ok,就生成了一个login_en_US.properties文件
关于国际化的符号可见:国际化语言符号_weixin_33785108的博客-CSDN博客
3、当我们点开login_en_uS.properties或login_zh_CN.properties,发现Idea的下方多了一个Resource Bundle按钮,我们点一下,出现一个界面,且选中了login这个配置,我们点击界面中的加号new Property,弹窗输入login.tip点击ok,就发现login.properties文件中多了个login.tip=的配置。
4、我们还是换回刚刚点击Resource Bundle按钮的login_en_uS.properties或login_zh_CN.properties中,界面中选中了login.tip右侧有三个配置文件文本框login_en_uS.properties或login_zh_CN.properties和login.properties的,我们分别输入其相应语言,如默认的login.proeprties和中文的login_zh_CN.properties文件输入请登录,在login_en_uS.properties文件中输入Please Sign in
其实第4步也可自己一个个配置文件中的配置,比如配置完默认的再拷贝到别的语言的properties中修改数值。
5、然后修改html中使用thymeleaf获取国际化配置文件中的内容。#{}来获取
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>indedx</title>
</head>
<body>
<div style="align-content: center">
<h1 th:text="#{login.tip}"></h1>
<input type="text" th:placeholder="#{login.username}"><br/><br/>
<input type="passord" th:placeholder="#{login.password}"><br/><br/>
<input type="checkbox">[[#{login.remember}]]<br/><br/>
<button type="submit" >[[#{login.btn}]]</button><br/><br/>
<!--之前html的href带参数需要写?,现thymeleaf带参数直接写括号即可-->
<a th:href="@{/index.html(l='zh_CN')}">中文</a>
<a th:href="@{/index.html(l='en_US')}">English</a>
</div>
</body>
</html>
也可:双击shift搜索一下MessageSourceAutoConfiguration,然后还能找到其配置文件MessageSourceProperties。
6、html中给超链接切换中英文国际化
<!--之前html的href带参数需要写?,现thymeleaf带参数直接写括号即可-->
<a th:href="@{/index.html(lang='zh_CN')}">中文</a> <a th:href="@{/index.html(lang='en_US')}">English</a>
7、自己写一个LocaleResolver
自定义地址解析配置
我们双击shift搜索WebMvcAutoConfiguration找EnableWebMvcConfiguration类中有个localeResolver方法,这个方法中默认使用new‘了个AcceptHeaderLocaleResolver跟进这个类AcceptHeaderLocaleResolver implements LocaleResolver发现实现了LocaleResolver,所以我们可自己写一个类来实现
com.kdy.config
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l"); //请求参数内容
Locale local = Locale.getDefault(); //默认的locale
if(!StringUtils.isEmpty(language)){
String[] split = language.split("_");
local = new Locale(split[0],split[1]);
}
return local;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
并在springmvc拓展中配上这个bean
com.kdy.config
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");//路径/就会跳转到index.html了
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
@Bean("localeResolver") //将自定义国际化的对象放到spring接管的容器中就生效了
public LocaleResolver myLocaleResolver(){
return new MyLocaleResolver();
}
}
3、登录功能的实现
由于暂时没有数据库,我们让所有请求都登录成功。
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(@RequestParam("username") String username, @RequestParam("password")String password, Model model){
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
return "redirect:main.html";//在springmvc拓展配置中加了一个路径main.html映射到dashboard.html文件的配置。这里就可以直接这样重定向了
}else{
model.addAttribute("msg","用户名或密码错误!");
return "index";
}
}
}
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");//路径/就会跳转到index.html了
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");//这里映射一个虚拟的main.html路径
}
@Bean("localeResolver") //将自定义国际化的对象放到spring接管的容器中就生效了
public LocaleResolver myLocaleResolver(){
return new MyLocaleResolver();
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>indedx</title>
</head>
<body>
<div style="align-content: center">
<h1 th:text="#{login.tip}"></h1>
<!-- //如果msg的值为空,则不显示消息- -->
<p style="color: #ff0000" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<form th:action="@{/login}">
<input type="text" name="username" th:placeholder="#{login.username}"><br/><br/>
<input type="passord" name="password" th:placeholder="#{login.password}"><br/><br/>
<input type="checkbox">[[#{login.remember}]]<br/><br/>
<button type="submit" >[[#{login.btn}]]</button><br/><br/>
</form>
<!--之前html的href带参数需要写?,现thymeleaf带参数直接写括号即可-->
<a th:href="@{/index.html(l='zh_CN')}">中文</a>
<a th:href="@{/index.html(l='en_US')}">English</a>
</div>
</body>
</html>
4、登录拦截器
登录成功后才能进入到主页面,否则要重新登录
1、先写一个springmvc阶段学的过滤器
//@WebFilter(urlPatterns = "/main.html")这个是Javaweb的filter的在springmvc中是没效果的。
public class LoginHandlerInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser==null){
request.setAttribute("msg","没有权限请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
return true;
}
}
}
2、配置这个springmvc的拦截去到springboot的WebMvc配置中让其自动装配
回到我们自己拓展的springmvc的配置
public class MyMvcConfig implements WebMvcConfigurer {}方法体中看重写的父类WebMvcConfigurer 方法有没有和拦截器接近的。有个addInterceptors方法。
com.kdy.config 重写这个addInterceptors方法
//如果我们要扩展springmvc.官方建议我们这样去做!
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");//路径/就会跳转到index.html了
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");//这里映射一个虚拟的main.html路径
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/login","/css/*","/js/**","/img/**");//还可排除一些静态资源。
}
@Bean("localeResolver") //将自定义国际化的对象放到spring接管的容器中就生效了
public LocaleResolver myLocaleResolver(){
return new MyLocaleResolver();
}
}
主页面dashboard.html中加上session中读取的用户名。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>dashboard</title>
</head>
<body>
<h3>用户名:[[${session.loginUser}]]</h3>
</body>
</html>
5、展示员工数据
controller
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model){
model.addAttribute("emps",employeeDao.getAll());
return "emp/list";
}
}
resource下template目录下emp目录下list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>list</title>
<style>
table,thead,tbody,th,tr,td{
border: 1px solid black
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>ID</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>departmentName</th>
<th>birthday</th>
<th>操 作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td >[[${emp.getLastName()}]]</td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td >
<button>编辑</button>
<button>删除</button>
</td>
</tr>
</tbody>
</table>
</body>
</html>
6、添加员工
controller的同一个路径”/emp“一个get请求的用于跳到添加页面,一个post请求的用于提交数据
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@Autowired
private DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model){
model.addAttribute("emps",employeeDao.getAll());
return "emp/list";
}
@GetMapping("/emp")
public String toAddEmp(Model model){
//查询部门的数据
model.addAttribute("depts",departmentDao.getDepartments());//放到request域
return "emp/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
}
resource下template目录下emp目录下add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add</title>
</head>
<body>
<h3>添加一个用户</h3>
<form th:action="@{/emp}" method="post">
lastName:<input type="text" name="lastName"/><br>
email:<input type="text" name="email"/><br>
gender:<input type="radio" value="1" name="gender"/>男 <input type="radio" value="0" name="gender"/>女<br>
departmentName:<select name="department.id">
<option>---请选择---</option>
<option th:each="dept:${depts}" th:value="${dept.getId()}" th:text="${dept.getDepartmentName()}"></option>
</select><br>
birthday:<input type="date" name="birth"/><br>
<button type="submit">提交</button>
<button type="reset">重置</button>
</form>
</body>
</html>
yml配置中改一下date格式
spring:
thymeleaf:
cache: false
messages:
basename: i18n/login # 国际化的配置文件配置到的真实的位置
mvc:
format:
date: yyyy-MM-dd #配置input type为date上传日期的格式问题
7、修改员工、删除员工
controller加上去修改界面和修改操作和删除操作的方法
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@Autowired
private DepartmentDao departmentDao;
@RequestMapping("/emps")
public String list(Model model){
model.addAttribute("emps",employeeDao.getAll());
return "emp/list";
}
@GetMapping("/emp")
public String toAddEmp(Model model){
//查询部门的数据
model.addAttribute("depts",departmentDao.getDepartments());//放到request域
return "emp/add";
}
@PostMapping("/emp")
public String addEmp(Employee employee){
System.out.println(employee);
employeeDao.save(employee);
return "redirect:/emps";
}
@GetMapping("/emp/{id}")
public String toUpdate(@PathVariable("id") Integer id,Model model){
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp",employee);
model.addAttribute("depts",departmentDao.getDepartments());//放到request域
return "emp/update";
}
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDao.save(employee);
return "redirect:/emps";
}
@GetMapping("/deleteEmp/{id}")
public String deleteEmp(@PathVariable("id")Integer id){
employeeDao.deleteByEmployeeId(id);
return "redirect:/emps";
}
}
resource下template目录下emp目录下list.html,修改和删除的thymeleaf的a标签带参数
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>list</title>
<style>
table,thead,tbody,th,tr,td{
border: 1px solid black
}
</style>
</head>
<body>
<h3><a th:href="@{/emp}">添加员工</a></h3>
<table>
<thead>
<tr>
<th>ID</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>departmentName</th>
<th>birthday</th>
<th>操 作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId()}"></td>
<td >[[${emp.getLastName()}]]</td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
<td >
<a th:href="@{/emp/{empId}(empId=${emp.getId()})}">修改</a>
<a th:href="@{/deleteEmp/}+${emp.getId()}">删除</a>
</td>
</tr>
</tbody>
</table>
</body>
</html>
resource下template目录下emp目录下update.html接收数据
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>update</title>
</head>
<body>
<h3>修改一个用户</h3>
<form th:action="@{/updateEmp}" method="post">
<input name="id" type="hidden" th:value="${emp.getId()}">
lastName:<input type="text" name="lastName" th:value="${emp.getLastName()}"/><br>
email:<input type="text" name="email" th:value="${emp.getEmail()}"/><br>
gender:<input type="radio" value="1" name="gender" th:checked="${emp.getGender()==1}"/>男
<input type="radio" value="0" name="gender" th:checked="${emp.getGender()==0}"/>女 <br/>
departmentName:<select name="department.id">
<option>---请选择---</option>
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${depts}" th:value="${dept.getId()}" th:text="${dept.getDepartmentName()}"></option>
</select><br>
birthday:<input type="date" name="birth" th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}"/><br>
<button type="submit">提交</button>
</form>
</body>
</html>
修改一下dao层的save方法,有id就修改,无id就新增。
package com.kdy.dao;
import com.kdy.pojo.Department;
import com.kdy.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//员工Dao
@Repository
public class EmployeeDao {
//模拟数据库的数据
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDao departmentDao;
static { //该类加到堆的静态区时,静态代码块执行唯一的一次
employees = new HashMap<Integer, Employee>(); //创建一个部门表
employees.put(1001,new Employee(1001,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1002,new Employee(1002,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1003,new Employee(1003,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1004,new Employee(1004,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
employees.put(1005,new Employee(1005,"zhangsan","26559484@qq.com",1,new Department(101,"教学部"),new Date()));
}
private static Integer intId = 1005;
private static Integer getIntId(){
return intId++;
}
public void save(Employee employee){//假设参数传递了员工name,emial,性别,和门id,员工id不知道传没传
Integer newID = getIntId();
if (employee.getId()==null){
employee.setId(newID);
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(newID,employee);
}else{
Department departmentById = departmentDao.getDepartmentById(employee.getDepartment().getId());
employee.setDepartment(departmentById);
employees.put(employee.getId(),employee);
}
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
public void deleteByEmployeeId(Integer id){
employees.remove(id);
}
}
8、404处理
resource下的template下创建error文件夹,创建404.html或500.html,出现问题就会自动跳转这个页面
9、退出登录
controller消除session并重定向到登陆页面
package com.kdy.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password")String password,
Model model,
HttpSession session){
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
session.setAttribute("loginUser",username);
return "redirect:main.html";//在springmvc拓展配置中加了一个路径main.html映射到dashboard.html文件的配置。这里就可以直接这样重定向了
}else{
model.addAttribute("msg","用户名或密码错误!");
return "index";
}
}
@RequestMapping("/logout")
public String logout(HttpSession session){
session.invalidate();
return "redirect:/index.html";
}
}
某个页面加上退出登录的按钮
<h5><a th:href="@{/logout}">退出登录</a></h5>
可了解一下x-admin后台模板
整合JDBC
可参考springData的官网:Spring Data
数据库相关的启动器可参考:Spring Boot Reference Guide
1、创建新项目,勾选sql中的JDBC api和MySQL driver,再加个web的包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
<scope>runtime</scope>
</dependency>
2、yml中配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root123
3、测试用例
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println(connection); //HikariProxyConnection@319144230 wrapping com.mysql.jdbc.JDBC4Connection@72f9f27c
System.out.println(dataSource.getClass());//查看默认数据源 class com.zaxxer.hikari.HikariDataSource
}
}
我们可在yml配置中ctr点进去看源码:就调到了DataSourceProperties类中,然后这个肯定对应一个AutoConfig,我们双击shift搜索一下DataSourceAutoConfiguration
spring中有很多template模板就是spring配置好的拿来即用的bean,如jdbcTemplate。
还是找这个Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.4里找jdbc包,里面就有JdbcTemplateAutoConfiguration、JdbcProperties之类的配置
jdbcTemplate最基础的增删改查
@RestController
public class JDBCController {
@Autowired
private JdbcTemplate jdbcTemplate; //可跟进这个jdbcTemplate看源码的方法,都都可直接拿来用的
//查询数据库所有信息
//没有实体类,我们使用map来获取数据库返回的东西
@RequestMapping("/getAll")
public List<Map<String,Object>> userList() {
String sql = "select * from user;";
List<Map<String, Object>> list_map = jdbcTemplate.queryForList(sql);
System.out.println(list_map);
return list_map;
}
@RequestMapping("/add")
public String add(){
String sql = "insert into jdbc.user(name,password,birthday) values('小明','123456789',?)";
System.out.println(sql);
jdbcTemplate.update(sql,new Date());
return "add-ok";
}
@RequestMapping("/update/{id}")
public String updateUser(@PathVariable("id") Integer id){
String sql = "update jdbc.user set name = ? ,password = ? where id = "+id;
Object [] objects = new Object[2];
objects[0] = "张三";
objects[1] = "password666";
jdbcTemplate.update(sql,objects);
return "update-ok";
}
@RequestMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") Integer id){
String sql = "delete from jdbc.user where id = "+id;
jdbcTemplate.update(sql);
return "delete-ok";
}
}
整合Druid数据源
DRUID简介
Druid是阿里巴巴开源平台上一个数据库连接池实现,结合了C3PO、DBCP、PROXOOL等DB池的优点,同时加入了日志监控。
Druid可以很好的监控DB池连接和SQL的执行情况,天生就是针对监控而生的DB连接池。
Spring Boot 2.0以上默认使用Hikari 数据源,可以说 Hikari与Driud 都是当前Java Web上最优秀的数据源,我们来重点介绍Spring Boot如何集成 Druid数据源,如何实现数据库监控。
基本配置参数可参考druid 参数设置_druid配置参数_iiaythi的博客-CSDN博客
1、引依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
2、整合配置文件 详见SpringBoot基础认识_阳光明媚UPUP的博客-CSDN博客
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root123
type: com.alibaba.druid.pool.DruidDataSource
测试一下
@SpringBootTest
class Springboot04DataApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());//class com.alibaba.druid.pool.DruidDataSource
}
}
启动引导类,上节课的jdbc依然能正常使用,只不过底层的数据库连接池变了
还有druid的一些专有的配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root123
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot默认是不注入这些属性值的,需要自己绑定
#druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的fiLters,stat:监控统计、Log4j:日志记录、wall:防御sqL注入
#如果允许时报错java.Lang.CLassNotFoundException: org.apache.Log4j.Priority
#则导入log4j依赖即可,Maven地址: https: / /mvnrepository.com/artifact/Log4j/Log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
对于其监控功能,我们测试一下日志的监控。
先导入log4j的依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3、自己配置DruidConfig文件进行一些配置
com.kdy.config下DruidConfig类
//自定义druid的配置,不使用spring默认的那一套了
@Configuration //这个是config文件
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource") //这个标properties文件
public DataSource druidDataSource(){
return new DruidDataSource();
}
//后台监控 相当于web.xml,通过这种全注解的方式注册进去
@Bean
public ServletRegistrationBean StatViewServlet(){
//访问路径/druid/**就可进入druid后台界面,这个界面别人写好的直接用
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录账号密码
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");//登陆key是固定的 LoginUsername loginPassword
initParameters.put("loginPassword", "123456");
//允许谁可以访问I
initParameters.put("allow", "");//""所有人访问,localhost本机访问,具体的人就具体的人访问
//禁止谁访问
//initParameters.put("zhangsan", "192.168.11.123");//
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
}
配置好上面后
重启项目后,访问http://localhost:8080/druid/**,即可跳转到一个druid的页面
登录后,里面会有首页、数据源、SQL监控、SQL防火墙、Web应用、URI监控、Session监控、Spring监控、JSON API的导航栏的日志
web时期的servlet和filter等在springboot中怎么用?
因为SpringBoot内置了servlet容器,所以没有web.xml , 我们可用替代方法:如上面的ServletRegistrationBean ,以全注解的方式,将配置注册进去。
再如下面的filter
//自定义druid的配置,不使用spring默认的那一套了
@Configuration //这个是config文件
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource") //这个标properties文件
public DataSource druidDataSource(){
return new DruidDataSource();
}
//后台监控 相当于web.xml,通过这种全注解的方式注册进去
@Bean
public ServletRegistrationBean StatViewServlet(){
//访问路径/druid/**就可进入druid后台界面,这个界面别人写好的直接用
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//后台需要有人登录账号密码
HashMap<String, String> initParameters = new HashMap<>();
//增加配置
initParameters.put("loginUsername","admin");//登陆key是固定的 LoginUsername loginPassword
initParameters.put("loginPassword", "123456");
//允许谁可以访问I
initParameters.put("allow", "");//""所有人访问,localhost本机访问,具体的人就具体的人访问
//禁止谁访问
//initParameters.put("zhangsan", "192.168.11.123");//
bean.setInitParameters(initParameters);//设置初始化参数
return bean;
}
//filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//可过滤哪些请求呢?
Map<String, String> initParameters = new HashMap<>();
//排序过滤项
initParameters.put("exclusions","*.js,*.css,/druid/*"); //可跟WebStatFilter进查看配置的参数,如exclusions
bean.setInitParameters(initParameters);
return bean;
}
}
整合Mybatis框架
mybatis-spring为当时spring整合mybatis的包
springboot的整合包
mybatis-spring-boot-start
1、创建新模块,勾选web,mysql driver和jdbc api
2、手动引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
这个是mybatis-spring-boot-starter,
之前引入的整合包都是spring-boot-starter打头的,第三方在后面,
而这个mybatis-spring-boot-starter名字是mybatis打头的,说明为mybatis提供的整合包
可参考mybatis整合springboot的文档https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/site/markdown/index.md
3、加个lombok依赖,写实体类User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String password;
private Date birthday;
}
4、com.kdy.mapper中写mapper
@Mapper
//@Repository //不用加其他bean的注解,只有一个mapper注解也可以
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
5、resource下建立文件夹写mapper.xml,这里我们可建立com.kdy.mapper文件夹中写UserMapper.xml这种接近mybatis规范的写法,也可建个mybatis.mapper中写UserMapper.xml这种写法。都能识别到
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kdy.mapper.UserMapper"><!--即使使用mybatis的注解开发也需要加上命名空间的mapper标签-->
<!-- <select id="queryUserList" resultType="User" useCache="true">-->
<select id="queryUserList" resultType="User">
select * from user;
</select>
<select id="queryUserById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
<insert id="addUser" parameterType="user">
insert into user(name,password,birthday) values(#{name},#{password},#{birthday});
</insert>
<update id="updateUser" parameterType="user">
update user set name = #{name},set password =#{password},set birthday = #{birthday} where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
</mapper>
6、对于配置文件yml,之前spring阶段学mybatis在mybatis.xml中的一些配置,可使用yml中配置,具体在MybatisProperties文件中。如配置别名的包,配置mapper.xml文件的位置(可以不用配)
server:
port: 8081
debug: true #方便查看日志
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root123
mybatis:
type-aliases-package: com.kdy.pojo #可跟进注解查看MybatisProperties的所有配置
#mapper-locations: classpath:com.kdy.mapper/*.xml #classpath:后面不加/代表resource,加/代表从项目根目录找
7、如果例如com.kdy.mapper包的mapper接口上不配置@Mapper,可在如com.kdy.mapper包的mapper接口上先加一个@Repository,然后在引导类中加上@MapperScanner去扫描如com.kdy.mapper包
@SpringBootApplication
//@MapperScan("com.kdy.mapper") //也可使用mapperScanner扫描某个包下的类作为mapper
public class Springboot05MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot05MybatisApplication.class, args);
}
}
8、写controller并访问测试
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/getAll")
public List<User> getAll(){
return userMapper.queryUserList();
}
@RequestMapping("/getById")
public User getOne(){
return userMapper.queryUserById(2);
}
@RequestMapping("/add")
public int add(){
return userMapper.addUser(new User(null,"张三丰","pass666",new Date()));
}
@RequestMapping("/update")
public int update(){
return userMapper.updateUser(new User(2,"张三丰666","666afedf",new Date()));
}
@RequestMapping("/delete")
public int delete(){
return userMapper.deleteUser(2);
}
}
9、当配置完yml的数据源后,可测试用例测试一下
@SpringBootTest
class Springboot05MybatisApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() {
System.out.println(dataSource);
}
}
SpringSecurity
web开发中,安全第一位:
虽然过滤器和拦截器也可增加网站的安全,
但这个springSecurity框架更方便一些。
安全不是功能性考虑。
设置网站之初就应该将安全考虑进去,如漏洞,隐私泄露...
shiro和SpringSecurity很像~除了类不一样,名字不一样;
shiro认证,SpringSecurity授权(vip1,vip2,vip3)
参考官网:Spring Security
SpringSecurity是身份验证和访问控制的框架,对Java应用。
是一个功能强大且高度定制的身份验证和访问控制框架,是保护基于Spring的应用程序的事实上的标准。是一个框架,重点是提供身份验证和Java应用程序的授权。与所有Spring项目一样,SpringSecurity的真正强大之处在于它能够很容易地扩展以满足自定义需求。
权限:功能权限-管理员和普通用户和游客。访问权限。菜单权限。
之前都是用拦截器或过滤器实现,大量的原生代码参数,非常冗余。
SpringSecurity就是解决这个事情的框架。
1、创建springboot模块,勾选spring web包。再加个thymeleaf的依赖:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
2、环境准备,resource下templates目录下,创建下图所示的html
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
index页面
<h3>首页</h3>
<h3><a th:href="@{/toLogin}">登录</a></h3>
<h3>Level1</h3>
<a th:href="@{/level1/1}">level1-1</a>
<a th:href="@{/level1/2}">level1-2</a>
<a th:href="@{/level1/3}">level1-3</a>
<hr>
<h3>Level2</h3>
<a th:href="@{/level2/1}">level2-1</a>
<a th:href="@{/level2/2}">level2-2</a>
<a th:href="@{/level2/3}">level2-3</a>
<hr>
<h3>Level3</h3>
<a th:href="@{/level3/1}">level3-1</a>
<a th:href="@{/level3/2}">level3-2</a>
<a th:href="@{/level3/3}">level3-3</a>
<hr>
</body>
</html>
level1的1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1</title>
</head>
<body>
level1的1.html的页面
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
login 页面
</body>
</html>
问题
现在谁都可以访问任何内容
解决用springSecurity以近乎aop横切的思想进行授权身份验证和访问控制
3、用户认证和授权
简介
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
. WebSecurityConfigurerAdapter:自定义Security策略
. AuthenticationManagerBuilder:自定义认证策略
.@EnableWebSecurity:开启WebSecurity模式
Spring Security的两个主要目标是“认证”和“授权”(访问控制)。“认证”(Authentication)
“授权”(Authorization) 这个概念是通用的,而不是只在Spring Security 中存在。
参考官网: Spring Security,查看我们自己项目中的版本,找到对应的帮助文档:Spring Security Reference
3.1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
导入后在右侧maven看到本依赖的核心包有如下三个aop和security-config和security-web
org.springframework:spring-aop:5.3.9
org.springframework.security:spring-security-config:5.5.2 org.springframework.security:spring-security-web:5.5.2
3.2、配置自定义的SecurityConfig
根据官网的用法https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/#abstractsecuritywebapplicationinitializer-without-existing-spring的The custom DSL can then be used like this部分,也就是https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/的15.1的部分进行配置
com.kdy.config文件夹下创建下面这个类
//AOP横切的思想比拦截器好多了
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); //链式编程
///首页所有人可以访问,功能页只有对应有权限的人才能访问
http.authorizeRequests() //对请求路径进行拦截校验有没有权限
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没没有权限默认会到登录页面,需要开启登录的页面
http.formLogin();
}
}
然后重启项目发现,首页可正常访问,level123开头的路径被拦截403了,但我们上面配置了没有权限自动跳转,于是自动跳转到第三方生成的登录界面了。
至于为什么我们没有配置/login的路径,而资源里只有一个login.html,就能没权限跳回登陆界面(这个登录页面为代码自动生成的)呢?
我们可以跟进http.formLogin这个方法,并上面点击下载源码,看到相关方法的详细注释
我们再配置一些认证用的
//AOP横切的思想比拦截器好多了
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); //链式编程
///首页所有人可以访问,功能页只有对应有权限的人才能访问
http.authorizeRequests() //对请求路径进行拦截校验有没有权限
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没没有权限默认会到登录页面,需要开启登录的页面
http.formLogin();
}
//认证 springboot 2.1.X可以直接使用~
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
// auth.jdbcAuthentication() //在数据库中认证,下行为在内存中认证
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("zhangsan").password("123456").roles("vip2","vip3")
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
这时重启项目,到index.html,点击level3菜单的1.html的那个,由于没有权限,未登录,就会跳转到第三方生成的登陆界面,然后我们输入用户名zhangsan,密码123456后,就会正常显示level3的1.html内容,但是点击level1菜单的1.html,由于zhangsan没有这个vip1的role,没有权限就还是报403
上面的是处于内存中的认证,如果想用数据看中的参考官网https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/#hello-web-security-java-configuration的9.2部分9.2 JDBC Authentication.如下配置即可:
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(users.username("user").password("password").roles("USER"))
.withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}
3.3、加上注销功能
package com.kdy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//AOP横切的思想比拦截器好多了
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http); //链式编程
///首页所有人可以访问,功能页只有对应有权限的人才能访问
http.authorizeRequests() //对请求路径进行拦截校验有没有权限
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没没有权限默认会到登录页面,需要开启登录的页面
http.formLogin();
//注销
http.logout();
}
//认证 springboot 2.1.X可以直接使用~
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
// auth.jdbcAuthentication() //在数据库中认证,下行为在内存中认证
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("zhangsan").password("123456").roles("vip2","vip3")
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
index.html中货架上注销按钮
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
index页面
<h3>首页</h3>
<h3><a th:href="@{/toLogin}">登录</a></h3>
<h3><a th:href="@{/logout}">注销</a></h3>
<h3>Level1</h3>
<a th:href="@{/level1/1}">level1-1</a>
<a th:href="@{/level1/2}">level1-2</a>
<a th:href="@{/level1/3}">level1-3</a>
<hr>
<h3>Level2</h3>
<a th:href="@{/level2/1}">level2-1</a>
<a th:href="@{/level2/2}">level2-2</a>
<a th:href="@{/level2/3}">level2-3</a>
<hr>
<h3>Level3</h3>
<a th:href="@{/level3/1}">level3-1</a>
<a th:href="@{/level3/2}">level3-2</a>
<a th:href="@{/level3/3}">level3-3</a>
<hr>
</body>
</html>
重启后,点击注销进入第三方确认界面并确认后,就会重新跳回第三方登录界面
在SecurityConfig自定义配置中,注销换成下面这样写,注销后就不会默认跳回第三方登录界面,而是会跳回首页了
//注销
http.logout().logoutSuccessUrl("/");
3.4、权限控制
后面有时间会更新待续。大概剩下两天的内容
Shiro
异步任务
邮箱任务
定时任务
SpringBoot集成Redis
分布式系统理论
什么是RPC
通信可以http和RPC:
HTTP是无状态且基于网络的。
RPC协议:
什么是RPC?
RPC【Remote Procedure Call)】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
可参考:https://www.jianshu.com/p/2accc2840a1b
RPC两个核心模块:通讯,序列化。
后面dubbo会做这件事RPC
Dubbo及Zookeeper的安装
什么是dubbo?
Apache Dubbo |'dxbau|是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,T以及服务自动注册和发现。
可参考官网的使用说明
https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/spring-boot/
async异步,sync同步。invoke调用为同步的。文章来源:https://www.toymoban.com/news/detail-683668.html
文章来源地址https://www.toymoban.com/news/detail-683668.html
Dubbo-admin安装测试
到了这里,关于SpringBoot笔记——(狂神说)——待续的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!