赞
踩
目录
12、SpringBoot:Mybatis + Druid 数据访问
15、分布式 Dubbo+Zookenper+SpringBoot
==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]
环境:
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
通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
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 {
- }
@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
SpringApplication.run分析
分析该方法主要分两部分,一是SpringApplication的实例化,二是run方法的执行;
SpringApplication这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目;
2、查找并加载所有可用的初始化器 , 设置到initializers属性中;
3、找出所有的应用程序监听器,设置到listeners属性中;
4、推断并设置main方法的定义类,找到运行的主类.
run方法流程分析
run()的作用:
1、推断应用的类型是普通的项目还是Web项目;
2、查找并加载所有可用初始化器 , 设置到initializers属性中;
3、找出所有的应用程序监听器,设置到listeners属性中;
4、推断并设置main方法的定义类,找到运行的主类.
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的(可以有多个,有优先级)
application.properties
application.yml
*配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
注意:
基本语法例子:
注意@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 对象是否符合正则表达式的规则
-
- .......等等
- 除此以外,我们还可以自定义一些数据校验规则
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境;
多配置文件:
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置
文件;
需要通过一个配置来选择需要激活的环境:
application.properties
- #SpringBoot的多环境配置,可以选择激活哪一个配置
- spring.profiles.active=dev
和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配置文件的!
springboot 启动会扫描以下位置的application.properties或者application.yaml文件作为SpringBoot的默认配置文件:
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
指定位置加载配置文件,还可以通过spring.config.location来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.prop
首先先解释注解:
@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
总结
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,加载了这么多的配置类,但不是所有的都生效了。
那怎么才能知道哪些自动配置类生效?
这时候就要了解
@ConditionalOnXxx注解:
通过名字,可以理解为这种注解是用于判断的,满足条件是怎么样,点进去可以发现,他们有一个共同的注解@Conditional()
只有满足@ConditionalOnXxx()里面参数的条件,简单来说就是pom.xml中有导入的包或者导入的场景的,哪么这个类就会呗自动装配并交由spring管理
总结:这些自动装配的类是只有满足某些条件后才会自动装配。比如找到了某些相关基础类。
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
以上就是自动装配的原理!
SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
如有错误请评论让我改正~~~
jar:webapp!
自动装配
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
找到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
搜索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的配置文件中关闭默认图标即可
首先,看一下官网的描述: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甚至纯文本。
简单点说,就是Thymeleaf提供一种优雅且高度可维护的模板创建方式,可缩小设计团队与开发团队之间的差距。Thymeleaf也已经从一开始就遵从Web标准,尤其是HTML5,这就允许创建一些完全验证的模板。
三方式:
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;
- //.....
- }
测试: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>
测试:
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属性允许将它们的值设置为或包含表达式,由于它们使用的方言,我们将其称为标准表达式。这些表达式可以有五种类型:
SpringBoot已经帮我们自动配置好了大部分的SpringMVC中的配置,但是拦截器,视图控制器等没有配置,这时候我们可以拓展自动配置
怎么进行扩展:
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进行功能扩展
全面接管即: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中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~
导入我们的所有提供的资源!
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);
- }
- }
导入完毕这些之后,还需要导入我们的前端页面,及静态资源文件!
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">
**第一步 :**编写国际化配置文件,抽取页面需要显示的国际化页面消息。
**第二步:**在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";
定向成功之后!我们解决了之前资源没有加载进来的问题!后台主页正常显示!
但是又发现新的问题,我们可以直接登录到后台主页,不用登录也可以实现!
怎么处理这个问题呢?我们可以使用拦截器机制,实现登录检查!
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页面拉
主页点击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>
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
实现员工修改功能,需要实现两步;
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}">
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";
- }
在template中创建一个Error文件夹
500等错误均是这样,
<a class="nav-link" th:href="@{/logOut}">Sign out</a>
对应的controller
- @RequestMapping("/logOut")
- public String logOug(HttpSession httpSession){
- httpSession.invalidate();
- return "redirect:index";
- }
对于数据访问层,无论是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
项目建好之后,发现自动帮我们导入了如下的启动器:
- <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 作为数据源;
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主要提供以下几类方法:
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默认支持以下数据源:
可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。默认情况下,它是从类路径自动检测的。
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
这个过滤器的作用就是统计 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
在web开发中,安全第一位! 过滤器, 拦截器等
做网站:安全应该在什么时候考虑?设计之初!
●漏洞,隐私泄露
●架构一旦确定
shiro、SpringSecurity: 很像除了类不一样,名字不一样;
认证,授权(vip1, vip2, vip3)
功能权限
●访问权限
●菜单权限
… 拦截器,过滤器:大量的原生代码比较冗余
mvc- spring-springboot-框架思想
AOP :横切~配置类
1、SpringSecurity 简介
记住几个类:
Spring Security的两个主要目标是“认证”和“授权”(访问控制)。
这个概念是通用的,不是只在SpringSecurity中存在。
参考官网: https://spring.io/projects/spring-security.
引入依赖
- <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;
- }
- }
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Shiro可以非常容易的开发出足够好的应用,不仅可以用在JavaSE环境,也可以用在JavaEE环境。
下载地址: http://shiro.apache.org/
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();
-
导入相关依赖
- <!-- 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
官方地址:https://swagger.io/
Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务以及 集成Swagger自动生成API文档。
Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,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
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:用在类上,说明该类的作用。
@ApiOperation:注解来给API增加方法说明。
@ApiImplicitParams : 用在方法上包含一组参数说明。
可以包含一个或多个@ApiImplicitParam
@ApiImplicitParam:用来注解来给方法入参增加说明。
类型 | 作用 |
---|---|
path | 以地址的形式提交数据 |
query | 直接跟参数完成自动映射赋值 |
body | 以流的形式提交 仅支持POST |
header | 参数在request headers 里边提交 |
form | 以form表单的形式提交 仅支持POST |
@ApiResponses:用于表示一组响应
可以包含一个或多个@ApiResponses
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
@ApiModel:描述一个Model的信息(一般用在请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:描述一个model的属性
用于方法,字段 ,表示对model属性的说明或者数据操作更改
- <!-- 换肤-->
- <!-- 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
服务器响应时间长,客户端体验不好
模拟代码
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这是一个异步方法
这样就可以实现前台秒刷新后台出数据
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里面的
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 | , - * / 四个字符 |
1、分布式理论
1、什么是分布式系统?
在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system)是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题… …
2、Dubbo文档
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,急需一个治理系统确保架构有条不紊的演进。
在Dubbo的官网文档有这样一张图
3、单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:
1、性能扩展比较难
2、协同开发问题
3、不利于升级维护
4、垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点: 公用模块无法重复利用,开发性的浪费
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架(RPC)**是关键。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于*提高机器利用率的资源调度和治理中心***(SOA)***[ Service Oriented Architecture]***是关键。
什么是RPC?
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数;
RPC基本原理
1、什么是dubbo?
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
dubbo官网
1.了解Dubbo的特性
2.查看官方文档
服务提供者:暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
监控中心:服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
调用关系说明
服务容器负责启动,加载,运行服务提供者。
服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者在启动时,向注册中心订阅自己所需的服务。
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
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);
- }
-
- }
结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。