目录
1、什么是SpringBoot?
2、第一个SpringBoot程序
3、项目结构分析:
@AutoConfigurationPackage
总结:
4、yaml语法学习
1、配置文件
2、yaml基础语法
5、多环境配置以及配置文件位置
1、多环境切换
2、yaml的多文档块
3、配置文件加载位置
4、拓展,运维小技巧
5、自动配置原理
总结:
6、SpringBoot Web开发
1、Web开发探究
2、静态资源处理
1、静态资源映射规则:
3、静态资源映射规则访问资源
7、首页以及图标的定制
1、首页处理
8、Thymeleaf模板引擎
1、Thymeleaf是什么
2、Thymeleaf有何优势
3、Thymeleaf 特点
4、引入Thymeleaf
8、Thymeleaf 语法学习
9、SpringMVC拓展
实现WebMvcConfigurer接口
10.全面接管SpringMVC
11、员工管理系统
1、员工管理系统:准备工作
3、员工管理系统:国际化
5、员工管理系统:登陆拦截器
6、员工管理系统:展示员工列表
7、员工管理系统:添加员工信息
8、员工管理系统:修改员工信息
9、员工管理系统:删除员工
10、员工管理系统:404错误页面
11、员工管理系统:注销功能
12、SpringBoot:Mybatis + Druid 数据访问
1、简介
2、JDBC
3、CRUD操作
4、自定义数据源 DruidDataSource
2、配置 Druid web 监控 filter
13.SpringSecurity (安全)
1、SpringSecurity环境搭建
14、Shiro(安全)
1、Shiro简介
2、Shiro有哪些功能?
3、Shiro架构(外部)
6、SpringBoot整合Shiro环境搭建
13、Swagger
1、Swagger的作用和概念
1、Swagger 的优势
2、SwaggerUI 特点
2、SpringBoot集成Swagger
3、配置Swagger
5、其他皮肤
14、任务
1、异步任务
2、邮件任务
3.定时任务:有点像linux里面的
15、分布式 Dubbo+Zookenper+SpringBoot
1、分布式服务架构
2、流动计算架构
3、RPC
4、Dubbo
5、dubbo基本概念
==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包这样就可以在任意地方执行。
如:
更改端口号:
更改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
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 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 getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List 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 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> loadSpringFactories(ClassLoader classLoader) {
Map> 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中如果爆红
则引入下面的依赖
org.springframework.boot
spring-boot-starter-validation
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
合法条件下:
不合法:
@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依赖即可!
org.webjars
jquery
3.6.0
访问:只要是静态资源,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官方文档:找到我们对应的版本
org.thymeleaf
thymeleaf-spring5
org.thymeleaf.extras
thymeleaf-extras-java8time
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
Title
test
学习文档地址
测试:
1、Controller层
@Controller
public class IndexController {
@RequestMapping("/test")
public String test(Model model){
//存入数据
model.addAttribute("msg","Hello SpringBoot!
");
model.addAttribute("users", Arrays.asList("hua","你好帅"));
return "test";
}
}
Title
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 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 CollectiongetDepartments(){
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 employeeMap=null;
@Autowired
private static DepatmentDao depatmentDao;
static{
employeeMap=new HashMap();
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 CollectiongetEmployees(){
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、修改所有本地静态资源的链
**第一步 :**编写国际化配置文件,抽取页面需要显示的国际化页面消息。
**第二步:**在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取值操作为: #{…}。
Please sign in
[[#{login.remember}]]
根据按钮自动切换中文英文!
在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 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,可以在链接上携带区域信息!
修改一下前端页面的跳转连接;
测试页面
4、员工管理系统:登陆功能实现
问题:输入任意用户名都可以登录成功!
原因:templates下的页面只能通过Controller跳转实现,而static下的页面是能直接被外界访问的,就能正常访问
把登录页面的表单提交地址写一个controller,所有表单标签都需要加上一个name属性
编写对应的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";
}
}
}
登录失败的话,我们需要将后台信息输出到前台,可以在首页标题下面加上判断!
将 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 employees = employeeDao.getEmployees();
model.addAttribute("epms",employees);
return "list";
}
Thymeleaf 公共页面元素抽取
1.抽取公共片段 th:fragment 定义模板名
2.引入公共片段 th:insert 插入模板名:插入一个div
3.引入公共片段 th:replace插入模板名:替换元素插入
抽取后的主页:
Dashboard Template for Bootstrap
Dashboard
抽取后的员工列表页面:
高亮设置
list.html
dashboard.html
抽取页面接收的active:list
抽取页面接收的active:top
遍历我们的员工信息
1.在list.html页面中将添加员工信息改为超链接
2.编写对应的controller层
@GetMapping("/toAdd")
public String goToAdd(Model model){
Collection employees = employeeDao.getEmployees();
model.addAttribute("epms",employees);
return "add";
}
3.添加前端页面;复制list页面,修改即可
Dashboard Template for Bootstrap
4.部门信息下拉框应该选择的是我们提供的数据,所以我们要修改一下前端和后端
@PostMapping("/toAdd")
public String addEmp(HttpServletRequest httpServle,Employee employee){
//自动封装保存数据
employeeDao.addEmployee(employee);
return "redirect:goToList";
}
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.显示原数据,修改完毕后跳回列表页面!
首先修改跳转链接的位置;
编写对应的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页面;需要修改页面,将我们后台查询数据回显
日期显示不完美,可以使用日期工具,进行日期的格式化!
修改表单提交的地址:
编写对应的controller
@PostMapping("/update")
public String updateEmp(Employee employee){
employeeDao.addEmployee(employee);
return "redirect:goToList";
}
现页面提交的没有id;我们在前端加一个隐藏域,提交id;
list页面,编写提交地址
编写Controller
@RequestMapping("/del/{id}")
public String delEmp(@PathVariable("id") Integer id){
employeeDao.deleteEmployeeById(id);
return "redirect:/goToList";
}
在template中创建一个Error文件夹
500等错误均是这样,
对应的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
项目建好之后,发现自动帮我们导入了如下的启动器:
org.springframework.boot
spring-boot-starter-jdbc
mysql
mysql-connector-java
runtime
实现数据库的访问
先连接上数据库 , 直接使用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
测试成功!
原理探究 :
org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 数据源配置类作用 :根据逻辑判断之后,添加数据源;
SpringBoot默认支持以下数据源:
可以使用 spring.datasource.type 指定自定义的数据源类型,值为要使用的连接池实现的完全限定名。默认情况下,它是从类路径自动检测的。
DRUID 简介
*com.alibaba.druid.pool.DruidDataSource 基本配置参数如下:*
引入数据源
com.alibaba
druid
1.2.8
切换数据源;
之前已经说过 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 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 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 bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//可以过滤哪些请求?
HashMap initParams = new HashMap<>();
//这些东西不进行统计
initParams .put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
return bean;
}
}
如上图所示。
SpringBoot集成Mybatis
导入mybatis依赖
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
org.projectlombok
lombok
true
在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 queryAll();
int add(User user);
int delById(int age);
int update(User user);
}
mapper的xml文件放在resource文件夹下:
insert into book.user(name,age,gender) value (#{name},#{age},#{gender})
update book.user set name=#{name},age=#{age},gender=#{gender} where age=#{age};
delete from book.user where age=#{age};
Service层:
package com.hua.service;
import com.hua.pojo.User;
import org.springframework.stereotype.Service;
import java.util.List;
public interface UserService {
List 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 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 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.
引入依赖
org.springframework.security
spring-security-test
test
写一个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
导入依赖
org.apache.shiro
shiro-core
1.8.0
org.slf4j
jcl-over-slf4j
1.7.21
org.slf4j
slf4j-log4j12
1.7.21
org.apache.logging.log4j
log4j-core
2.14.1
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 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();
导入相关依赖
org.apache.shiro
shiro-core
1.8.0
org.apache.shiro
shiro-spring
1.8.0
org.thymeleaf
thymeleaf-spring5
org.thymeleaf.extras
thymeleaf-extras-java8time
注意测试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 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
login
Title
登录
add
Title
add
update
Title
update
Title
未经授权无法访问
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
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 消除了调用服务时可能会有的猜测。
引入依赖
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
测试是否运行成功
@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" ,"[email protected]"),"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属性的说明或者数据操作更改
com.github.xiaoymin
swagger-bootstrap-ui
1.9.6
访问 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.导包
org.springframework.boot
spring-boot-starter-mail
2、获取qq邮箱授权码:这个就不演示
3、编写配置文件application.properties
[email protected]
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("[email protected]");
simpleMailMessage.setTo("[email protected]");
javaMailSender.send(simpleMailMessage);
}
@Test
//复杂邮寄
void contextLoads1() throws Exception {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mail = new MimeMessageHelper(mimeMessage,true);
mail.setSubject("陈俊华华华");
mail.setText("华哥");
mail.setFrom("[email protected]");
mail.setTo("[email protected]");
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.导入依赖
org.apache.dubbo
dubbo-spring-boot-starter
2.7.3
com.github.sgroschupf
zkclient
0.1
org.apache.curator
curator-framework
2.12.0
org.apache.curator
curator-recipes
2.12.0
org.apache.zookeeper
zookeeper
3.4.14
org.slf4j
slf4j-log4j12
搭建提供者:
提供者的配置文件:
#提供者的名字
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);
}
}
结果: