目录
12、SpringBoot:Mybatis + Druid 数据访问
15、分布式 Dubbo+Zookenper+SpringBoot
1、什么是SpringBoot?
- Spring Boot是由Pivotal团队提供的全新框架
- 其设计目的是用来简化新Spring应用的初始搭建以及开发过程。
- 该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
- 通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者
==SpringBoot基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。 ==
SpringBoot所具备的特征有:
1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。 [1] [2]
2、第一个SpringBoot程序
环境:
jdk1.8
maven 最新
spring 最新
原始方法从Spirng.io官网中下载
解压它
SpringBoot所有的依赖都是以:
spring-boot-starter 开头
第二种方式:
在HelloworldApplication 主入口的同级目录下创建包什么dao controller pojo service包等等
只有在它同级目录下创建包它才会去扫描。
启动项目:
成功访问:
如何打包?
双击package后会生成一个jar包这样就可以在任意地方执行。
如:
SpringBoot较SpringMVC来说,简化了配置
更改端口号:
更改banner
更改banner网站:https://www.bootschool.net/ascii
3、项目结构分析:
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类
2、一个 application.properties 配置文件
3、一个 测试类
4、一个 pom.xml
SpringBoot的底层机制
主启动类:
如:
@SpringBootApplication
public class HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloworldApplication.class, args);
}
}
点进入@SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
可以看到SpringBootAppliccation类下也有很多注解
@SpringBootConfiguration:SpringBoot的配置文件
@EnableAutoCofigutation:自动配置
@ComponentScant:扫描同级目录下的包,即跟主启动类同级目录下的包,该注解主要用来扫描@Component及其集成它的类装配到容器中(默认启动类包路径下).例如@Controller、@Service、@Repository等Bean声明注解.
先来看@SpringBootConfiguration这个注解,点进去
package org.springframework.boot;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
重要的注解有@Configuration,点进去
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
@Configuration,说明SpringBootAppliccation这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
@Component:证明这是一个组件,说白了SpringBootConfiguration是一个组件,他交给了Spring容器管理,启动类本身也是Spring中的一个组件(Bean)而已,负责启动应用!
@SpringBootConfiguration的小结:
@SpringBootApplication
-> @SpringBootConfiguration
-> @Configuration
-> @Component
通过这个注解可以得出一个结论:@SpringBootApplication
注解标注的主启动类就是一个配置类,启动类本身也是Spring中的一个组件(Bean)
再来看@EnableAutoConfiguration:开启自动配置功能,点进去
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
重要的有
@AutoConfigurationPackage:自动配置包,它负责加载当前包路径下所有的@Configuration配置Bean.该注解加载Bean至容器中发生于run方法的上下文准备完成后的刷新上下文过程中
@Import(AutoConfigurationImportSelector.class):导入自动配置选择器这个类
先看第一个,点进去
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class):引用了一个内部类Registrar
他与前面的@ComponentScan结合起来,在被@ComponentScan扫描到的包,来这里注册,
点进去(AutoConfigurationPackages)
现在点进他的内部类看看做了什么
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
registerBeanDefinitions方法: 理解为注册bean的定义
AnnotationMetadata metadata : 获取注解的元数据、简单来说就是可以通过这个来获取启动类的信息
BeanDefinitionRegistry registry:负责bean做注册的
我们先来看这个new PackageImports(metadata).getPackageNames().toArray(new String[0])
他是一个什么值
进入这个PackageImports
类这个类的构造方法
PackageImports(AnnotationMetadata metadata) {
//第一块
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
this.packageNames = Collections.unmodifiableList(packageNames);
}
可以看出获取到主启动类的类名、包括一些其他信息
获取@AutoConfigurationPackage
注解中的属性有没有值,通过第一个地方获取出来的值进行遍历添加到packageNames中,判断packageNames是否null、由于在@AutoConfigurationPackage
注解中没有添加属性值所以走的就是第三块,因此来获取包名、最后将包名存入packageNames的集合中,并返回给Registrar
类中的register
方法
register
方法—>bean的注册方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
尤于第一次运行肯定是不存在的所以走的else,就是将所获取包名的同级包以及子包注入将被扫描(特定注解)的类创建bean加入IOC容器中。
自动扫描包的最后做的就是获取启动类的文件位置、将其同包以及子包内的(类似被@Controller标注的)类注入到springioc容器当中
而@Import(AutoConfigurationImportSelector.class)
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
继续点入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());
}
继续点return中的loadSpringFactories方法
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
这个方法会去spring-boot-autoconfigure/META-INF/spring.factories中加载全部的配置文件的全类名:
但是它不会全部加载进去,而是根据@ConditionalOnClass这个注解来判断符合条件的包并引入相应的场景所需要的组件,注入到SpringIOC容器中
如:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
总结:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值;
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 然后进行自动配置工作;
- 整个javaEE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
- 有了自动配置类 , 免去了手动编写配置注入功能组件等的工作。
SpringApplication.run分析
分析该方法主要分两部分,一是SpringApplication的实例化,二是run方法的执行;
SpringApplication这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目;
2、查找并加载所有可用的初始化器 , 设置到initializers属性中;
3、找出所有的应用程序监听器,设置到listeners属性中;
4、推断并设置main方法的定义类,找到运行的主类.
run方法流程分析
run()的作用:
1、推断应用的类型是普通的项目还是Web项目;
2、查找并加载所有可用初始化器 , 设置到initializers属性中;
3、找出所有的应用程序监听器,设置到listeners属性中;
4、推断并设置main方法的定义类,找到运行的主类.
4、yaml语法学习
1、配置文件
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的(可以有多个,有优先级)
-
application.properties
- 语法结构 :key=value
-
application.yml
- 语法结构 :key:空格 value 对空格非常严格
*配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
2、yaml基础语法
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
注意:
- “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
- 比如 :name: “kk\n nh” 输出 :kk换行 nh
- ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
- 比如 :name: ‘kk\n nh’ 输出 :kk\n nh
基本语法例子:
注意@ConfigurationProperties注解:里面的属性比如person 是你在.yaml文件中创建的对象名
JSR303校验:
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;
若在使用@Eamil中如果爆红
则引入下面的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
eg:我们让person中的名字只能使用邮件的格式:
package com.hua.springboot02.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@Data
@ConfigurationProperties(prefix = "person")
@AllArgsConstructor
@NoArgsConstructor
@Validated
public class Pserson {
@Email(message = "错误啦宝贝!!")
private String name;
private int age;
private Date birth;
private Dog dog;
private List<Object> list;
private Map<Object,Integer>map;
}
合法条件下:
不合法:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
.......等等
除此以外,我们还可以自定义一些数据校验规则
5、多环境配置以及配置文件位置
1、多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
多配置文件:
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置
文件;
需要通过一个配置来选择需要激活的环境:
application.properties
#SpringBoot的多环境配置,可以选择激活哪一个配置
spring.profiles.active=dev
2、yaml的多文档块
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便!
application.yaml
server:
port:8081
spring:
profiles:
active: dev
---
server:
port:8082
spring:
profiles: dev
---
server:
port:8082
spring:
profiles: test
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
3、配置文件加载位置
springboot 启动会扫描以下位置的application.properties或者application.yaml文件作为SpringBoot的默认配置文件:
- 优先级1:项目路径下的config文件夹配置文件
- 优先级2:项目路径下配置文件
- 优先级3:资源路径下的config文件夹配置文件
- 优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
4、拓展,运维小技巧
指定位置加载配置文件,还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.prop
5、自动配置原理
首先先解释注解:
@EnableConfigurationProperties:自动配置属性
@ConfigurationProperties:绑定了一个文件,如上面的yaml语法中的基本语法例子,又如:
而我们的application.yaml 与 Spring.factory 的联系
在yaml配置文件中: 每一个Xxx开头都是一个 XxxAutoConfigeruation
比如我们用ActiveMQAutoConfiguration为例子:
而broker-url则对应activemq中的属性,当然它有默认的属性
而这些属性是在properties中获取的
点进去:
也就是说在自动装配时,它会去Xxx.properties中装配默认值,如果我们要自定义值就可以在SpringBoot的配置文件中改,即.yaml文件等。
也就是说在位们的配置文件中能配置的东西,都存在一个一般的规律
有一个Xxxautoconfiguration:默认值,xxxProperties和配置文件绑定,这样我们就可以使用自定义的配置啦:
eg:spring.factories中的
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,
点进去
点进去webproperties
再点进去Format
总结
- 当这个配置类生效;这个配置类就会给容器中添加各种组件;
- 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
- 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
- 配置文件能配置什么就可以参照某个功能对应的这个属性类
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,加载了这么多的配置类,但不是所有的都生效了。
那怎么才能知道哪些自动配置类生效?
这时候就要了解
@ConditionalOnXxx注解:
通过名字,可以理解为这种注解是用于判断的,满足条件是怎么样,点进去可以发现,他们有一个共同的注解@Conditional()
只有满足@ConditionalOnXxx()里面参数的条件,简单来说就是pom.xml中有导入的包或者导入的场景的,哪么这个类就会呗自动装配并交由spring管理
总结:这些自动装配的类是只有满足某些条件后才会自动装配。比如找到了某些相关基础类。
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
- Positive matches:(自动配置类启用的:正匹配)
- Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
- Unconditional classes: (没有条件的类)
以上就是自动装配的原理!
总结:
SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
如有错误请评论让我改正~~~
6、SpringBoot Web开发
1、Web开发探究
jar:webapp!
自动装配
- 向容器中自动配置组件 : Autoconfiguration
- 自动配置类,封装配置文件的内容:***Properties
2、静态资源处理
1、静态资源映射规则:
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;前往看看
在这个配置类中 有这么一个方法:addResourceHandlers 添加静态资源处理器
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});
}
});
}
}
源代码:比如所有的 /webjars/** , 都要去 classpath:/META-INF/resources/webjars/ 找对应的资源;
2、什么是webjars 呢?
webjars本质就是以jar包的方式引入我们的静态资源 , 以前要导入一个静态资源文件,现在直接导入即可。
使用SpringBoot需要使用Webjars,网站:https://www.webjars.org
要使用jQuery,我们只需要引入jQuery对应版本的pom依赖即可!
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,这里访问:http://localhost:8080/webjars/jquery/3.4.1/jquery.js
3、静态资源映射规则访问资源
找到staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,点进去看一下分析:
ResourceProperties可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。
得出结论,以下四个目录存放的静态资源可以被我们识别:
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
public static class Resources {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
在这个类中可以找到,优先级就是写的代码中的顺序
我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;
比如我们访问 http://localhost:8080/1.js , 它会去这些文件夹中寻找对应的静态资源文件;
得出结论:优先级:resouces>static(默认)》public
7、首页以及图标的定制
1、首页处理
搜索welcomePageHandlerMapping,找到相关Bean
private Resource getWelcomePage() {
String[] var1 = this.resourceProperties.getStaticLocations();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String location = var1[var3];
Resource indexHtml = this.getIndexHtml(location);
if (indexHtml != null) {
return indexHtml;
}
}
ServletContext servletContext = this.getServletContext();
if (servletContext != null) {
return this.getIndexHtml((Resource)(new ServletContextResource(servletContext, "/")));
} else {
return null;
}
}
private Resource getIndexHtml(String location) {
return this.getIndexHtml(this.resourceLoader.getResource(location));
}
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;
}
==欢迎页,静态资源文件夹下的所有 index.html 页面;被 /** 映射。
访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html
2、网站图标ico
1、关闭SpringBoot默认图标
application.properties
# 关闭默认图标
spring.mvc.favicon.enabled=false
2、自己放一个图标在静态资源目录下,静态资源 目录下
然后在Springboot的配置文件中关闭默认图标即可
8、Thymeleaf模板引擎
1、Thymeleaf是什么
首先,看一下官网的描述:Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
意思就是Thymeleaf是适用于Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。
2、Thymeleaf有何优势
简单点说,就是Thymeleaf提供一种优雅且高度可维护的模板创建方式,可缩小设计团队与开发团队之间的差距。Thymeleaf也已经从一开始就遵从Web标准,尤其是HTML5,这就允许创建一些完全验证的模板。
3、Thymeleaf 特点
- Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板 + 数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示;
- Thymeleaf 开箱即用的特性。它提供标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL 表达式效果,避免每天套模板、改 JSTL、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
- Thymeleaf 提供 Spring 标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
4、引入Thymeleaf
三方式:
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
Spring官方文档:找到我们对应的版本
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
Thymeleaf的自动配置类:ThymeleafProperties
@ConfigurationProperties(
prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/"; //前缀
public static final String DEFAULT_SUFFIX = ".html"; //后缀
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
private Charset encoding;
//.....
}
- 我们可以看到默认的前缀和后缀!
- 只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。
- 使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!
测试:controller类
@Controller
public class IndexController {
@RequestMapping("/test")
public String test(){
return "test";
}
}
templates目录下新建test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>test</h1>
</body>
</html>
8、Thymeleaf 语法学习
测试:
1、Controller层
@Controller
public class IndexController {
@RequestMapping("/test")
public String test(Model model){
//存入数据
model.addAttribute("msg","<h1>Hello SpringBoot!</h1>");
model.addAttribute("users", Arrays.asList("hua","你好帅"));
return "test";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--所有的html元素都可以被thymeleaf替换接管, th:元素名-->
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
<hr>
<h3 th:each="user:${users}" th:text="${user}"></h3>
</body>
</html>
3、启动项目测试!
大多数Thymeleaf属性允许将它们的值设置为或包含表达式,由于它们使用的方言,我们将其称为标准表达式。这些表达式可以有五种类型:
- ${...} : 变量表达式。
- *{...} : 选择表达式。
- #{...} : 消息 (i18n) 表达式。
- @{...} : 链接 (URL) 表达式。
- ~{...} : 片段表达式。
9、SpringMVC拓展
SpringBoot已经帮我们自动配置好了大部分的SpringMVC中的配置,但是拦截器,视图控制器等没有配置,这时候我们可以拓展自动配置
怎么进行扩展:
实现WebMvcConfigurer接口
springboot官方对于mvc的扩展时,建议我们实现WebMvcConfigurer接口,并且要将该实现类加入到容器中。
在以前比如 springboot1.x.x 的时候,我们可以继承WebMvcConfigurerAdapter这个抽象类来完成对mvc的扩展,但是这个类值·全部是对WebMvcConfigurer的空实现
并且WebMvcConfigurer接口中的方法全是default方法(java8之后支持在接口中定义default和static方法),所以我们可以只重写我们需要重写的方法:
我们可以复写这个接口中的default方法实现扩展,比如
package com.hua.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
@Configuration
//如果你想定制一些定制化的功能,只需要写这个组件,然后将他交给SpringBoot管理,SpringBoot就会帮我们自动装配
public class MyConfig implements WebMvcConfigurer {
@Bean
public ViewResolver getMyViewResolver(){
return new MyViewResolver();
}
//localhost:8080/hua 实际上跳转到test.html中
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hua").setViewName("/test");
}
}
//只要实现了ViewResolver接口的类,我们就可以把它看作视图解析器
//自定义自己的试图解析器
class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
实现了扩展自己的视图解析器和视图控制器
公式:如果看到了xxxConfigurer证明我们可以对xxx进行功能扩展
10.全面接管SpringMVC
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
不加注解之前,访问首页:
给配置类加上注解:@EnableWebMvc
我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;
当然,我们开发中,不推荐使用全面接管SpringMVC
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
1、这里发现它是导入了一个类,我们可以继续进去看
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
2、它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
3、我们来回顾一下Webmvc自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
重点:// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~
11、员工管理系统
1、员工管理系统:准备工作
导入我们的所有提供的资源!
pojo 及 dao 放到项目对应的路径下:
pojo:
package com.hua.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String name;
}
package com.hua.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@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层
package com.hua.Mapper;
import com.hua.pojo.Department;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class DepatmentDao {
//模拟数据
private static Map<Integer, Department> departmentMap;
static{
departmentMap=new HashMap<>();//创建一个部门类
departmentMap.put(101,new Department(101,"教学部"));
departmentMap.put(102,new Department(101,"市场部"));
departmentMap.put(103,new Department(101,"教研部"));
departmentMap.put(104,new Department(101,"运营部"));
departmentMap.put(105,new Department(101,"后勤部"));
}
//获得所有部门信息
public Collection<Department>getDepartments(){
return departmentMap.values();
}
//通过id得到部门
public Department getDepartmentById(Integer id){
return departmentMap.get(id);
}
}
package com.hua.Mapper;
import com.hua.pojo.Department;
import com.hua.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
public class EmployeeDao {
private static Map<Integer, Employee> employeeMap=null;
@Autowired
private static DepatmentDao depatmentDao;
static{
employeeMap=new HashMap<Integer,Employee>();
employeeMap.put(101,new Employee(1,"华","123",
1,depatmentDao.getDepartmentById(101),new Date()));
employeeMap.put(102,new Employee(1,"红草","123",
1,depatmentDao.getDepartmentById(102),new Date()));
employeeMap.put(103,new Employee(1,"湖东狗","123",
1,depatmentDao.getDepartmentById(103),new Date()));
employeeMap.put(104,new Employee(1,"遮浪耀","123",
1,depatmentDao.getDepartmentById(104),new Date()));
employeeMap.put(105,new Employee(1,"甲子佳","123",
1,depatmentDao.getDepartmentById(105),new Date()));
}
public static Integer id=106;
//增加一个员工
public void addEmployee(Employee employee){
if (employee.getId()==null){
employee.setId(id++);
}else {
throw new RuntimeException("改员工已经存在");
}
employee.setDepartment(depatmentDao.getDepartmentById(employee.getDepartment().getId()));
employeeMap.put(employee.getId(),employee);
}
//查询全部员工
public Collection<Employee>getEmployees(){
return employeeMap.values();
}
//通过id查员工
public Employee getEmployeeById(Integer id){
return employeeMap.get(id);
}
//删除员工通过id
public void deleteEmployeeById(Integer id){
employeeMap.remove(id);
}
}
导入完毕这些之后,还需要导入我们的前端页面,及静态资源文件!
- css,js等放在static文件夹下
- html放在templates文件夹下
2、员工管理系统:首页实现
访问首页
方式一:写一个controller实现!
package com.hua.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping({"/index","/"})
public String getIndex(){
return "index";
}
}
方式二:自己编写MVC的扩展配置
package com.hua.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
@Configuration
//如果你想定制一些定制化的功能,只需要写这个组件,然后将他交给SpringBoot管理,SpringBoot就会帮我们自动装配
public class MyConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index.html");
registry.addViewController("/index").setViewName("index.html");
}
解决资源导入的问题;
#关闭thmeleaf的缓存
spring.thymeleaf.cache=false
#设置后 thymeleaf中的@{..}自动变换为@{./hua....}
server.servlet.context-path=/hua
现在你访问localhost:8080 就不行了,需要访问localhost:8080/kk==
为了保证资源导入稳定,在所有资源导入的时候使用 th:去替换原有的资源路径!
1、先在所有需要使用到th:的html文件导入该配置
xmlns:th="http://www.thymeleaf.org"
2、修改所有本地静态资源的链
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
<img class="mb-4" th:src="@{/img/bootstrap-solid.png}" alt="" width="72" height="72">
3、员工管理系统:国际化
**第一步 :**编写国际化配置文件,抽取页面需要显示的国际化页面消息。
**第二步:**在resources资源文件下新建一个i18n目录,建立一个login.propetries文件,还有一个login_zh_CN.properties,login_en_US.properties,发现IDEA自动识别了我们要做国际化操作;文件夹变了
第三步:编写propertie文件,login.properties为默认的页面文字输出
第四步 :看一下SpringBoot对国际化的自动配置!
涉及到一个类: MessageSourceAutoConfiguration ,里面有一个方法,发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
public MessageSourceAutoConfiguration() {
}
@Bean
@ConfigurationProperties(prefix = "spring.messages") //我们的配置文件可以直接放在类路径下叫: messages.properties, 就可以进行国际化操作了
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) { //设置国际化文件的基础名
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
}
真实的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;
# 配置文件放置的真实位置
spring.messages.basename=i18n.login
第五步 : 去页面获取国际化的值;
查看Thymeleaf的文档,找到message取值操作为: #{…}。
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
<input type="checkbox" value="remember-me" >[[#{login.remember}]]
<button class="btn btn-lg btn-primary btn-block" type="submit" >[[#{login.btn}]]</button>
根据按钮自动切换中文英文!
在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器
在webmvc自动配置文件就可以看到SpringBoot默认配置了
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
prefix = "spring.mvc",
name = {"locale"})
public LocaleResolver localeResolver() { //容器中没有就自己配,有的话就用用户配置的
if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
} else {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
}
AcceptHeaderLocaleResolver 这个类中有一个方法
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale(); //默认的就是根据请求头带来的区域信息获取Locale进行国际化
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
写一个自己的LocaleResolver,可以在链接上携带区域信息!
package com.hua.config;
import org.springframework.web.servlet.LocaleResolver;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
// System.out.println(language);
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(language)){
String[] s = language.split("_");
locale = new Locale(s[0],s[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
在SpringMVC的扩展文件中注入,@Bean表示交给SpringBoot管理
写一个自己的LocaleResolver,可以在链接上携带区域信息!
修改一下前端页面的跳转连接;
<a class="btn btn-sm" th:href="@{/index(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index(l='en_US')}">English</a>
测试页面
4、员工管理系统:登陆功能实现
问题:输入任意用户名都可以登录成功!
原因:templates下的页面只能通过Controller跳转实现,而static下的页面是能直接被外界访问的,就能正常访问
把登录页面的表单提交地址写一个controller,所有表单标签都需要加上一个name属性
<form class="form-signin" th:action="@{/user/login}" >
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
编写对应的controller
package com.hua.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.thymeleaf.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class loginController {
@RequestMapping("/login")
public String login(
@RequestParam("username") String name,
@RequestParam("pas") String password,
Model model){
if(!empty && "123".equals(password)){
System.out.println(2);
return "dashboard";
}else {
System.out.println(1);
model.addAttribute("msg","密码错误或者用户名错误");
return "index";
}
}
}
登录失败的话,我们需要将后台信息输出到前台,可以在首页标题下面加上判断!
<!-- msg的消息为空 则不显示-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
将 Controller 的代码改为重定向;
//登录成功!防止表单重复提交,我们重定向
return "redirect:/main.html";
5、员工管理系统:登陆拦截器
定向成功之后!我们解决了之前资源没有加载进来的问题!后台主页正常显示!
但是又发现新的问题,我们可以直接登录到后台主页,不用登录也可以实现!
怎么处理这个问题呢?我们可以使用拦截器机制,实现登录检查!
1、自定义一个拦截器
这个定义和我们在SpringMVC中学的是一样的
package com.hua.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptot implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(request.getSession().getAttribute("loginUser")!=null){
return true;
}else {
request.setAttribute("msg","请先登录");
request.getRequestDispatcher("/index").forward(request,response);
}
return false;
}
}
写一个类去实现HandlerInterceptor,然后在扩展MVC的配置文件中加入,重写他的addInterceptors方法,addPathPatterns里面的参数是拦截的路径,而excludePathPatterns中的参数是不拦截的路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptot()).addPathPatterns("/**")
.excludePathPatterns("/index","/","/loging","/css/*","/img/*","/js/*");
}
这样做了之后就只有登录才可以进入main页面拉
6、员工管理系统:展示员工列表
主页点击Customers,显示列表页面;
1.将首页的侧边栏Customers改为员工管理
2.a链接添加请求
编写处理请求的controller层
@RequestMapping("/goToList")
public String goToList(Model model){
Collection<Employee> employees = employeeDao.getEmployees();
model.addAttribute("epms",employees);
return "list";
}
Thymeleaf 公共页面元素抽取
1.抽取公共片段 th:fragment 定义模板名
2.引入公共片段 th:insert 插入模板名:插入一个div
3.引入公共片段 th:replace插入模板名:替换元素插入
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--顶部侧边栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
</li>
</ul>
</nav>
<!--左侧侧边栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a th:class="${active=='main' ?' nav-link active' : 'nav-link'}" th:href="@{/main}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
首页 <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
<polyline points="13 2 13 9 20 9"></polyline>
</svg>
Orders
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Products
</a>
</li>
<li class="nav-item">
<a th:class="${active=='list'? 'nav-link active' :'nav-link'}" th:href="@{/goToList}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
员工管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Reports
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
Integrations
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reports</span>
<a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
</a>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Current month
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Last quarter
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Social engagement
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Year-end sale
</a>
</li>
</ul>
</div>
</nav>
</html>
抽取后的主页:
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<!--顶部侧边栏-->
<div th:replace="~{common/common::topbar}"></div>
<!--侧边栏-->
<div class="container-fluid">
<div class="row">
<div th:replace="~{common/common::sidebar(active='main')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
</div>
<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
</div>
</div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Share</button>
<button class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
This week
</button>
</div>
</div>
<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}" ></script>
<script type="text/javascript" th:src="@{/js/popper.min.js}" ></script>
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}" ></script>
<!-- Icons -->
<script type="text/javascript" th:src="@{/js/feather.min.js}" ></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" th:src="@{/js/Chart.min.js}" ></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
抽取后的员工列表页面:
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<!--顶部侧边栏-->
<div th:replace="~{common/common::topbar}"></div>
<!--侧边栏-->
<div class="container-fluid">
<div class="row">
<div th:replace="~{common/common::sidebar(active='list')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a th:href="@{/toAdd}" class="btn btn-sm btn-success">添加员工</a></h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>operation</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${epms}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getName()}"></td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}"></td>
<td>
<a th:href="@{/update/}+${emp.getDepartment().getId()}" class="btn btn-sm btn-primary">修改</a>
<a href="#" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>
<!-- Icons -->
<script type="text/javascript" th:src="@{/js/feather.min.js}"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" th:src="@{/js/Chart.min.js}"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
高亮设置
list.html
<!--侧边-->
<div th:replace="~{commons/commons::sidebar(active='list')}"></div>
dashboard.html
<!--侧边-->
<div th:replace="~{commons/commons::sidebar(active='main')}"></div>
抽取页面接收的active:list
抽取页面接收的active:top
遍历我们的员工信息
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>operation</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${epms}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender==0?'女':'男'}"></td>
<td th:text="${emp.getDepartment().getName()}"></td>
<td th:text="${#dates.format(emp.birth,'yyyy-MM-dd')}"></td>
<td>
<a th:href="@{/update/}+${emp.getDepartment().getId()}" class="btn btn-sm btn-primary">修改</a>
<a href="#" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
</tbody>
</table>
7、员工管理系统:添加员工信息
1.在list.html页面中将添加员工信息改为超链接
<h2><a th:href="@{/toAdd}" class="btn btn-sm btn-success">添加员工</a></h2>
2.编写对应的controller层
@GetMapping("/toAdd")
public String goToAdd(Model model){
Collection<Employee> employees = employeeDao.getEmployees();
model.addAttribute("epms",employees);
return "add";
}
3.添加前端页面;复制list页面,修改即可
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<!--顶部侧边栏-->
<div th:replace="~{common/common::topbar}"></div>
<!--侧边栏-->
<div class="container-fluid">
<div class="row">
<div th:replace="~{common/common::sidebar(active='list')}"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/toAdd}" method="post">
<table class="table table-striped sortable">
<thead>
</thead>
<tbody>
<tr>
<th>LastName</th>
<td><input type="text" name="lastName"/><span class="error"/></td>
</tr>
<tr>
<th>Email</th>
<td><input type="email" name="email"/></td>
</tr>
<tr>
<th>Gender</th>
<td>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</td>
</tr>
<tr>
<th>department</th>
<td>
<!--我们在controller接收到的时一个Employee 所以我们需要提交的是其中的一个属性 所以是department.id-->
<select class="form-control" name="department.id">
<option th:each="emp:${epms}"
th:text="${emp.getDepartment().getName()}"
th:value="${emp.getDepartment().getId()}"></option>
</select>
</td>
</tr>
<tr>
<th>Birth</th>
<td><input type="text" name="birth"/></td>
</tr>
<tr>
<td></td>
<td>
<input class="btn btn-success" type="submit" value="添加"/>
<input class="btn btn-danger" type="reset" value="重置"/>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</form>
</main>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}"></script>
<script type="text/javascript" th:src="@{/js/popper.min.js}"></script>
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}"></script>
<!-- Icons -->
<script type="text/javascript" th:src="@{/js/feather.min.js}"></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" th:src="@{/js/Chart.min.js}"></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
4.部门信息下拉框应该选择的是我们提供的数据,所以我们要修改一下前端和后端
@PostMapping("/toAdd")
public String addEmp(HttpServletRequest httpServle,Employee employee){
//自动封装保存数据
employeeDao.addEmployee(employee);
return "redirect:goToList";
}
<!--我们在controller接收到的时一个Employee 所以我们需要提交的是其中的一个属性 所以是department.id-->
<select class="form-control" name="department.id">
<option th:each="emp:${epms}"
th:text="${emp.getDepartment().getName()}"
th:value="${emp.getDepartment().getId()}"></option>
</select>
5、接收前端传过来的属性,将它封装成为对象!首先需要将前端页面空间的name属性编写完毕!然后编写controller层
@PostMapping("/toAdd")
public String addEmp(HttpServletRequest httpServle,Employee employee){
//自动封装保存数据
employeeDao.addEmployee(employee);
return "redirect:goToList";
}
6、在application.properties修改时间格式
# 日期格式化
spring.mvc.date-format=yyyy-MM-dd
8、员工管理系统:修改员工信息
实现员工修改功能,需要实现两步;
1.点击修改按钮,去到编辑页面,我们可以直接使用添加员工的页面实现
2.显示原数据,修改完毕后跳回列表页面!
首先修改跳转链接的位置;
<a th:href="@{/update/}+${emp.getDepartment().getId()}" class="btn btn-sm btn-primary">修改</a>
编写对应的controller
@GetMapping("/update/{id}")
//rstful风格参数要用@PathVariable
public String update(Model model,@PathVariable("id") Integer id){
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp",employee);
return "udt";
}
将add页面复制一份,改为update页面;需要修改页面,将我们后台查询数据回显
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/toAdd}" method="post">
<table class="table table-striped sortable">
<thead>
</thead>
<tbody>
<tr>
<th>LastName</th>
<td><input type="text" name="lastName"/><span class="error"/></td>
</tr>
<tr>
<th>Email</th>
<td><input type="email" name="email"/></td>
</tr>
<tr>
<th>Gender</th>
<td>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</td>
</tr>
<tr>
<th>department</th>
<td>
<!--我们在controller接收到的时一个Employee 所以我们需要提交的是其中的一个属性 所以是department.id-->
<select class="form-control" name="department.id">
<option th:each="emp:${epms}"
th:text="${emp.getDepartment().getName()}"
th:value="${emp.getDepartment().getId()}"></option>
</select>
</td>
</tr>
<tr>
<th>Birth</th>
<td><input type="text" name="birth"/></td>
</tr>
<tr>
<td></td>
<td>
<input class="btn btn-success" type="submit" value="添加"/>
<input class="btn btn-danger" type="reset" value="重置"/>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</form>
</main>
</main>
日期显示不完美,可以使用日期工具,进行日期的格式化!
<td><input th:value="${#dates.format(emp.birth,'yyyy-MM-dd')}" type="text" name="birth"/></td>
修改表单提交的地址:
<form th:action="@{/update}" method="post">
编写对应的controller
@PostMapping("/update")
public String updateEmp(Employee employee){
employeeDao.addEmployee(employee);
return "redirect:goToList";
}
现页面提交的没有id;我们在前端加一个隐藏域,提交id;
<input type="hidden" name="id" th:value="${emp.id}">
9、员工管理系统:删除员工
list页面,编写提交地址
<a th:href="@{/del/}+${emp.getId()}" class="btn btn-sm btn-danger">删除</a>
编写Controller
@RequestMapping("/del/{id}")
public String delEmp(@PathVariable("id") Integer id){
employeeDao.deleteEmployeeById(id);
return "redirect:/goToList";
}
10、员工管理系统:404错误页面
在template中创建一个Error文件夹
500等错误均是这样,
11、员工管理系统:注销功能
<a class="nav-link" th:href="@{/logOut}">Sign out</a>
对应的controller
@RequestMapping("/logOut")
public String logOug(HttpSession httpSession){
httpSession.invalidate();
return "redirect:index";
}
12、SpringBoot:Mybatis + Druid 数据访问
1、简介
对于数据访问层,无论是SQL(关系型数据库) 还是NOSQL(非关系型数据库),SpringBoot 底层都是采用 SpringData 的方式进行统一处理。
Spring Boot 底层都是采用 SpringData 的方式进行统一处理各种数据库,SpringData也是Spring中与SpringBoot、SpringCloud 等齐名的知名项目。
SpingData 官网:https://spring.io/projects
数据库相关的启动器 : 可以参考官方文档:https://docs.spring.io/springboot/docs/2.1.7.RELEASE/reference/htmlsingle/#using-boot-starter
2、JDBC
项目建好之后,发现自动帮我们导入了如下的启动器:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
实现数据库的访问
-
先连接上数据库 , 直接使用IDEA连接即可【操作】
-
SpringBoot中,我们只需要简单的配置就可以实现数据库的连接了;
我们使用yaml的配置文件进行操作!
spring:
datasource:
username: mysqlChen
password: 1234
url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
配置完这一些东西后,就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;我们去测试类测试一下
package com.hua;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.test.context.SpringBootTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SpringBootTest
class SpringBoot04ApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
Connection connection = dataSource.getConnection();
System.out.println(dataSource.getClass());
System.out.println(connection);
connection.close();
}
}
输出结果:可以看到它默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置
全局搜索一下,找到数据源的所有自动配置都在 :DataSourceProperties 文件下;这里自动配置的原理以及能配置哪些属性?
Spring Boot 2.1.7 默认使用 com.zaxxer.hikari.HikariDataSource 数据源,
而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;
3、CRUD操作
1、有了数据源(com.zaxxer.hikari.HikariDataSource),可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用连接和原生的 JDBC 语句来操作数据库
2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即 org.springframework.jdbc.core.JdbcTemplate。
3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
5、JdbcTemplate 的自动配置原理是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration 类
JdbcTemplate主要提供以下几类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForxxx方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
crontroller
package com.hua.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class Test {
//查询
@Autowired
JdbcTemplate jdbcTemplate ;
@RequestMapping("/select")
public List<Map<String,Object>> getAll(){
String sql = "select * from book.user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
@RequestMapping("/add")
public String add(){
String sql = "insert into book.user(name,age,gender) values('陈俊华',21,'男')";
int update = jdbcTemplate.update(sql);
return "ok";
}
@RequestMapping("/update")
public String update(){
String sql = "update book.user set name=?,age=?,gender=? where name=?";
int update = jdbcTemplate.update(sql,"陈俊华",18,"男","陈晓佳");
return "ok";
}
@RequestMapping("/del/{name}")
public String del(@PathVariable("name") String name){
String sql = "delete from book.user where name =?";
int update = jdbcTemplate.update(sql,name);
return "ok";
}
}
测试成功!
原理探究 :
org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 数据源配置类作用 :根据逻辑判断之后,添加数据源;
SpringBoot默认支持以下数据源:
- com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,默认使用此数据源)
- org.apache.tomcat.jdbc.pool.DataSource
- org.apache.commons.dbcp2.BasicDataSource
可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。默认情况下,它是从类路径自动检测的。
4、自定义数据源 DruidDataSource
DRUID 简介
- Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP、PROXOOL 等 DB 池的优点,同时 加入了日志监控。
- Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
- Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们 来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
*com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:*
引入数据源
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
切换数据源;
之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,可以通过 spring.datasource.type 指定数据源。
配置其他参数:
spring:
datasource:
username: mysqlChen
password: 1234
url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
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
1、配置 Druid 数据源监控
Druid 数据源具有监控的功能,并提供了一个web界面方便用户查看,类似安装路由器 时,它也提供了一个默认的 web 页面。 所以第一步需要设置 Druid 的后台管理页面,比如登录账号、密码等配置后台管理
这里和SpringMVC扩展一样,写一个config包在创建一个config类
参数尽量和源码保持一致,以免导入失败
package com.hua.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
//配置是死的 根据自身条件更改即可
@Configuration
public class DruidConfig {
//关联application.yaml中的spring.datasource
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
//测试访问! http://localhost:8080/druid/login.html
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean registra = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParams = new HashMap<>();
initParams .put("loginUsername", "admin"); //后台管理界面的登录账号 key值不能变
initParams .put("loginPassword", "123456"); //后台管理界面的登录密码 key值不能变
//后台允许谁可以访问
initParams .put("allow", "localhost");//表示只有本机可以访问
//initParams .put("allow", ""):为空或者为null时,表示允许所有访问
initParams .put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams .put("hua", "192.168.14.20");表示禁止此ip访问
registra.setInitParameters(initParams);
return registra;
}
}
测试访问! http://localhost:8080/druid/login.html
2、配置 Druid web 监控 filter
这个过滤器的作用就是统计 web 应用请求中所有的数据库信息,比如 发出的 sql 语句,sql 执行的时间、请求次数、请求的 url 地址、以及seesion 监控、数据库表的访问次数 等等。
package com.hua.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
//配置是死的 根据自身条件更改即可
@Configuration
public class DruidConfig {
//关联application.yaml中的spring.datasource
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
@Bean
//测试访问! http://localhost:8080/druid/login.html
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean registra = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
HashMap<String, String> initParams = new HashMap<>();
initParams .put("loginUsername", "admin"); //后台管理界面的登录账号 key值不能变
initParams .put("loginPassword", "123456"); //后台管理界面的登录密码 key值不能变
//后台允许谁可以访问
initParams .put("allow", "localhost");//表示只有本机可以访问
//initParams .put("allow", ""):为空或者为null时,表示允许所有访问
initParams .put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams .put("hua", "192.168.14.20");表示禁止此ip访问
registra.setInitParameters(initParams);
return registra;
}
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求?
HashMap<String, String> initParams = new HashMap<>();
//这些东西不进行统计
initParams .put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
return bean;
}
}
如上图所示。
SpringBoot集成Mybatis
导入mybatis依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
在application.yaml中配置数据库信息,记住如果你用的是idea,要先链接数据库
spring:
datasource:
username: mysqlChen
password: 1234
url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.hua.pojo
mybatis: mapper-locations: classpath:mybatis/mapper/*.xml 表示mapper的xml在那个路径下 type-aliases-package: com.hua.pojo 给实体类取别名
配置完要先测试一下能不能正常获取链接再继续编写代码
实体类:
package com.hua.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private int age;
private String gender;
}
配置mapper接口:@Mapper:将Mapper接口类交给Sprinig进行管理
方式一:使用@Mapper注解
优点:粒度更细
缺点:直接在Mapper接口类中加@Mapper注解,需要在每一个mapper接口类中都需要添加@Mapper注解,较为繁琐
方式二:使用@MapperScan注解:x写在主启动类上
通过@MapperScan可以指定要扫描的Mapper接口类的包路径
package com.hua.mapper;
import com.hua.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserMapper {
List<User> queryAll();
int add(User user);
int delById(int age);
int update(User user);
}
mapper的xml文件放在resource文件夹下:
<?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.hua.mapper.UserMapper">
<select id="queryAll" resultType="User">
select * from book.user;
</select>
<insert id="add" parameterType="User">
insert into book.user(name,age,gender) value (#{name},#{age},#{gender})
</insert>
<update id="update" parameterType="User">
update book.user set name=#{name},age=#{age},gender=#{gender} where age=#{age};
</update>
<delete id="delById" parameterType="User">
delete from book.user where age=#{age};
</delete>
</mapper>
Service层:
package com.hua.service;
import com.hua.pojo.User;
import org.springframework.stereotype.Service;
import java.util.List;
public interface UserService {
List<User> queryAll();
int add(User user);
int delById(int id);
int update(User user);
}
serviceImpl:
package com.hua.service;
import com.hua.mapper.UserMapper;
import com.hua.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public List<User> queryAll() {
return userMapper.queryAll();
}
@Override
public int add(User user) {
return userMapper.add(user);
}
@Override
public int delById(int id) {
return userMapper.delById(id);
}
@Override
public int update(User user) {
return userMapper.update(user);
}
}
Controller:
package com.hua.contrller;
import com.hua.pojo.User;
import com.hua.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/query")
public List<User> queryAll(){
return userService.queryAll();
}
@RequestMapping("/update/{name}/{age}/{gender}")
public String update(@PathVariable("name") String name, @PathVariable("age")
int age,@PathVariable("gender") String gender, User user){
user.setName(name);
user.setAge(age);
user.setGender(gender);
userService.update(user);
return "OK";
}
@RequestMapping("/add/{name}/{age}/{gender}")
public String add(@PathVariable("name") String name, @PathVariable("age")
int age,@PathVariable("gender") String gender, User user){
user.setName(name);
user.setAge(age);
user.setGender(gender);
userService.update(user);
userService.add(user);
return "OK";
}
@RequestMapping("/del/{age}")
public String del(@PathVariable("age") int age){
userService.delById(age);
return "ok";
}
}
以上就是mybatis集成SpringBoot
13.SpringSecurity (安全)
在web开发中,安全第一位! 过滤器, 拦截器等
做网站:安全应该在什么时候考虑?设计之初!
●漏洞,隐私泄露
●架构一旦确定
shiro、SpringSecurity: 很像除了类不一样,名字不一样;
认证,授权(vip1, vip2, vip3)
功能权限
●访问权限
●菜单权限
… 拦截器,过滤器:大量的原生代码比较冗余
mvc- spring-springboot-框架思想
AOP :横切~配置类
1、SpringSecurity 简介
- Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大
- 的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实
- 现强大的安全管理!
记住几个类:
- WebSecurityConfigurerAdapter: 自定义Security策略
- AuthenticationManagerBuilder: 自定义认证策略
- @EnableWebSecurity: 开启WebSecurity模式,@Enablexxx 开启某个功能
Spring Security的两个主要目标是“认证”和“授权”(访问控制)。
- “认证”(Authentication)
- "授权”(Authorization
这个概念是通用的,不是只在SpringSecurity中存在。
参考官网: https://spring.io/projects/spring-security.
1、SpringSecurity环境搭建
引入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
写一个config类去继成 WebSecurityConfigurerAdapter,实现其两个核心方法
package com.hua.config;
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;
@EnableWebSecurity
public class config extends WebSecurityConfigurerAdapter {
//链式编程
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人都可以访问
//level1要VIP1 2要 vip2 3要vip3
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限会默认跳到登陆页面,需要开启登录的页面
//login
//http.formLogin()
//定制登录页
//http.formLogin().usernameParameter().passwordParameter().loginPage("/Login"); 设置前端发过来的参数别名
http.formLogin().loginPage("/Login");
//定制注销后跳转的页面
http.logout().logoutSuccessUrl("/");
//防止跨站攻击 SpringBoot默认帮我们开启 不关闭他会报错
http.csrf().disable();
// http.logout();
// 记住账号密码
// http.rememberMe();
// 自定义记住我
http.rememberMe().rememberMeParameter("remenber");
}
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//如果不给他一个解码,他就会报错 spring版本2.2.+
//而2.1.- 可以完美运行
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("hua").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("hudonggou").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
.and()
.withUser("hongcao").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
controller
package com.hua.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class Test {
@RequestMapping({"/","/inedx"})
public String toIndex(){
return "index";
}
@RequestMapping({"/Login"})
public String toLogin(){
return "views/login";
}
// @RequestMapping("/login")
// public String login(){
// return "index";
// }
@RequestMapping("/level1/{id}")
public String toLevel1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String toLevel2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String toLevel3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
14、Shiro(安全)
1、Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Shiro可以非常容易的开发出足够好的应用,不仅可以用在JavaSE环境,也可以用在JavaEE环境。
下载地址: http://shiro.apache.org/
2、Shiro有哪些功能?
- Authentication: 身份认证、登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操
- 作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
- Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;
- 会话可以是普通的avaSE环境,也可以是Web环境;
- Cnyptography: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
- Web Support: Web支持,可以非常容易的集成到Web环境;
- Caching: 缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
- Concurrency: Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一 个线程,能把权限自动的传
- 播过去
- Testing:提供测试支持;
- Run As: 允许一 个用户假装为另- -个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3、Shiro架构(外部)
- subject: 应用代码直接交互的对象是Subject, 也就是说Shiro的对外API核心就是Subject, Subject代表了当前的用户,这个用户不-定是一个具体的人, 与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager; Subject其实是一 个门面,SecurityManageer 才是实际的执行者
- SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色
- Realm: Shiro从Realm获取安全数据 (如用户,色,权限),就是说SecurityManager 要验证用户身份,
- 那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看成DataSource;
SpringBoot集成Shiro
导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.8.0</version>
</dependency>
<!-- configure logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
2、配置文件
resources目录下
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini,需要安装ini插件:在idea中可以下载
Quickstart
ini文件
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
quicklystart代码
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
//日志
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// The easiest way to create a Shiro SecurityManager with configured
// realms, users, roles and permissions is to use the simple INI config.
// We'll do that by using a factory that can ingest a .ini file and
// return a SecurityManager instance:
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
// for this simple example quickstart, make the SecurityManager
// accessible as a JVM singleton. Most applications wouldn't do this
// and instead rely on their container configuration or web.xml for
// webapps. That is outside the scope of this simple quickstart, so
// we'll just do the bare minimum so you can continue to get a feel
// for things.
SecurityUtils.setSecurityManager(securityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// get the currently executing user:
//获取当前的用户对象 Subject
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
//通过当前对象获取当前用户的Session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
//将aValue的session保存在someKey中
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Subject==》session [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
//判断当前的用户是否被认证
if (!currentUser.isAuthenticated()) {
//Token 令牌
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token); //执行 登录操作
} catch (UnknownAccountException uae) {//未知账号
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {//密码错误
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { //锁定账号
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) { //认证异常
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
//获取当前用户信息
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
//测试角色 判断当前用户是什么角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
//简单 粗粒度
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//细粒度
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
//注销
currentUser.logout();
//结束
System.exit(0);
}
}
这些功能在Spring-Secutiry都有,总结:
//获取当前的用户对象 Subject
Subject currentUser = SecurityUtils.getSubject();
//通过当前对象获取当前用户的Session
Session session = currentUser.getSession();
//判断当前的用户是否被认证
currentUser.isAuthenticated()
//获取当前用户信息
currentUser.getPrincipal()
//测试角色 判断当前用户是什么角色
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
//注销
currentUser.logout();
6、SpringBoot整合Shiro环境搭建
导入相关依赖
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.8.0</version>
</dependency>
<!--shiro整合spring的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.8.0</version>
</dependency>
<!--thymeleaf模板,我们都是基于3.x开发-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
注意测试SpringBoot环境是否搭建成功
首先写一个config类:
package com.hua.config;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.session.DefaultWebSessionManager;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//3.ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("security") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
Map<String, String> map = new LinkedHashMap<>();
/*
* anon:无需认证就可以访问
* authc:必须认证才可以范文
* user:必须拥有记住我功能才可以访问
* perms:拥有对某个资源的权限才可以访问
* role:拥有某个角色权限才能访问
* */
// map.put("*","anon");
//授权,没有权限会跳转到无法访问页面
//亲测,这样只会生效下面的
map.put("/user/add", "perms[user:add]");
// map.put("/user/add", "perms[user:all]");
map.put("/user/update", "perms[user:update]");
// map.put("/user/update", "perms[user:all]");
map.put("/user/*", "authc");
factoryBean.setFilterChainDefinitionMap(map);
factoryBean.setLoginUrl("/toLogin");
//未授权页面
factoryBean.setUnauthorizedUrl("/mistake");
return factoryBean;
}
//2.defalutWebSecurityManage
@Bean
@Qualifier("security")
public DefaultWebSecurityManager securityManager(@Qualifier("getRealm") Realm getRealm){
DefaultWebSecurityManager securityManager =
new DefaultWebSecurityManager();
securityManager.setRealm(getRealm);
return securityManager;
}
//1.realm
@Bean
public Realm getRealm(){
return new MyRealm();
}
}
自定义Realm类:继承AuthorizingRealm
package com.hua.config;
import com.hua.pojo.User;
import com.hua.service.UserService;
import jdk.nashorn.api.scripting.ScriptUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class MyRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
// 授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了1");
// 有个很像的要注意
SimpleAuthorizationInfo
info = new SimpleAuthorizationInfo();
//拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
//还可以授予权限
if(user.getName().equals("hua")){
info.addStringPermission("user:update");
}
info.addStringPermission(user.getPermit());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了5");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
//链接真实数据库
User user = userService.queryByname(username);
System.out.println(username);
// System.out.println(username);
if(username==null){//没有这个人
return null;//UnknownAccountException
}
if(!username.equals(user.getName())){
// System.out.println(1);
return null;
}
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("userName",user.getName());
//密码可以加密 MD5/MD5盐值(更深)
// 密码认证不用我们做 shiro做
return new SimpleAuthenticationInfo(user,user.getPsd(),"");
}
}
静态页面:
index
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<hr>
<div th:if="${session.userName==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
login
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<p th:text="${msg}" style="color: red">
<form th:action="@{/login}">
账号:<input type="text" name="username">
<br>
密码:<input type="password" name="password">
<br>
<input type="submit" value="登录">
</form>
</body>
</html>
add
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
add
</body>
</html>
update
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
update
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
未经授权无法访问
</body>
</html>
controller
package com.hua.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class getRequest {
@RequestMapping("/")
public String getIndex(){
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, Model model){
Subject subject = SecurityUtils.getSubject();
// System.out.println(username);
// System.out.println(password);
UsernamePasswordToken token = new UsernamePasswordToken(username,
password);
try {
subject.login(token);//执行登录方法,如果没有异常就正常登录
return "index";
} catch (UnknownAccountException e) {//账号错误抛出这个
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){//密码错误抛出
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequestMapping("/mistake")
public String getError() {
return "error";
}
}
实体类:
package com.hua.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
private String psd;
private int id;
private String permit;
}
mapper
package com.hua.mapper;
import com.hua.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface userMapper {
User queryByname(String username);
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hua.mapper.userMapper">
<select id="queryByname" resultType="User">
select * from book.useraccount where name = #{name}
</select>
</mapper>
Service
package com.hua.service;
import com.hua.pojo.User;
public interface UserService {
User queryByname(String username);
}
接口
package com.hua.service;
import com.hua.pojo.User;
public interface UserService {
User queryByname(String username);
}
配置文件
spring:
datasource:
username: mysqlChen
password: 1234
url: jdbc:mysql://localhost:3306/book?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
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
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.hua.pojo
13、Swagger
1、Swagger的作用和概念
官方地址:https://swagger.io/
Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务以及 集成Swagger自动生成API文档。
Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。
1、Swagger 的优势
- 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
- 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。
2、SwaggerUI 特点
- 无依赖 UI可以在任何开发环境中使用,无论是本地还是在Web端中。
- 人性化允许最终开发人员轻松地进行交互,并尝试API公开的每个操作,以方便使用。
- 易于浏览归类整齐的文档可快速查找并使用资源和端点。
- 所有浏览器支持 Swagger UI 在所有主要浏览器中均可使用,以适应各种可能的情况。
- 完全可定制 通过完整的源代码访问方式以所需方式设置和调整Swagger UI。
- 完整的OAS支持 可视化Swagger 2.0或OAS 3.0中定义的API
2、SpringBoot集成Swagger
引入依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
测试是否运行成功
@RestController
public class HelloController {
@RequestMapping(value = "/hello")
public String Hello(){
return "Hello Swgger!";
}
}
4、要使用Swagger,需要编写一个配置类SwaggerConfig来配置 Swagger
@EnableSwagger2// 开启Swagger2的自动配置
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
}
访问测试 :http://localhost:8080/swagger-ui.html
如果用的是2.6.x以上的版本还要在配置文件修改路径
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
3、配置Swagger
1、Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger
package com.hua.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
//配置了Swagger的Docket的bean实例
@Bean
public Docket docket(Environment environment){
// Environment environment:在配置文件中获取启动环境
Profiles of = Profiles.of("dev", "test");
//判断配置环境是否是dev或者test 若是则是true 若不是则为false
//如果我们只希望在开发的时候开启swagger 而在发布的时候不开启
//判断是否为开发环境boolean b = environment.acceptsProfiles(of);把b作为enable的实参传进去
boolean b = environment.acceptsProfiles(of);
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
// enable()是否开启swagger
.enable(true)
.groupName("华")
.select()
// any() 扫描全部
// none() 全部不扫描
// basePackage() 根据路径扫描
// withClassAnnotation() 根据类注解扫描
// withMethodAnnotation() 根据方法注解扫描
.apis(RequestHandlerSelectors.basePackage("com.hua.controller"))
// .paths(PathSelectors.ant("/hua/**"))//过滤什么路径 只扫描com.hua.controller带有hua的controller
.build();
}
@Bean
public Docket docket1(Environment environment){
return new Docket(DocumentationType.SWAGGER_2).groupName("波");
}
@Bean
public Docket docket2(Environment environment){
return new Docket(DocumentationType.SWAGGER_2).groupName("湖东狗");
}
//配置swgger信息Bean
public ApiInfo apiInfo(){
return new ApiInfo("陈俊华的博客","这个人很懒什么都没有留下"
,"1.0","www.baidu.com",new Contact("陈俊华","www.baidu.com" ,"407909756@qq.com"),"H工作室","www.HJLove.com",new ArrayList<>());
}
}
每一个这个对应一个分组的成员
@Configuration
@EnableSwagger2 // 开启Swagger2的自动配置
public class SwaggerConfig {
//配置了Swagger的Docket的bean实例
@Bean
public Docket docket(Environment environment){
return new Docket(DocumentationType.SWAGGER_2);
}
这个是测试配置文件,可以用这个配合enable,是否开启swagger
//判断配置环境是否是dev或者test 若是则是true 若不是则为false //如果我们只希望在开发的时候开启swagger 而在发布的时候不开启 //判断是否为开发环境boolean b = environment.acceptsProfiles(of);把b作为enable的实参传进去在我们实际的运行过程中是不会开启这个模式的,毕竟给用户看到接口不好。
4、实体配置
package com.hua.pojo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
//@Api(注释)
@Api("我是注释")
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
private String name;
@ApiModelProperty("密码")
private String pas;
public User() {
}
public User(String name, String pas) {
this.name = name;
this.pas = pas;
}
@ApiOperation("得到名字")
public String getName() {
return name;
}
@ApiOperation("设置名字")
public void setName(String name) {
this.name = name;
}
@ApiOperation("得到密码")
public String getPas() {
return pas;
}
@ApiOperation("设置名字")
public void setPas(String pas) {
this.pas = pas;
}
}
//只要接口中,返回值存在实体类,它就会被扫描到Swagger中
@PostMapping(value = "/user")
public User user(){
return new User();
}
注解详解
@Api:用在类上,说明该类的作用。
- tags:可以使用tags()允许您为操作设置多个标签的属性,而不是使用该属性。
- description:可描述描述该类作用。
@ApiOperation:注解来给API增加方法说明。
- value 接口说明
- httpMethod 接口请求方式
- response 接口返回参数类型
- notes 接口发布说明
@ApiImplicitParams : 用在方法上包含一组参数说明。
可以包含一个或多个@ApiImplicitParam
@ApiImplicitParam:用来注解来给方法入参增加说明。
- name :参数名。
- value : 参数的具体意义,作用。
- required : 参数是否必填。
- dataType :参数的数据类型。
- paramType :查询参数类型,这里有几种形式:
类型 | 作用 |
---|---|
path | 以地址的形式提交数据 |
query | 直接跟参数完成自动映射赋值 |
body | 以流的形式提交 仅支持POST |
header | 参数在request headers 里边提交 |
form | 以form表单的形式提交 仅支持POST |
@ApiResponses:用于表示一组响应
可以包含一个或多个@ApiResponses
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
- code:数字,例如400
- message:信息,例如"请求参数没填好"
- response:抛出异常的类
@ApiModel:描述一个Model的信息(一般用在请求参数无法使用@ApiImplicitParam注解进行描述的时候)
- 用于类 ;表示对类进行说明,用于参数用实体类接收
- value–表示对象名
- description–描述
@ApiModelProperty:描述一个model的属性
用于方法,字段 ,表示对model属性的说明或者数据操作更改
- value:字段说明
- name:重写属性名字
- dataType:重写属性类型
- required:是否必填
- example:举例说明
- hidden:隐藏
5、其他皮肤
<!-- 换肤-->
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
访问 http://localhost:8080/doc.html
14、任务
1、异步任务
服务器响应时间长,客户端体验不好
模拟代码
service层
package com.hua.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class Asyn{
@Async//@Async:告诉spring这是一个异步方法
public void getInfo(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据处理中");
}
}
package com.hua.controller;
import com.hua.service.Asyn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class controller {
@Autowired
com.hua.service.Asyn Asyn;
@RequestMapping("/hello")
public String hello(){
Asyn.getInfo();
return "ok";
}
}
在主方法中加一个注解:
@EnableAsync://开启异步任务,前台秒刷新,后台处理数据
在被调用的方法中@Async:告诉spring这是一个异步方法
这样就可以实现前台秒刷新后台出数据
2、邮件任务
1.导包
<!--邮件发送-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2、获取qq邮箱授权码:这个就不演示
3、编写配置文件application.properties
spring.mail.username=407909756@qq.com
spring.mail.password=验证码
spring.mail.host=smtp.qq.com
# 开启加密验证
spring.mail.properties.mail.smtp.ssl.enable=true
package com.hua;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
@SpringBootTest
class SpringBoot10MailApplicationTests {
@Autowired
JavaMailSenderImpl javaMailSender;
//普通邮件
@Test
void contextLoads() {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("陈俊华华华");
simpleMailMessage.setText("华哥");
simpleMailMessage.setFrom("407909756@qq.com");
simpleMailMessage.setTo("1944575687@qq.com");
javaMailSender.send(simpleMailMessage);
}
@Test
//复杂邮寄
void contextLoads1() throws Exception {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mail = new MimeMessageHelper(mimeMessage,true);
mail.setSubject("陈俊华华华");
mail.setText("华哥");
mail.setFrom("407909756@qq.com");
mail.setTo("407909756@qq.com");
mail.addAttachment("陈艺元的狗照.jpg",
new File("C:\\Users\\Dista\\Desktop\\1.jpg"));
javaMailSender.send(mimeMessage);
}
}
正常的话是在controller里面的
3.定时任务:有点像linux里面的
Taskscheduler任务调度者
TaskExecutor任务执行者
在main中添加@Enablescheduling://开启定时功能的注解
package com.hua;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling//开启定时功能的注解
public class SpringBoot10MailApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot10MailApplication.class, args);
}
}
写一个定时类:@Scheduled(cron = "0/1 * * * * ?") //每秒钟执行一次
package com.hua;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class Schedule {
@Scheduled(cron = "0/1 * * * * ?") //每秒钟执行一次
public void hello() {
System.out.println("hello,你被执行了~");
}
}
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59的整数 | , - * / 四个字符 |
分(Minutes) | 0~59的整数 | , - * / 四个字符 |
小时(Hours) | 0~23的整数 | , - * / 四个字符 |
日(DayofMonth) | 1~31的整数(但是你需要考虑你月的天数) | ,- * ? / L W C 八个字符 |
月份(Month) | 1~12的整数或者 JAN-DEC | - * / 四个字符 |
周(DayofWeek) | 1~7的整数或者 SUN-SAT (1=SUN) | , - * ? / L C # 八个字符 |
年(可选,留空)(Year) | 1970~2099 | , - * / 四个字符 |
15、分布式 Dubbo+Zookenper+SpringBoot
1、分布式理论
1、什么是分布式系统?
在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system)是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题… …
2、Dubbo文档
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。
在Dubbo的官网文档有这样一张图
3、单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
1、性能扩展比较难
2、协同开发问题
3、不利于升级维护
4、垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点: 公用模块无法重复利用,开发性的浪费
1、分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。
2、流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于*提高机器利用率的资源调度和治理中心***(SOA)***[ Service Oriented Architecture]***是关键。
3、RPC
什么是RPC?
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
RPC基本原理
4、Dubbo
1、什么是dubbo?
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo官网
1.了解Dubbo的特性
2.查看官方文档
5、dubbo基本概念
服务提供者:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心:服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo+zookeeper+springboot
首先要下载Dubbo+zookeeper
以管理员的身份打开它,不要关闭,否则找不到zookeeper注册中心会报错,
dubbo
详细下载参照其他博客
环境搭建:
1.导入依赖
<!-- 导入Dubbo+zookeeper依赖-->
<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!-- Zkclient-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
搭建提供者:
提供者的配置文件:
#提供者的名字
dubbo.application.name=provider-server
server.port=8001
#哪些类要去注册,导入需要注册的包
dubbo.scan.base-packages=com.hua.service
#注册中心 ip地址和端口可以改,可以是外网
dubbo.registry.address=zookeeper://127.0.0.1:2181
提供者的类:
package com.hua.service;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
@Component
@Service
public class TickteServiceImpl implements TickteServise{
@Override
public String getTickte() {
return "huashen";
}
}
接口:
package com.hua.service;
public interface TickteServise {
String getTickte();
}
配置开启服务后:
消费者:
配置文件:
server.port=8002
#消费者名字
dubbo.application.name=comsumer
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=zookeeper://127.0.0.1:2181
消费者一样要导入依赖:一样的依赖
package com.hua.service;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class comsumer {
//想要拿到provider提供的票 需要去拿去注册中心的服务 @DubboReference
@Reference
private TickteServise tickteServise;
public void buyTicket(){
String ticket = tickteServise.getTickte();
System.out.println("在注册中心拿到=》"+ticket);
}
}
引入的时候,我们创建和提供者一样的包一样的TickteServise接口
因为用了@Reference所以不是使用这个comsumer同级的TickteService而是会去注册中心中找
测试类:
package com.hua;
import com.hua.service.comsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ComsummerApplication {
public static void main(String[] args) {
SpringApplication.run(ComsummerApplication.class, args);
}
}
结果: