SpringBoot
微服务阶段
javaSE:OOP(面向对象);
mysql:持久化;
html-css-js-jquery+框架:视图,框架不熟练,css不好;
javaweb:独立开发mvc三层架构的网站;
ssm:框架,简化了开发流程,,但是配置也开始复杂了;
war:tomcat运行
SpringBoot:微服务架构!内嵌tomcat!
springcloud:微服务越来越多!
新服务架构:服务网格
==约定大于配置==:maven、spring、SpringBoot、springMVC.docker、k8s
spring
Spring是一个开源框架,2003年兴起的一个轻量级java开发框架,作者:Rod Johnson。Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
spring如何简化开发的?
- 基于POJO的轻量级和最小入侵性编程
- 通过IOC,依赖注入(DI)和面向接口实现松耦合
- 基于切面(AOP)和惯例进行声明式编程
- 通过切面和模板减少样式代码
springboot的优点
- 为所有spring开发者提1+2
- 供更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
springcloud
- 构建一个个功能独立的微服务应用单元,可以使用springboot;
- 大型分布式网络服务的调用由SpringCloud实现分布式;
- 在分布式中间,通过spring cloud data flow进行流式数据计算、批处理;
第一个springboot程序
1. jdk1.8
2. maven 3.6.1
3. springboot 最新
4. IDEA
官方提供了一个快速生成的网站!IDEA集成了这个网站!
一般用idea创建
如果出现报错
Initialization failed for 'https://start.spring.io' Please check URL, network and proxy settings.
则可以使用阿里云的 https://start.aliyun.com/
注意:创建springboot项目时用了阿里云的镜像是没有parent标签的,用springboot自身的创建项目是有parent标签的
- 项目元数据信息:创建的时候输入的Project Metadata部分,也就是maven项目的基本元素,包括groupid、artifactid、version、name、description等;
- parent:集成==spring-boot-starter-parent==的依赖管理,控制版本与打包等内容;
- dependencies:项目的具体依赖,这里包含了==spring-boot-starter-web==用于实现HTTP接口(该依赖中包含了Spring MVC),官网对他的描述是:使用spring mvc构建web(包括RESTful)应用程序的入门者,使用tomcat作为默认嵌入式容器,==spring-boot-starter-test==用于编写单元测试的依赖包;
- build:构建配置部分,默认使用==spring-boot-maven-plugin==,配合spring-boot-starter-parent就可以把spring boot应用打包成JAR来直接运行。
原理初探
自动配置:
pom.xml
- spring-boot-dependencies:核心依赖早父工程中!
- springboot有版本仓库,所以一般不需要指定一些springboot依赖的版本,除非版本仓库的版本不是我们需要的,这个时候需要重置一下版本,方式是在pom文件中新增如下:
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
启动器:
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
-
启动器说白了就是springboot的启动场景;
-
比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
-
springboot会将所有的功能场景都变成一个个启动器!
-
我们要使用什么功能,就使用相应的启动器 starter!!!!参见
主程序
//标注这个类是一个springboot的应用:启动类下的所有资源被导入
@SpringBootApplication
public class HelloworldApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(HelloworldApplication.class, args);
}
}
注解
@SpringBootConfiguration:springboot的配置
@Configuration
@Component
@EnableAutoConfiguration://自动配置
@AutoConfigurationPackage //自动配置包
@Import({Registrar.class}) //自动配置包注册
@Import({AutoConfigurationImportSelector.class}) //自动配置导入选择器
//获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取候选的配置
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
/**
class:SpringFactoriesLoader
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ?
classLoader.getResources("META-INF/spring.factories")
: ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
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[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
==META-INF/spring.factories==:自动配置的核心文件
所有资源都加载到配置类中 Properties properties = PropertiesLoaderUtils.loadProperties(resource);
结论:springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但不一定生效,要判断条件是否成立,只要导入了对应的starter,就有了相应的启动器了,有了启动器,我们自动装配才会生效,然后就配置成功!
- springboot在启动的时候,从类路径下/META-INF/factories获取指定的值;
- 将这些自动配置的类导入到容器,自动配置就会生效,帮我们进行自动配置;
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.3.7.RELEASE.jar这个包下;
- 他会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在非常多的xxxAutoConfiguration的文件,就是这些类给容器中导入了这个场景需要的所有组件,并自动配置,@Configuration,javaconfig!
- 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作!
启动main方法
main方法运行并开启了一个服务
@SpringBootApplication
public class HelloworldApplication {
public static void main(String[] args) {
//该方法返回一个ConfigurableApplicationContext对象
//参数:应用入口类, 命令行参数
SpringApplication.run(HelloworldApplication.class, args);
}
}
该方法主要分为两部分,一部分是SpringApplication的实例化,另一部分是run方法的执行
SpringAppplication:
1.推断应用的类型是普通项目还是web项目;
2.查找并加载所有可用初始化器,设置到initializers属性中;
3.找出所有的应用程序监听器,设置到listeners属性中;
4.推断并设置main方法的定义类,找到运行的主类。
//查看构造器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
run
public ConfigurableApplicationContext run(String... args) {
//计时器启动
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
关于springboot,谈谈理解?
- 自动装配
# 注解
- run:
1.推断应用的类型是普通项目(启动完毕就结束)还是web项目(可以一直启动);
2.推断并设置main方法的定义类,找到运行的主类(不知道主类是没有办法加载的);
3.有一些全局的监听器,获取上下文,处理bean
配置文件以及自动装配原理
官方的配置,2.7.0版本路径如下
配置文件
springboot使用一个全局的配置文件,名称是固定的
- application.properties 语法结构为 key=value
- application.yaml 语法结构为 key:空格 value(==建议使用==)
配置文件作用:修改springboot自动配置的默认值,因为springboot在底层都给我们自动配置好了。
#普通的key-value
server:
port: 8081
# 对象
student:
name: zzm
age: 29
#行内对象写法
student: {name: zzm,age: 29}
#数组
animals:
- cat
- dog
- pig
#行内数组写法
animals: [cat,dog,pig]
如果使用properties文件的话,idea要设置编码为utf-8
案例1 通过yaml文件注入一个对象
注意:==使用注解@ConfigurationProperties时,要导入包,否则会提示,但是不影响程序的运行==
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
- yaml文件(yaml文件中还可以使用占位符${}以及逻辑判断的${:})
person:
name: zzm${random.int}
age: 29
happy: true
birth: 1993/11/20
maps: {k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: ${person.hello:kaka}比鲁斯
age: 3
- Person类和Dog类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Component
@ConfigurationProperties(prefix = "person")
public class Person implements Serializable {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Component
public class Dog implements Serializable {
private String name;
private int age;
}
- 调用person对象
@Autowired
Person person;
@Test
void contextLoads() {
System.out.println(person);
}
案例2 通过properties文件注入一个对象
- zidingyi.properties文件
person.name=卡卡罗特
person.age=14
- person类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@PropertySource("classpath:zidingyi.properties")
public class Person implements Serializable {
//取出配置文件的值
@Value("${person.name}")
private String name;
//SPEL表达式
@Value("#{11*2}")
private Integer age;
//字面量
@Value("true")
private Boolean happy;
}
两种配置方式的区别
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SPEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
附:
- 松散绑定:比如yaml中的last-name和lastName是一样的,-后面跟的字母默认是大写的;
- JSR303数据校验:在字段上增加一层过滤器验证,可以保证数据的合法性。
2.1. 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2. 配置到实体类
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person implements Serializable {
private static final long serialVersionUID = 281903912367009575L;
@Email(message="姓名格式不正确")
private String name;
private Integer age;
}
Hibernate 中填充一部分
注解 注解详情
@Null 被指定的注解元素必须为 Null
@NotNull 任意类型,不能为 Null,但可以为空,比如空数组、空字符串。
@NotBlank 针对字符串,不能为 Null,且去除前后空格后的字符串长度要大于 0。
@NotEmpty 针对字符串、集合、数组,不能为 Null,且长度要大于 0。
@Size 针对字符串、集合、数组,判断长度是否在给定范围内。
@Length 针对字符串,判断长度是否在给定范围内。
@AssertTrue 针对布尔值,用来判断布尔值是否为 true
@AssertFalse 针对布尔值,用来判断布尔值是否为 false
@Past 针对日期,用来判断当前日期是否为 过去的日期
@Future 针对日期,用来判断当前日期是否为 未来的日期
@Max(value) 针对字符串、数值,用来判断是否小于等于某个指定值
@Min(value) 针对字符串、数值,用来判断是否大于等于某个指定值
@Pattern 验证字符串是否满足正则表达式
@Email 验证字符串是否满足邮件格式
@Url 验证是否满足 url 格式
配置文件位置
以上四个位置都可以被读到
优先级按照序号,默认是优先级最低的,会被优先级高的地方覆盖(这应该是配置可以集中由三方服务管理的原因:比如disconf、Apollo等)。
springboot的多环境配置
- 方式1:多个配置文件
#application.properties文件
#springboot的多环境配置:可以激活某一个配置文件
spring.profiles.active=test
server.port=8080
#application-test.properties
server.port=8081
#application-product.properties
server.port=8082
- 方式2
server:
port: 8080
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: pro
---
server:
port: 8083
spring:
profiles: test
配置文件内容
#配置文件能写什么? ---联系---spring.factories
#规则:
# xxxAutoConfiguration:默认值 xxxProperties 和配置文件绑定,我们就可以使用自定义的配置了
#理解: spring.factories下的配置类xxxAutoConfiguration会加载配置属性类xxxProperties,设置默认值,如果我们
#需要重新设置值,则在配置文件中按照松散绑定编写相应的变量即可。
总结自动装配的精髓:
- springboot启动会加载大量的自动配置类;
- 判断我们需要的功能是否在springboot默认写好的自动配置类中;
- 查看自动配置类中配置了哪些组件;(如果使用的组件存在于自动配置类中,那我们不需要再手动配置了)
- 给容器中的自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
- xxxAutoConfiguration:自动配置类 ;xxxProperties:封装配置文件中相关属性。
==注意==
自动配置类必须在一定条件下才能生效:@Conditionxxx注解(Spring的@Condition注解的派生注解)
@Conditional | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
查看哪些配置类是生效的
#application.yaml文件中增加配置
debug: true
启动应用,查看日志即可,分为三种:
- Positive matches 自动配置类启用的:正匹配
- Negative matches 没有启动,没有匹配成功的自动配置类:负匹配
- Exclusions 排除掉的
- Unconditional classes 没有条件的类
SpringBoot Web开发
解决问题:
- 导入静态资源
- 首页
- jsp,模板引擎 Thymeleaf
- 装配扩展SpringMvc
- 增删改查
- 拦截器
- 国际化
静态资源
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//首先判断是否自定义配置
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//其次,查看webjars 官网导入所需maven依赖,对应相应的jar包中的/META-INF/resources/webjars/路径
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
//第二种就是经常见到的路劲,所有的/开头的,都会从下面四个路径中匹配,优先级就是下面的顺序
// staticPathPattern => "/**"
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
//this.resourceProperties.getStaticLocations() => { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
总结:
- 在springboot中,我们可以使用一下方式处理静态资源
- webjars localhost:8080/webjars/
- public, static, reosurce localhost:8080/
- 优先级:public (公共的文件)< static (js等静态资源)<resource(上传文件)
获取首页
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
private Optional<Resource> getWelcomePage() {
//{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
注意:在template目录下的所有页面,只能通过controller跳转
模板引擎
模板引擎有很多,包括js、freemarker以及springboot推荐的Thymeleaf。
Thymeleaf
github:https://github.com/thymeleaf/thymeleaf
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- java类增加传递参数
//model 和 maps同时生效,只传一个也可以,如果出现同一个参数msg赋值,后面的会覆盖前面的
@RequestMapping("/index")
public String index(Model model, Map<String,Object> maps){
maps.put("msg","hello,everyone33!");
model.addAttribute("msg","hello,everyone!");
model.addAttribute("users", Arrays.asList("卡卡罗特","贝吉塔","布罗利"));
model.addAttribute("user",new User("卡卡罗特别强",22));
maps.put("msg3","hello,everyone3dfgds3!");
return "index";
}
- html增加名称空间
<!DOCTYPE html>
<!--导入thymeleaf的名称空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<h2 th:text="${msg}"></h2>
<h2 th:text="${msg3}"></h2>
<h3 th:each="user:${users}" th:text="com.wenyibi.futuremail.model.User@1167a47f"></h3>
<!--这种不做推荐使用-->
<h3 th:each="user:${users}" >[[ com.wenyibi.futuremail.model.User@1167a47f ]]</h3>
<!--如果有指定对象th:object,则里面不能使用$,只能使用*,这就是*和$的区别 -->
<div th:object="com.wenyibi.futuremail.model.User@1167a47f">
<p>Name: <span th:text="${name}"></span>.</p>
<p>Age: <span th:text="*{age}"></span>.</p>
</div>
</body>
</html>
- 结果
//这个配置参数类解释了导入thymeleaf包后,controller可以直接链接到template目录下的html文件
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
//在此处加载Thymeleaf的配置参数类
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "defaultTemplateResolver")
static class DefaultTemplateResolverConfiguration {
在springboot中,有非常多的xxxConfiguration帮助我们进行扩展配置,只要看见这个东西,我们就要注意了!
实战1-员工管理系统
自定义视图解析器
//@EnableWebMvc这个加上自定义视图不生效
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("signin");
registry.addViewController("/index").setViewName("signin");
registry.addViewController("/index.html").setViewName("signin");
}
}
前端模板下载
https://sc.chinaz.com/tag_moban/bootstrap.html
首页配置
首页配置:所有页面的静态资源都需要用Thymeleaf接管;url : @{}
页面国际化
//org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
- 配置i18n文件;
- 如果需要项目通过按钮自动切换,需自定义组件LocaleResolver;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolver implements LocaleResolver {
/**
* 解析请求
* @param request
* @return
*/
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
//zh_CN en_US
if (!StringUtils.isEmpty(language)) {
String[] splits = language.split("_");
//language region
return new Locale(splits[0],splits[1]);
}
return Locale.getDefault();
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
- 将自己写的组件配置到spring容器中 @Bean
//@EnableWebMvc这个加上自定义视图不生效
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("signin");
registry.addViewController("/index").setViewName("signin");
registry.addViewController("/index.html").setViewName("signin");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
登录+拦截器
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session){
if(!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password) && "67890".equals(password)){
model.addAttribute("msg","登录成功!");
session.setAttribute("loginStatus",1);
return "redirect:/index.html";
}
if(!(StringUtils.isEmpty(username) && StringUtils.isEmpty(password))){
model.addAttribute("msg","用户名或密码错误!");
}
return "signin";
}
}
//登录拦截器
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginStatus = request.getSession().getAttribute("loginStatus");
if(loginStatus == null){
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/signin").forward(request,response);
return false;
}
return true;
}
}
//添加自定义拦截器 com.karottezzm.springboot04web.config.MyMvcConfig
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**").excludePathPatterns("/signin","/","/user/login","/css/**","/js/**","/img/**");
}
展示列表
提取公共页面
1. <nav class="navbar bg-light navbar-light" th:fragment="sidebar">
2. <div th:replace="~{commons/common::sidebar}"></div>
3. 如果要传递参数,可以直接使用()传参,接收判断即可
<div th:replace="~{commons/common::sidebar(act='table')}"></div>
<a th:href="@{index1.html}" th:class="'nav-item nav-link active':'nav-item nav-link'">
编辑删除按钮需要拼接字符串
<td>
<a class="btn btn-sm btn-primary" th:href="@{/employee/toEdit/}+${emp.getId()}" >编辑</a>
<a class="btn btn-sm btn-danger" th:href="@{/employee/del/}+${emp.getId()}">删除</a>
</td>
404:将错误页面放到templates/error/目录下即可
后端模板
http://x.xuebingsi.com/ xadmin
Data
对于数据访问层,无论是SQL还是NOSQL,springboot底层采用的都是Spring Data的方式进行统一处理。
官网:https://spring.io/projects/spring-data
JDBC
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver #与mysql版本相关 8.0为分界 小于,则com.mysql.jdbc.Driver
@RestController
@RequestMapping("/jdbc")
public class JDBCController {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
DataSource dataSource;
@RequestMapping("/list")
public List<Map<String,Object>> getList(){
String sql = "select * from user ";
List<Map<String,Object>> result = jdbcTemplate.queryForList(sql);
return result;
}
public void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
DRUID
Druid是阿里巴巴开源平台上一个数据库连接池实现,结合了C3P0、DBCP、PROXOOL等DB、池的优点,同时加入了==日志监控==。
Springboot2.0以后默认使用Hikari数据源,可以说Hikari和Druid都是当前Java Web上最优秀的数据源。
springboot低版本使用的数据源为org.apache.tomcat.jdbc.pool.DataSource.
==高版本的为HikariDataSource,号称Java Web当前速度最快的数据源。==
http://mvnrepository.com/artifact/com.alibaba/druid
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#springboot默认不注入这些属性值,需要自己绑定
#druid数据源专用绑定
initialSiza: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMills: 60000
MinEvictableIdleTimeMills: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
#配置监控统计拦截的filters、 stat:监控统计、log4j:日志记录、wall:防御SQL注入
#如果运行时报错,java.lang.ClassNotFoundException:org.apache.log4j.Priority
#则导入log4j依赖即可,Maven地址:https://mvnrepository.com/artifact/log4j/logj
filters: stat,wall,log4j
max-pool-prepared-statement-per-connection-size: 20
use-global-data-source-stat: true
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMills=500
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.Arrays;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DruidConfig{
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource(){
return new DruidDataSource();
}
//后台监控
//因为springboot内置了servlet容器,所以所以没有web.xml 替换方法:ServletRegistrationBean
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean =
//注意,这里如果写成"/druid/**"就会报404
new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");
//后台系统需要有人登录,设置账号密码
HashMap<String,String> initParameter = new HashMap<>();
//下面两个字段固定
initParameter.put("loginUsername","admin");
initParameter.put("loginPassword","admin");
initParameter.put("allow","");//默认就是允许所有访问
//initParameter.put("deny","192.168.15.21");
bean.setInitParameters(initParameter);//初始化参数
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
//这些路径不会统计
initParams.put("exclusions","*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
mybatis
整合包:
mybatis-spring-boot-starter
pom.xml文件
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package: com.kaka.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
mapper.xml内容格式参见:https://mybatis.net.cn/getting-started.html
<?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.kaka.mapper.UserMapper">
<select id="queryUserList" resultType="User" useCache="true">
select * from user
</select>
</mapper>
SpringSecurity(安全)
截止2022/5/28
简介
SpringSecurity是针对Spring项目的安全框架,也是springboot底层安全模块默认的技术选型,他可以实现强大的web安全控制,而我们只需引入spring-boot-starter-security模块,进行少量的配置。
关键类
- WebSecurityConfigurerAdapter:自定义安全策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启webSecurity模式
SpringSecurity的主要目标就是 认证(Authentication)和授权(authorization,也可以说是访问控制)
参考官网:https://spring.io/projects/spring-security#learn
与thymeleaf整合
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4</version>
</dependency>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
shiro
关键类
- Subject
- SecurityManager
- Realm :授权(doGetAuthenticationInfo) 认证(doGetAuthorizationInfo)
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("loginUser",user);
currentUser.isAuthenticated();
currentUser.getPrincipal();
currentUser.hasRole("user");
currentUser.isPermitted("user:add");
currentUser.logout();
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.0</version>
</dependency>
与thymeleaf整合
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">x新增</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">更新</a>
</div>
Swagger
-
作用和概念
-
前后端分离
-
SpringBoot集成swagger
swagger简介
前后端分离
vue+SpringBoot
后端时代:前端只负责管理静态页面;html==》后端。模板引擎 jsp
前后端分离时代:
- 后端:后端控制层、服务层、数据访问层
- 前端:前端控制层、视图
- 交互:API
- 前端测试后端接口:postman Apifox(联网可共享)
- 后端提供接口,需要实时更新最新的消息及改动。
swagger
- RestFul Api文档在线自动生成工具=》==Api文档与Api定义同步更新==
- 直接运行,可以在线测试API接口
- 支持多种语言
- 官网:https://swagger.io/
项目中的swagger
在项目中使用swagger需要springbox:
- swagger2
- ui
SpringBoot集成swagger
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
}
访问:http://localhost:8080/swagger-ui.html
==上述就是最简单的使用==,接口中返回中有实例的时候,model中才会出现
配置Swagger
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
/**
* 配置swagger的实例
* @return
*/
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
Contact con = new Contact("卡卡", "https://blog.nowcoder.net/karottezzm", "wuqing@163.com");//作者信息
return new ApiInfo("卡卡罗特别强", "醉卧沙场君莫笑", "v1.0", "https://blog.nowcoder.net/karottezzm",
con, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList<>());
}
自定义扫描接口
@Value("${swagger.enable}")
private boolean enbale;
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(true)//是否启用swagger 根据enbale实现多个环境不同配置swagger开关
.select()
//RequestHandlerSelectors配置要扫描接口的方式
//any():全部
//none():不扫描
//withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
//withMethodAnnotation:扫描方法上的注解
//basePackage:包扫描
.apis(RequestHandlerSelectors.withMethodAnnotation(Api.class))
//path 过滤什么路径
.paths(PathSelectors.none())
.build();
}
spring:
thymeleaf:
cache: false
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
profiles:
active: pro
mybatis:
type-aliases-package: com.kaka.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
---
spring:
profiles: test
swagger:
enable: false
---
spring:
profiles: pro
swagger:
enable: true
---
spring:
profiles: dev
swagger:
enable: false
==也可以通过自定义注解横向配置swagger==
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface SwaggerMethod{}
@SwaggerMethod
@ApiOperation("获取用户信息列表")
@GetMapping("/list")
public List<User> getUserList(){
return userMapper.queryUserList();
}
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(enbale)//是否启用swagger
.groupName("super赛亚人")
.select()
//RequestHandlerSelectors配置要扫描接口的方式
//any():全部
//none():不扫描
//withClassAnnotation:扫描类上的注解,参数是一个注解的反射对象
//withMethodAnnotation:扫描方法上的注解
//basePackage:包扫描
.apis(RequestHandlerSelectors.withMethodAnnotation(SwaggerMethod.class))
//.apis(RequestHandlerSelectors.basePackage("com.kaka.controller"))
//path 过滤什么路径
//.paths(PathSelectors.none())
.build();
}
任务
异步任务
@Service
public class UserService {
/**
* 测试异步任务
*/
@Async
public void sayHello(){
try {
Thread.sleep(3000);
}catch (Exception e){
}
}
}
/////////////////////////@EnableAsync 调用者要添加该注解
@Controller
@EnableAsync
public class IndexController {
@Autowired
UserService userService;
@SwaggerMethod
@GetMapping({"/","index","index.html"})
public String index(){
userService.sayHello();
return "index";
}
}
邮件任务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
spring:
mail:
username: 14536595848@163.com
password: hsudha987678hhasjkh
properties:
mail:
smtp:
ssl:
enable: true
host: smtp.11.com
@Autowired
JavaMailSenderImpl mailSender;
@Autowired
UserMapper userMapper;
@SwaggerMethod
@ApiOperation("获取用户信息列表")
@GetMapping("/list")
public List<User> getUserList(){
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("wuqingzzm@163.com");
message.setTo("wuqingzzm@163.com");
message.setSubject("lalala");
message.setText("通知一下而已");
mailSender.send(message);
return userMapper.queryUserList();
}
定时任务
TaskSchduler #任务调度
TaskExecutor #任务执行
@EnbaleScheduling #开启定时功能的注解 一般放到入口
@Scheduled #执行时间
cron表达式
@SpringBootApplication
@EnableAsync
@EnableScheduling
public class SpringbootSwaggerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSwaggerApplication.class, args);
}
}
@Async
@Scheduled(cron = "0 * * * * ?")
public void sayHello(){
try {
System.out.println("执行蒂诺故事人物");
}catch (Exception e){
}
}
分布式 + Dubbo + Zookeeper
all in -> mvc -> rpc -> soa
分布式系统:distributed system
RPC: remote procedure call 远程过程调用,进程间通信方式 核心:通讯+序列化
SOA:service oriented architecture 面向服务的架构 ==资源调度和治理中心是关键==
dubbo: 是一个jar包,引入jar包即可
dubbo-admin:是一个监控管理后台,可以查看服务注册情况和消费情况 7001端口
zookeeper:2181端口
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.8</version>
</dependency>
<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>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
#提供服务
dubbo:
application:
name: provider-server
registry:
address: zookeeper://127.0.0.1:2181
scan:
base-packages: com.kaka.service.rpc
#消费服务
dubbo:
application:
name: consume-server
registry:
address: zookeeper://127.0.0.1:2181
import org.springframework.stereotype.Component;
import org.apache.dubbo.config.annotation.Service;
@Service
@Component
public class UserService { //提供服务
import org.springframework.stereotype.Service;
@Service
public class UserService { //消费服务
微服务架构的问题?
分布式架构会遇到的四个核心问题:
- 大量的服务,客户端如何访问;
- 大量的服务,服务之间如何通信;
- 大量的服务,如何治理;
- 服务挂了怎么办?
解决方案:
springcloud,一套生态,就是来解决以上分布式架构的4个问题。
- Spring Cloud Netflix 出来了一套解决方案!一站式解决方案,我们都可以直接去这里拿?
- Api网关 zuul组件
- Feign --> httpclient ---> HTTP的通信方式,同步并阻塞
- 服务注册与发现 Eureka
- 熔断机制 Hystrix
2018年底,NetFlix宣布巫期限停止维护。生态不再维护,就会脱节。
-
Apache Dubbo zookeeper 第二套解决方案
- API:没有,要么找三方组件,要么自己实现
- Dubbo是一个高性能的基于java实现的RPC通信框架
- 服务注册与发现:zookeeper-动物管理员(Hadoop,Hive)
- 没有,借助Hystrix
-
ApacheCloud Alibaba 一站式解决方案
-
服务网格:下一代微服务标准,Server Mesh
代表解决方案:istio
最终解决的问题:
- API网关,服务路由;
- HTTP,RPC框架,异步调用;
- 服务注册与发现,高可用;
- 熔断机制,服务降级
以上内容如有错误之处,欢迎指正!!!
学习源为:https://space.bilibili.com/95256449/channel/seriesdetail?sid=393820&ctype=0