1. 前言
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。
Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。
Spring Boot的主要优点:
- 为所有Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
2. 第一个SpringBoot程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVCXifSU-1629872608430)(C:\Users\hjy\AppData\Roaming\Typora\typora-user-images\image-20210817224611308.png)]
- DemoApplication.java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注意 : 这个是启动类,所有类必须在启动类的相邻包或者是子包下
- HelloController
@Controller
@ResponseBody
public class HelloController {
@RequestMapping("hello")
public String hello(){
return "hello world";
}
}
- application.properties
# 应用名称
spring.application.name=demo
# 应用服务 WEB 访问端口
server.port=8080
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.demo.DemoApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3. 基本使用
3.1 改变banner
默认的banner是
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
我们可以自定义banner
首先打开网站https://www.bootschool.net/ascii
复制
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
在resources下新建banner.txt,再把其复制进去即可
3.2 自动配置
结论:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
- 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
由@SpringBootApplication ==>@EnableAutoConfiguration ==>@Import(AutoConfigurationImportSelector.class)
==> AutoConfigurationImportSelector.class ==>
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
== > SpringFactoriesLoader == >
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
== > loadSpringFactories ==> META-INF/spring.factories
然后打开该文件
选择org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration点入
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
== > 点击ServerProperties.class
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
精髓
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解:@Conditional
了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
3.3 主启动类的运行
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
3.4 yml配置注入
SpringBoot使用一个全局的配置文件 (不同格式可以有多个,如application.properties+application.yaml), 配置文件名称是固定的
-
application.properties -
-
application.yml -
3.4.1 语法
说明:语法要求严格!
1、空格不能省略
2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
3、属性和值的大小写都是十分敏感的。
字面量:普通的值 [ 数字,布尔值,字符串 ]
hello: 11
注意:
-
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思; 比如 :name: "kuang \n shen" 输出 :kuang 换行 shen -
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出 比如 :name: 'kuang \n shen' 输出 :kuang \n shen
对象、Map(键值对)
person:
name: junyang
age: 18
行内写法
person: {name: junyang,age: 18}
数组( List、set )
animals:
- pig
- cat
- dog
行内写法 :
animals: [pig,cat,dog]
3.4.2 yaml的el表达式
可以用${} 占位符
随机数
person:
name: 骏扬${random.int}
uuid
person:
name: 骏扬${random.uuid}
如果有值就取,没有值就取默认值
name: yaml
person:
name: ${name:hehe}
3.5 配置文件给java类属性赋值
方法一 : 使用@Value
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
@Value("旺财")
private String name;
@Value("18")
private int age;
}
测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
private Dog dog ;
@Test
void contextLoads() {
System.out.println(dog);
}
}
方法二 : 用@ConfigurationProperties
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private int age;
private Date birthday;
private List<Object> list;
private Map<Object,Object> map;
private Dog dog;
}
- 导入依赖(可以不导)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
该依赖可以为你的配置生成元数据
- application.yaml
person:
name: 骏扬
age: 18
birthday: 2002/11/30
list:
- cat
- dog
- mouse
map:
k1: v1
k2: v2
dog:
name: 旺财
age: 3
- 测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
private Person person ;
@Test
void contextLoads() {
System.out.println(person);
}
}
方法三 : 用@PropertySource
注意 : 这种方法只能用于properties文件
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@PropertySource(value="classpath:dog.properties")
public class Dog {
@Value("${name}")
private String name;
@Value("${age}")
private int age;
}
dog.properties
name=狗狗
age=5
测试
@SpringBootTest
class DemoApplicationTests {
@Autowired
private Dog dog ;
@Test
void contextLoads() {
System.out.println(dog);
}
}
3.6 JSR-303数据校验
首先加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
在要数据验证的类上加上注解@Validated 即可
在字段上加注解,如
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "dog")
@Validated
public class Dog {
private String name;
private int age;
@Email(message="邮箱格式错误")
private String email;
}
这样email字段被赋值的时候会自动数据验证,如果发现数据验证失败就会报错: “邮箱格式错误”
常见的数据验证的注解有
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
@Null
@NotNull
@NotBlank
@NotEmpty
@AssertTrue
@AssertFalse
@Size(min=, max=)
@Length(min=, max=)
@Past
@Future
@Pattern
所有注解
3.7 多环境配置
用properties文件实现多环境
在application.properties中配置,即可选择不同的配置文件
spring.profiles.active=dev # 这里选择了application-dev.properties
用yaml文件实现多环境
可以只在一个文件里面实现多个配置文件的效果,只要用--- 隔开即可
server:
port: 8080
spring:
profiles:
active: dev
---
server:
port: 8081
spring:
profiles: dev
---
server:
port: 8082
spring:
profiles: test
3.8 确认自动配置类是否生效
在配置文件中加上
debug: true
即可
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
3.9 自定义starter
创建一个项目demo1
HelloProperties
package com.hjy;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hjy.hello")
public class HelloProperties {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
HelloService
package com.hjy;
public class HelloService {
private HelloProperties helloProperties;
public HelloProperties getHelloProperties() {
return helloProperties;
}
public void setHelloProperties(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
public String sayHello(String name){
return helloProperties.getPrefix() + name + helloProperties.getSuffix();
}
}
HelloServiceAutoConfiguration
package com.hjy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired
private HelloProperties helloProperties;
@Bean
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setHelloProperties(helloProperties);
return helloService;
}
}
配置META-INF/spring.factories
# 由于导入一个bean需要ComponentScan的支持,而导入一个jar包却不知道程序是否写了导入该jar包的路径,所以配置
# spring.factories的目的就是让有@EnableAutoConfiguration的启动类在一开始就扫描spring.factories,获得其
# 字节码,再以反射的方式加入容器,实现动态的注入javaBean
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hjy.HelloServiceAutoConfiguration
这一步可以使即使程序的包扫描没有扫描到自定义的starter jar包,也可以把jar包里面的javabean注入容器
打包
测试
新建一个项目,导入依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>demo1</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
然后编写controller
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@RequestMapping("hello")
@ResponseBody
public String hello(){
return helloService.sayHello("何骏扬");
}
}
配置文件
hjy.hello.prefix=帅气的
hjy.hello.suffix=今天真开心
结果 :
总结流程:
- 要编写一个starter,首先要写一个xxxproperties来获取配置文件的赋值
- 在编写一个service类来实际操作
- 最后编写一个configuration总配置类导入xxxproperties到ioc容器,并且导入service类进容器
- 编写META-INF/spring.factories
- 键为org.springframework.boot.autoconfigure.EnableAutoConfiguration
- 值为最后的configuration总配置类的全路径名
- 打包(install)
- 一个新的项目引入该依赖
- 在新项目启动的时候,@SpringBootApplication == > @EnableAutoConfiguration ==> AutoConfigurationImportSelector.class ==> getCandidateConfigurations ==>SpringFactoriesLoader.loadFactoryNames ==>loadSpringFactories ,在最后获取所有配置的字节码并且把其实例装载进容器
- 最后就可以愉快地使用了
4. web开发
4.1 静态资源导入
在WebMvcAutoConfiguration.java中,有方法
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
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));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
由此方法可知,在resources下有四个地方可以导入静态资源文件,分别为
只要访问localhost:8080/**就可以直接访问到这些静态文件
优先级 : 上图的 1 > 3 > 4 > 2 (其实也就是按顺序,顺序越前面,优先级越大)
4.2 设置首页
在WebMvcAutoConfiguration.java中,有方法
@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() {
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");
}
方法的作用是设置location + "index.html" 的资源为首页
其中location是"classpath:/META-INF/resources/",“classpath:/resources/”, “classpath:/static/”, “classpath:/public/” };
这个页面放在哪个文件夹下都可以
自定义首页
若想自定义首页,就在跳转默认首页的时候再跳转一次页面
@Controller
public class HelloController {
@RequestMapping("/")
public String hello(){
return "forward:login.html";
}
}
4.3 模板引擎thymeleaf
4.3.1 基本使用
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
写文件
注意 : 1. thymeleaf的文件一定要写在templates文件夹下,而且要导入头文件
xmlns:th="http://www.thymeleaf.org"
2. templates文件夹下的文件是不可以直接访问的,只能通过服务器内部的请求来跳转其页面(thymeleaf有自己的视图解析器),
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${message}">Welcome to BeiJing!</p>
</body>
</html>
@Controller
public class HelloController {
@RequestMapping("hello")
public String hello(Model model){
model.addAttribute("message","你好,世界");
return "hello";
}
}
原理
找到ThymeleafProperties.java文件,在里面找到两个字段,这是其视图解析器
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
所以用thymeleaf的文件必须写在templates文件夹下,且后缀为.html
4.4 自定义类扩展MVC的功能
模板
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
}
原理
进入WebMvcAutoConfiguration.java ,找到其静态内部类WebMvcAutoConfigurationAdapter,它导入了一个类EnableWebMvcConfiguration,EnableWebMvcConfiguration又继承了DelegatingWebMvcConfiguration,这个类中有这么一个方法
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
从容器中获取了所有的配置
4.4.1 日期格式
在WebMvcAutoConfiguration中,有方法
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
addFormatters(conversionService);
return conversionService;
}
再进入WebMvcProperties.java,找到Format内部类,可以看见
@Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
public String getDateFormat() {
return this.format.getDate();
}
private String date;
修改:
spring:
mvc:
format:
date: yyyy-MM-dd
4.4.2 视图跳转功能
我们可以省去一些时间写controller,而选择该方法快速的实现请求路径映射
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
}
4.5 国际化
4.5.1 基本使用
参考文档
在resources下设立i18n包,里面放三个文件
login.properties :默认
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
英文:
login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username
中文:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
然后在配置文件中配置
spring:
messages:
basename: i18n.login
使用
在前端页面中,用#{…}来获取值,可以用在各种地方
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button>
但这样只可以得到默认的文件,怎么样实现国际化呢
实现国际化
中英切换的两个按钮
<a class="btn btn-sm" th:href="@{/index.html(language='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(language='en_US')}">English</a>
然后自定义一个类,实现LocaleResolver接口
package com.hjy.config;
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 {
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("language");
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(language)){
String[] strings = language.split("_");
locale = new Locale(strings[0],strings[1]);
}
return locale;
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
最后在MVC拓展类中加入容器
package com.hjy.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
这样点击即可切换其配置文件了
4.5.2 原理
在MessageSourceAutoConfiguration.java下,有方法
@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;
}
再看MessageSourceProperties.java,有private String basename = "messages"; ,所以配置这个即可
在WebMvcAutoConfiguration.java,有方法
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
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;
}
再点开AcceptHeaderLocaleResolver.java,找到方法resolveLocale
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
由于AcceptHeaderLocaleResolver继承了LocaleResolver接口,所以我们可以自定义自己的LocaleResolver完成操作
4.6 一些常见的web开发问题
首页跳转
有两种方法,方法一为拓展springmvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
方法二为controller跳转
@Controller
public class IndexController {
@RequestMapping({"/","/index.html"})
public String index(){
return "index";
}
}
静态资源访问
在前端页面中,访问静态资源与传统的ssm有些不同
由于springboot的项目静态资源是在
“classpath:/META-INF/resources/”, “classpath:/resources/”, “classpath:/static/”, "classpath:/public/"下的,不是直接在resources下了,所以url中不用加上这些目录的目录名称
例:
要访问bootstrap.min.css,只要这样
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
设置项目虚拟路径
server:
servlet:
context-path: /hello
删除thymeleaf的缓存
spring:
thymeleaf:
cache: false
伪装url
我们登录完后跳转的页面的主页的url一般不好看,这时候我们可以采用伪装url的方式美化且伪装主页的url,原理的重定向+转发
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("login")
public String login(String username, String password, Model model){
if ("admin".equals(username)&&"123456".equals(password)){
return "redirect:/main.html";
}
model.addAttribute("message","用户名或者密码错误");
return "index";
}
}
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
这样我们首页上显示的URL是XXX/main.html,十分美观
自定义登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object username = request.getSession().getAttribute("username");
if (username==null){
request.setAttribute("message","您未登录,权限不够");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
return true;
}
}
}
再放在继承WebMvcConfigurer接口的类中
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","/index.html", "/user/login",
"/css/**","/js/**","/img/**");
}
}
公共代码片段使用问题
我们使用thymeleaf模板引擎时,可以提取一段代码片段,达到代码的复用
在commons.html下有代码片段,用th:fragment="topbar" 为其命名
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.username}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" th:href="@{/user/logout}">注销</a>
</li>
</ul>
</nav>
我们可以在别的文件中引入这个代码片段
<div th:replace="~{commons/commons::topbar}"></div>
格式 : th:replace="~{代码片段的位置::代码片段的名字}"
thymeleaf处理后端数据
判断性别
<td th:text="${emp.getGender()==0?'女':'男'}"></td>
处理日期格式
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
Springboot中自动日期格式解析
spring接受前端表单的提交数据自动解析所需要的格式是yyyy/MM/dd, 比如说 2002/11/30
我们可以自定义日期格式解析,在配置文件中设置,可使日期格式解析为 2002-11-30
spring:
mvc:
format:
date: yyyy-MM-dd
也可以是(已经过时)
spring:
mvc:
date-format: yyyy-MM-dd HH:mm:ss
错误页面跳转
在templates文件夹下建一个error文件夹,里面的文件用状态码作为文件名,如 404.html
这样爆错的时候会根据状态码找到不同的页面进行跳转
这里设置了404文件夹,如果爆了404错误,自动跳转该页面
5. 整合数据库
5.1 整合JDBC
导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo3</name>
<description>Demo project for Spring Boot</description>
<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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.hjy.Demo3Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
配置文件
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接地址(这里配置了时区)
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
配置完后,springboot会自动注册一个DataSource 和 JdbcTemplate 进入容器,使用即可
@SpringBootTest
class Demo3ApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() throws SQLException {
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}
@Controller
@ResponseBody
public class JdbcController {
@Autowired
private JdbcTemplate jdbcTemplate;
@RequestMapping("hello")
public List<Map<String, Object>> hello(){
String sql = "select * from user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
}
jdbcTemplate如果是查询,方法名以query开头,如果是别的操作,方法名以update开头
5.2 整合Druid数据源
导入依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
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,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
把配置文件的配置导入数据源
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return new DruidDataSource();
}
}
配置监控页面
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 {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return new DruidDataSource();
}
@Bean
public ServletRegistrationBean register(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin");
initParams.put("loginPassword", "123456");
initParams.put("allow", "");
bean.setInitParameters(initParams);
return bean;
}
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
访问 localhost:8080/druid 即可登录查看数据源监控
5.3 整合mybatis
导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo3</name>
<description>Demo project for Spring Boot</description>
<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>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.hjy.Demo3Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
配置文件
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
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,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
configLocation: classpath:mybatis-config.xml
mapper
@Mapper
@Repository
public interface UserMapper {
public List<User> findAll();
}
也可以在启动类上加注解
@SpringBootApplication
@MapperScan("com.hjy.mapper")
public class Demo3Application {
public static void main(String[] args) {
SpringApplication.run(Demo3Application.class, args);
}
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hjy.mapper.UserMapper">
<select id="findAll" resultType="com.hjy.pojo.User">
select * from user ;
</select>
</mapper>
mybatis-config
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.hjy.pojo"/>
</typeAliases>
</configuration>
使用
@Autowired
private UserMapper userMapper;
@Test
public void test4(){
List<User> all = userMapper.findAll();
for (User user : all) {
System.out.println(user);
}
}
6. 安全
6.1 springSecurity
6.1.1 准备工作
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
页面结构
controller
@Controller
public class TestController {
@RequestMapping({"/","/index.html"})
public String index(){
return "index";
}
@RequestMapping("toLogin")
public String login(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id") int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id") int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id") int id){
return "views/level3/"+id;
}
}
6.1.2 用户认证和授权
写在config包下
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
http.formLogin();
}
}
如果定义用户权限用数据库的话,就取出数据库的一条用户数据,然后自动向类中注入一个数据源,以下面格式即可
auth.jdbcAuthentication().dataSource(datasource).passwordEncoder(new BCryptPasswordEncoder())..
6.1.3 注销与权限控制
实现注销
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
http.formLogin();
http.logout().logoutSuccessUrl("/");
}
}
加入http.logout().logoutSuccessUrl("/"); ,其注销后的跳转页面默认为登录页面,你可以自定义其注销成功后重定向的页面
权限控制
在页面中,我们可以依据登录用户的权限动态显示页面,首先需要thymeleaf和security集成
导入依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
在每个前端页面加上
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
比如说首页,在没登录的时候显示登录按钮,登录后显示注销按钮
<div class="right menu">
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}">
<i class="address card icon"></i> 登录
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item">
<i class="address card icon"></i>
用户名:<span sec:authentication="principal.username"></span>
角色:<span sec:authentication="principal.authorities"></span>
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
还可以依据权限等级来显示页面
<div sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
一、sec:authorize
authorize是用来判断普通权限的,通过判断用户是否具有对应的权限而控制其所包含内容的显示,其可以指定如下属性。
1??sec:authorize="isAnonymous()"
2??sec:authorize="isAuthenticated()"
3??sec:authorize="hasRole('common')"
4??sec:authorize="hasAuthority('ROLE_vip')"
二、sec:authentication
authentication标签用来代表当前Authentication对象,主要用于获取当前Authentication的相关信息。
如通常我们的Authentication对象中存放的principle是一个UserDetails对象,所以我们可以通过如下的方式来获取当前用户的用户名。
1??sec:authentication="name"
2??sec:authentication="principal.username"
3??sec:authentication="principal.authorities"
4??sec:authentication="principal.password"
6.1.4 记住我和自定义登录页面
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
http.formLogin();
http.logout().logoutSuccessUrl("/");
http.formLogin().loginPage("/toLogin")
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login");
http.rememberMe().rememberMeParameter("remember");
}
}
前端form表单必须是post请求,请求地址也必须是/login
<form th:action="@{/login}" method="post">
记住我
<div class="field">
<input type="radio" name="remember"> 记住我
</div>
6.2 shiro
主要功能
shiro主要有三大功能模块:
-
Subject:主体,一般指用户。 -
SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet) -
Realms:用于进行权限信息的验证,一般需要自己实现。
细分功能
-
Authentication:身份认证/登录(账号密码验证)。 -
Authorization:授权,即角色或者权限验证。 -
Session Manager:会话管理,用户登录后的session相关管理。 -
Cryptography:加密,密码加密等。 -
Web Support:Web支持,集成Web环境。 -
Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。 -
Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。 -
Testing:测试支持; -
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。 -
Remember Me:记住我,登录后,下次再来的话不用登录了。
6.2.1 设置用户认证和登录拦截和跳转登录页面
导入依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
controller
@Controller
public class UserController {
@RequestMapping({"/","index","index.html"})
public String index(Model model){
model.addAttribute("message","hello shiro");
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";
}
}
自定义的realm
public class Realms extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权==>AuthorizationInfo");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("授权==>AuthorizationInfo");
return null;
}
}
shiroConfig
@Configuration
public class ShiroConfig {
@Bean
public Realms realms(){
return new Realms();
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(Realms realms){
DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
bean.setRealm(realms);
return bean;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
Map<String, String> map = new HashMap<>();
map.put("/user/**", "authc");
bean.setFilterChainDefinitionMap(map);
bean.setLoginUrl("/toLogin");
return bean;
}
}
6.2.2 用户认证
登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<p th:text="${msg}"></p>
<form action="login" method="post">
<p>用户名: <input type="text" name="username"></p>
<p>密码: <input type="password" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
</div>
</body>
</html>
controller处理登录请求
@RequestMapping("login")
public String login(Model model,String username,String password){
Subject subject = SecurityUtils.getSubject();
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";
}
}
自定义的realms类中认证用户
public class Realms extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权==>AuthorizationInfo");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证==>AuthorizationInfo");
String username = "admin";
String password = "123456";
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if (!token.getUsername().equals(username)){
return null;
}
return new SimpleAuthenticationInfo("",password,"");
}
}
6.2.3 整合mybatis
在整合mybatis后,只需要修改认证的部分即可
import com.hjy.mapper.UserMapper;
import com.hjy.pojo.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
public class Realms extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权==>AuthorizationInfo");
return null;
}
@Autowired
private UserMapper userMapper;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User byName = userMapper.findByName(token.getUsername());
if (byName == null) {
return null;
}
return new SimpleAuthenticationInfo("", byName.getPassword(), "");
}
}
6.2.4 用户授权
以下为所有配置
Realms
package com.hjy.config;
import com.hjy.mapper.UserMapper;
import com.hjy.pojo.User;
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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class Realms extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
String[] s = user.getPerms().split(" ");
for (String s1 : s) {
authorizationInfo.addStringPermission(s1);
}
return authorizationInfo;
}
@Autowired
private UserMapper userMapper;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userMapper.findByName(token.getUsername());
if (user == null) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}
ShiroConfig
package com.hjy.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public Realms realms(){
return new Realms();
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(Realms realms){
DefaultWebSecurityManager bean = new DefaultWebSecurityManager();
bean.setRealm(realms);
return bean;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
Map<String, String> map = new HashMap<>();
map.put("/user/add", "perms[user:add]");
map.put("/user/update","perms[user:update]");
bean.setFilterChainDefinitionMap(map);
bean.setLoginUrl("/toLogin");
bean.setUnauthorizedUrl("/unAuthorize");
return bean;
}
}
UserController
package com.hjy.controller;
import org.apache.shiro.SecurityUtils;
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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@RequestMapping({"/","index","index.html"})
public String index(Model model){
model.addAttribute("message","hello shiro");
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(Model model,String username,String password){
Subject subject = SecurityUtils.getSubject();
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("unAuthorize")
@ResponseBody
public String unAuthorize(){
return "您未授权,请获得授权后再进行操作";
}
}
7. swagger
7.1 快速开始
swagger可以管理后端中的接口,可以在页面中测试每个扫描到的请求,类似postman
导入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
配置类SwaggerConfig
import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
}
访问页面
http://localhost:8080/swagger-ui.html
7.2 自定义信息
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
private ApiInfo apiInfo(){
Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
return new ApiInfo(
"何骏扬的接口文档",
"请叫我何帅哥",
"1.0",
"https://blog.csdn.net/qq_51677409",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<VendorExtension>());
}
}
7.3 配置扫描的接口
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
.paths(PathSelectors.ant("/hjy/**"))
.build();
}
private ApiInfo apiInfo(){
Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
return new ApiInfo(
"何骏扬的接口文档",
"请叫我何帅哥",
"1.0",
"https://blog.csdn.net/qq_51677409",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<VendorExtension>());
}
}
使用建造者模式
7.4 使用swagger的开关
在项目正式上线的时候,我们肯定是不会使用swagger技术,所以我们可以动态的管理swagger的开关,使用enable(boolean)
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${flag}")
private boolean flag;
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag)
.select()
.apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
.paths(PathSelectors.ant("/hjy/**"))
.build();
}
private ApiInfo apiInfo(){
Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
return new ApiInfo(
"何骏扬的接口文档",
"请叫我何帅哥",
"1.0",
"https://blog.csdn.net/qq_51677409",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<VendorExtension>());
}
}
或者使用多环境配置,通过判断你使用的环境判断是否开启swagger
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(Environment environment){
Profiles profiles =Profiles.of("dev","test");
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag)
.select()
.apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
.paths(PathSelectors.ant("/hjy/**"))
.build();
}
private ApiInfo apiInfo(){
Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
return new ApiInfo(
"何骏扬的接口文档",
"请叫我何帅哥",
"1.0",
"https://blog.csdn.net/qq_51677409",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<VendorExtension>());
}
}
7.5 组
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.groupName("何骏扬")
.select()
.apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
.paths(PathSelectors.ant("/hjy/**"))
.build();
}
private ApiInfo apiInfo(){
Contact contact = new Contact("何骏扬", "https://blog.csdn.net/qq_51677409", "2402668109@qq.com");
return new ApiInfo(
"何骏扬的接口文档",
"请叫我何帅哥",
"1.0",
"https://blog.csdn.net/qq_51677409",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList<VendorExtension>());
}
}
7.6 注释
注释汇总
作用范围 | API | 使用位置 |
---|
对象属性 | @ApiModelProperty | 用在出入参数对象的字段上 | 协议集描述 | @Api | 用于controller类上 | 协议描述 | @ApiOperation | 用在controller的方法上 | Response集 | @ApiResponses | 用在controller的方法上 | Response | @ApiResponse | 用在 @ApiResponses里边 | 非对象参数集 | @ApiImplicitParams | 用在controller的方法上 | 非对象参数描述 | @ApiImplicitParam | 用在@ApiImplicitParams的方法里边 | 描述返回对象的意义 | @ApiModel | 用在返回对象类上 | 描述参数 | @ApiParam | 用在controller方法参数上 |
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("用户")
public class User {
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
}
@RestController
@Api(tags={"用户操作接口"})
public class HelloController {
@RequestMapping("hjy/hello")
public String hello(){
return "hello";
}
@ApiOperation("得到一个user对象")
@RequestMapping("hjy/user")
public User user(@ApiParam("用户名") String username,@ApiParam("密码") String password){
return new User(username,password);
}
}
8. 任务
8.1 异步任务
- 在启动类上加注解
@EnableAsync - 在想被异步任务的注解上加上
@Async
@SpringBootApplication
@EnableAsync
public class Demo5Application {
public static void main(String[] args) {
SpringApplication.run(Demo5Application.class, args);
}
}
@Service
public class HelloService {
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据执行完毕");
}
}
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@RequestMapping("hello")
public String hello(){
helloService.hello();
return "ok";
}
}
访问http://localhost:8080/hello的时候,直接把helloService.hello();当多线程跳过了,两边同时执行
8.2 邮件任务
- 首先设置配置文件
# 固定
spring.mail.host=smtp.qq.com
# 授权码
spring.mail.password=pcafgozgzwgxebdc
# 发送者邮箱
spring.mail.username=2402668109@qq.com
# qq邮箱必须写
spring.mail.properties.mail.smtp.ssl.enable=true
# 后面这两个可以不写
# 邮件发送过程的日志会在控制台打印出来,方便排查错误
spring.mail.properties.mail.debug=true
# 端口
spring.mail.port=465
126邮箱SMTP服务器地址:smtp.126.com,端口号:465或者994 2163邮箱SMTP服务器地址:smtp.163.com,端口号:465或者994 yeah邮箱SMTP服务器地址:smtp.yeah.net,端口号:465或者994 qq邮箱SMTP服务器地址:smtp.qq.com,端口号465或587
- 发送邮件
@SpringBootTest
class Demo5ApplicationTests {
@Autowired
private JavaMailSenderImpl sender;
@Test
void contextLoads1() throws SQLException {
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
simpleMailMessage.setSubject("爷的主题");
simpleMailMessage.setText("爷的简单邮件");
simpleMailMessage.setFrom("2402668109@qq.com");
simpleMailMessage.setTo("2402668109@qq.com");
sender.send(simpleMailMessage);
}
@Test
void contextLoads2() throws SQLException, MessagingException {
MimeMessage mimeMessage = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
helper.setSubject("爷的主题");
helper.setText("<p style='color:red'>爷的复杂邮件</p>", true);
helper.addAttachment("1.png", new File("src/main/resources/1.png"));
helper.setTo("2402668109@qq.com");
helper.setFrom("2402668109@qq.com");
sender.send(mimeMessage);
}
}
带图片资源邮件
@Test
public void sendImgResMail() throws MessagingException {
MimeMessage mimeMessage = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
helper.setSubject("这是一封测试邮件");
helper.setFrom("2402668109@qq.com");
helper.setTo("2402668109@qq.com");
helper.setSentDate(new Date());
helper.setText("<p>hello 大家好,这是一封测试邮件,这封邮件包含两种图片,分别如下</p><p>第一张图片:</p><img src='cid:p01'/><p>第二张图片:</p><img src='cid:p02'/>",true);
helper.addInline("p01",new FileSystemResource(new File("/Users/gamedev/Desktop/压缩.jpeg")));
helper.addInline("p02",new FileSystemResource(new File("/Users/gamedev/Desktop/瑞文.jpg")));
javaMailSender.send(mimeMessage);
}
8.3 定时任务
- 启动类上加上注解
@EnableScheduling - 定时执行的方法上加上
@Scheduled
@SpringBootApplication
@EnableScheduling
public class Demo5Application {
public static void main(String[] args) {
SpringApplication.run(Demo5Application.class, args);
}
}
@Service
public class HelloService {
@Scheduled(cron = "0/2 * * * * ?")
public void hello2(){
System.out.println("任务执行了");
}
}
cron表达式直接去https://cron.qqe2.com/生成即可
9. Zookeeper和dobbo
9.1 下载
zookeeper : https://zookeeper.apache.org/
然后解压,打开config,赋值粘贴zoo_sample.cfg,再改名为zoo.cfg
如果还是不行在zkServer.cmd的最后面加pause,看是哪里出了问题
运行zkServer.cmd即可
dobbo-admin : https://github.com/apache/dubbo-admin/tree/master
然后在项目C:\Users\hjy\Desktop\dubbo-admin-develop 直接执行
mvn clean package -Dmaven.test.skip=true
C:\Users\hjy\Desktop\dubbo-admin-develop\dubbo-admin-distribution\target
再执行
java -jar .\dubbo-admin-0.3.0.jar
如果jdk版本高,则会报错
9.2 使用
参考地址
|