SpringBoot使用手册
本文最后更新于:2021年3月19日 晚上
SpringBoot开冲,Spring的配置实在是太太太太多了。
一、SpringBoot概述
Maven、Spring、SpringMVC、SpringBoot均约定大于配置
微服务架构。
1)特点
- 开箱即用,没有代码生成,也无需XML配置,同时也可以修改默认值来满足特定的需求
- 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标、健康监测等等
- SpringBoot不是Spring功能上的增强,而是提供了一种快速使用Spring的方式
2)核心功能
- 起步依赖:将具备某些功能的坐标打包在一起,并提供一些默认功能。(本质上是一个Maven项目对象模型POM)
- 自动配置:应用程序启动时,自动地完成相应配置
谈谈你对SpringBoot的理解:
SpringBoot提供了一种快速使用Spring的方式,无需繁杂的xml文件配置;其可以在应用程序启动时自动装配组件(自动装配的原理巴拉巴拉…;SpringBoot通过SpringBootApplication注解标注引导类,然后再通过SpringApplication.run方法运行该引导类。这个SpringApplciation在我看来,主要做了四个事吧…
二、HelloSpringBoot
1)spring initializer
- 在使用IDEA创建Spring Boot项目的时候,报
Artifact contains illegal characters
”:把Artifact的内容都改成小写;例如 : “Demo” 改为 “demo”
2)配置POM文件
- 继承SpringBoot的起步依赖
spring-boot-starter-parent
- 由于导入了父依赖,因此在导入SpringBoot相关依赖时,无需自己再指定版本了
- 导入web的启动依赖以使SpringBoot集成SpringMVC进行Controller的开发
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- SpringBoot的起步依赖 -->
<!-- 所有的SpringBoot工程都必须继承spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
<!-- 项目名、版本、组织等 -->
<groupId>com.gaowl</groupId>
<artifactId>SpringBoot_HelloWorld</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 可以实现热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- web功能的启动依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 打jar包用 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3)工程热部署
开启项目自动编译 | 同时按住 Ctrl + Shift + Alt + / 然后进入Registry ,勾选自动编译并调整延时参数 |
---|---|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
4)修改端口号、banner
- 在application.properties等配置文件中输入
server.port=8081
即可自定义端口号 - banner在线生成网站:https://www.bootschool.net/ascii
- 在resource文件夹下,新建banner.txt,粘贴上述网站中生成的信息,即可自定义banner
5)程序入口和控制器
package com.gaowl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// SpringBoot应用程序的启动入口
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
// 此处是SpringApplication!!!!不是SpringBootApplication
// run方法 表示运行SpringBoot的引导类,其参数用SpringBoot引导类的字节码即可
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}
SpringApplication.run这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
package com.gaowl.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test")
public String test(){
return "测试环境已成功配置...";
}
}
三、SpringBoot的配置文件
1)几种配置文件类型
application.yml
、application.yaml
或者application.properties
- 其中
application.properties
优先级最高,application.yml
优先级最低 - 更推荐yml,比properties不知道好用到哪了去了
- 其中
yaml基本语法:其通过空格来控制层级关系
# 1.普通数据的配置 key: value # 2.对象配置 map: key1: value1 key2: value2 # 示例 person: name: sam age: 18 addr: beijing # 行内写法 person: {name: sam, age: 18, addr: beijing} # 3.配置数组(list、set) city: - beijing - tianjian - chongqing city: [beijing, tianjin, chongqing] # 4.配置集合等 student: - name: tom age: 18 addr: beijing - name: sam age: 20 addr: tianjin student: [{name: tom, age: 18, addr: beijing},{name: sam, age: 20, addr: tianjin}]
2)配置文件 中的信息 注入到java中
@ConfigurationProperties |
@Value |
|
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(转换为驼峰命名) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
@ConfigurationProperties
映射// 在类上面 @ConfigurationProperties(prefix="person") 会自动从配置文件中寻找 // 添加预处理依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> // 然后在类里面,直接创建变量就好,无需再@value,比如 private String name; private String addr;
@Value
映射@Value("${name}") private String name; @Value("${person.addr}") private String addr;
示例:
实体类Person
package com.gaowl.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; @Component @ConfigurationProperties(prefix = "person") // 自动到yml等配置文件中寻找person,进行绑定 @Data @AllArgsConstructor @NoArgsConstructor public class Person { private String firstName; private Integer age; private Boolean happy; private Date birth; private Map<String, Object> maps; private List<Object> lists; private Dog dog; }
配置文件application.yml
person: first-name: gaowl age: 23 happy: true bitth: 2021/1/9 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: ${person.happy} age: ${random.int}
3)JSR303数据校验
Springboot中可以用@validated
注解来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理
@Component // 注册bean
@ConfigurationProperties(prefix = "person")
@Validated // 开启数据校验
public class Person {
@Email(message="邮箱格式错误") // 不符合邮箱格式时,弹出提示消息
private String email_addr;
}
常用的校验注解:
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
1.空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
2.Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
3.长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 @Length(min=, max=) string is between min and max included.
4.日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
4)多环境切换
配置文件的设置路径有以下四种,高优先级的配置会覆盖低优先级的配置。
./config/application.yml
> ./config/application.yml
> ./src/main/resource/config/application.yml
> ./src/main/resource/application.yml
yaml形式的的环境切换
server: port: 8081 spring: profiles: active: dev --- server: port: 8082 spring: profiles: dev --- server: port: 8083 spring: profiles: test
properties形式,需要创建多个properties文件(如
application-dev.properties
、application-test.properties
),然后在以applicaiton.properties
命名的文件中指定要激活的环境spring.profiles.active=dev
此外,我们还可以通过spring.config.location
来改变默认的配置文件位置:
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
四、自动装配原理
待补充,没怎么听懂
五、SpringMVC
1)静态资源访问
只要将静态资源(css、js、img等)放在/static
、/public
、/resources
或者INF/resource
文件夹下,即可通过当前项目根路径/ + 静态资源名称
访问
SpringBoot中请求进来时,先去找Controller看能不能处理,不能处理的所有请求均交给静态资源处理器;静态资源也找不到时,报404
改变默认的静态资源路径的方法:
spring: mvc: static-path-pattern: /res/** # 前缀,访问时需在8080后加上该前缀才可访问 resources: static-locations: [classpath:/haha/]
静态资源访问时,无需前缀,只要路径对了直接静态资源名即可
webjars的访问地址,要按照依赖里面具体的包路径:如
http://localhost:8080/webjars/jquery/3.5.1/jquery.js
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.5.1</version> </dependency>
2)首页和图标订制
- 在静态资源支持的路径下,放置的
index.html
即为首页 - 在静态资源支持的路径下,放置的
favicon.ico
即为网站的图标
3)thymeleaf 模板引擎
Thymeleaf是一张 适用于Web和独立环境 的现代 服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。
📚 基本语法:
🔎 常用的
th:*
标签及其优先级
4)员工管理系统
六、整合数据库
对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。
1)整合JDBC
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
编写yaml配置文件连接数据库
spring: datasource: username: root password: root #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver
查看当前数据源
SpringBoot默认的数据源为HikariDataSource
@SpringBootTest class SpringbootDataJdbcApplicationTests { // 注入数据源 @Autowired DataSource dataSource; @Test public void contextLoads() throws SQLException { //看一下默认数据源 System.out.println(dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); //关闭连接 connection.close(); } }
使用
package com.kuang.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.List; import java.util.Map; @RestController @RequestMapping("/jdbc") public class JdbcController { /** * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作 * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接 */ @Autowired JdbcTemplate jdbcTemplate; //查询employee表中所有数据 //List 中的1个 Map 对应数据库的 1行数据 //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值 @GetMapping("/list") public List<Map<String, Object>> userList(){ String sql = "select * from employee"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } //新增一个用户 @GetMapping("/add") public String addUser(){ //插入语句,注意时间问题 String sql = "insert into employee(last_name, email,gender,department,birth)" + " values ('狂神说','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')"; jdbcTemplate.update(sql); //查询 return "addOk"; } //修改用户信息 @GetMapping("/update/{id}") public String updateUser(@PathVariable("id") int id){ //插入语句 String sql = "update employee set last_name=?,email=? where id="+id; //数据 Object[] objects = new Object[2]; objects[0] = "秦疆"; objects[1] = "24736743@sina.com"; jdbcTemplate.update(sql,objects); //查询 return "updateOk"; } //删除用户 @GetMapping("/delete/{id}") public String delUser(@PathVariable("id") int id){ //插入语句 String sql = "delete from employee where id=?"; jdbcTemplate.update(sql,id); //查询 return "deleteOk"; } }
2)整合Druid数据源
添加Druid数据源相关依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
切换数据源
如果允许时报错”java.lang.ClassNotFoundException: org.apache.log4j.Priority”,则导入 log4j 依赖即可
spring: datasource: username: root password: root #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
自定义数据源配置类DruidConfig.java
package com.gaowl.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { // 将 全局配置文件中前缀为 spring.datasource的属性值注入到com.alibaba.druid.pool.DruidDataSource 的同名参数中 @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource() { return new DruidDataSource(); } // 内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式 @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到 Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); //后台管理界面的登录账号 initParams.put("loginPassword", "123456"); //后台管理界面的登录密码 // 为localhost,只有本机可以访问;为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问 bean.setInitParameters(initParams); return bean; } //配置 Druid 监控 //WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计 @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
测试
@SpringBootTest class Springboot04DataApplicationTests { @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { //看一下默认数据源 System.out.println("当前数据源为:" + dataSource.getClass()); //获得连接 Connection connection = dataSource.getConnection(); System.out.println(connection); DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println("druidDataSource 数据源最大连接数:" + druidDataSource.getMaxActive()); System.out.println("druidDataSource 数据源初始化连接数:" + druidDataSource.getInitialSize()); //关闭连接 connection.close(); } }
3)整合Mybatis
导入依赖
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>
配置Druid数据源,连接MySQL数据库,并整合mybatis
spring: datasource: username: root password: root #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf-8 driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 filters: stat,wall,log4j maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 server: port: 8081 mybatis: type-aliases-package: com.gaowl.pojo mapper-locations: classpath:mybatis/mapper/*.xml
创建实体类、接口、接口实现
和mybatis的没差
创建控制器类,测试
package com.gaowl.controller; import com.gaowl.dao.UserMapper; import com.gaowl.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class UserController { @Autowired UserMapper userMapper; @RequestMapping("/users") public List<User> getUsers(){ List<User> users = userMapper.getUsers(); for (User user : users) { System.out.println(user); } return users; } @GetMapping("/getUserById/{id}") public User getUserById(@PathVariable("id") Integer id){ return userMapper.getUserById(id); } }
七、Spring Security
官网介绍:Spring Security是一个功能强大且高度可定制的身份验证(Authentication)和访问控制(授权,Authorization)框架,其侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求。
WebSecurityConfigurerAdapter
:自定义Security策略AuthenticationManagerBuilder
:自定义认证策略@EnableWebSecurity
:开启WebSecurity模式
1)导入相关依赖项
- spring-boot-starter-security
- spring-boot-starter-thymeleaf
- thymeleaf-extras-springsecurity5
- …
<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>
<!-- 基于SpringSecurity对模板引擎进行增强 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
2)自定义安全策略
- 在config文件夹下创建
SecurityConfig
类自定义安全策略- 通过注解
@EnableWebSecurity
开启安全模式 - 其继承自
WebSecurityConfigurerAdapter
类,并重写configure
方法:- 定制请求的授权规则
- 通过注解
package com.gaowl.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 重写Http的安全规则
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定制请求的授权规则,此时写完了大家都木得权限,因此,需要重写认证规则,设置用户的权限
http.authorizeRequests().antMatchers("/").permitAll() // 首页所有人可以访问
.antMatchers("/level1/**").hasRole("vip1") // level1下的界面,只有拥有vip1权限才可访问
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
// 开启登录功能,控制器处理"/toLogin"请求,来到自定义的登陆页
// 登陆页传递参数username和password
// 登陆页中的<form th:action="@{/login}" method="post">表明提交请求为login,因此加一个loginProcessingUrl鉴权
// 若其action为toLogin,后面的loginProcessingUrl可以删掉。
http.formLogin().usernameParameter("username").passwordParameter("password")
.loginPage("/toLogin").loginProcessingUrl("/login");
// 关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();
// 开启账户注销功能,账号成功注销后跳转到首页
http.logout().logoutSuccessUrl("/");
// 开始记住用户名及密码功能,cookie
http.rememberMe().rememberMeParameter("remember");
}
// 重写定义认证规则:
// 为不同的用户设置权限,且对密码进行bcrypt加密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 在内存中定义,也可以在jdbc中去拿....
// Spring security 5.0中之后,将前端传过来的密码进行某种方式加密,spring security 官方推荐的是使用bcrypt加密方式。
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("ks").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");
}
}
3)自定义主页等
- 控制器代码:
package com.gaowl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
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;
}
}
- 视图代码:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>首页</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/qinjiang/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!--主容器-->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<!--登录注销-->
<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>
<i class="address card icon"></i>
权限:<span sec:authentication="principal.authorities"></span>
</a>
</div>
<!-- 如果已登录,显示注销按钮 -->
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="address card icon"></i> 注销
</a>
</div>
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study by 秦疆</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<div class="column" 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>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>
</body>
</html>
八、Shiro
1)概述
Shiro也是一种高效的安全框架,其主要的功能有:认证、鉴权、session管理、编码加密、缓存等等
2)HelloWorld
- 获取当前用户对象:Subject currentUser = SecurityUtils.getSubject();
- 获取当前用户对象的session:Session session = currentUser.getSession();
- 是否已认证:currentUser.isAuthenticated()
- 用户登陆:currentUser.login(token)
- currentUser.getPrincipal()
- 是否拥有角色:currentUser.hasRole(“schwartz”)
- 是否拥有权限:currentUser.isPermitted(“lightsaber:wield”)、currentUser.isPermitted(“winnebago:drive:eagle5”)
- 用户登出:currentUser.logout();
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// 创建默认安全管理器,然后将配置文件shiro.ini加载到该安全管理器中
DefaultSecurityManager securityManager = new DefaultSecurityManager();
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
// 获取当前用户对象
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户拿到shiro的session,存值、取值
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("取值正确! [" + value + "]");
}
// 如果当前用户未被认证
if (!currentUser.isAuthenticated()) {
// 设置其token(令牌)为true
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token); // 通过该token登陆当前用户
} catch (UnknownAccountException uae) {
log.info("不存在该用户:" + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("用户 " + token.getPrincipal() + " 的密码不正确");
} catch (LockedAccountException lae) {
log.info("用户 " + token.getPrincipal() + " 已被锁定,请寻找管理员解锁 " +
"Please contact your administrator to unlock it.");
}
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
log.info("用户 [" + currentUser.getPrincipal() + "] 登陆成功.");
// 当前用户是否有角色
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
// 测试权限(粗粒度)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
// 测试权限(细粒度)
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " );
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
currentUser.logout();
System.exit(0);
}
}
3)和SpringBoot整合
导入相关依赖
<!-- Subject 用户、SecurityManager 管理所有用户、Realm 连接数据 --> <!-- 安全框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <!-- 模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <!-- 数据库连接驱动 mysql-connector-java --> <!-- mybatis驱动 mybatis-spring-boot-starter --> <!-- Druid数据源 --> <!-- log4j日志 -->
编写Shiro配置
package com.gaowl.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; // 声明为配置类,替代原先的xml配置文件 @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager); /* 添加shiro的内置过滤器 * anon: 无需认证即可; authc: 必须认证过了才可访问 * user:必须拥有 记住我 功能才可访问; perms:拥有对某个资源的权限时才可访问 * role:拥有某个角色权限才可访问 * */ // 拦截 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add","perms[user:add]"); // 有先后顺序 filterMap.put("/user/update","perms[user:update]"); // 有先后顺序 filterMap.put("/user/*","authc"); bean.setFilterChainDefinitionMap(filterMap); // 拦截后跳转到的界面 bean.setLoginUrl("/toLogin"); bean.setUnauthorizedUrl("/noauth"); return bean; } // 创建默认安全管理器,并装配Realm对象 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; } // 创建Realm对象:其调用我们自定义的UserRealm类 @Bean public UserRealm userRealm(){ return new UserRealm(); } // 整合ShiroDialect以整合Shiro thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
九、Swagger
1)概述
可是实现==Api文档 与 Api定义实时更新==
可以直接运行,在线运行api接口
支持多种语言
2)和SpringBoot集成
导入相关依赖
<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>
当swagger版本大于3.0时,仅导入springfox-boot-starter,然后在浏览器中访问
http://localhost:8080/swagger-ui/
即可拥有文档功能;编写Swagger配置文件
package com.gaowl.config; 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.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; @Configuration @EnableSwagger2 public class SwaggerConfig { // 配置Swagger的Docket的bean实例 @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2).apiInfo( MyApiInfo()); } // 自定义Swagger信息 private ApiInfo MyApiInfo(){ // 作者信息 Contact contact = new Contact("gaowl", "https://www.jngwl.top/", "462549693@qq.com"); return new ApiInfo("Api Documentation by gaowl", "集中一点,登峰造极", "1.0", "https://www.jngwl.top/", contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } }
此时,便可以通过
http://localhost:8080/swagger-ui.html#/
查看文档了自定义Docket
- 扫描哪些端口:
RequestHandlerSelectors
- 如何使swagger在开发环境时使用,在发布时禁用:
enable
。 - 通过
groupName
进行分组,然后创建对个Docket方法
- 扫描哪些端口:
@Bean
public Docket docket(Environment environment){
// 获取项目的环境
Profiles profile = Profiles.of("dev","test"); // 当为开发和测试环境时,才开启swagger
boolean flag = environment.acceptsProfiles(profile);
return new Docket(DocumentationType.SWAGGER_2).groupName("AAAAA")
.apiInfo(myApiInfo()).enable(flag)
.select()
// RequestHandlerSelectors 配置要扫描接口的方式
// basePackage 指定要扫描哪个包下的接口
// any 扫描全部;none 都不扫描;
// withClassAnnotation 扫描带有某种注解的方法(需传入GetMapping.class这种注解的反射对象)
.apis(RequestHandlerSelectors.basePackage("com.gaowl.controller"))
.build();
}
一些注解
待补充
十、任务
假设我们现在有一个需求:每半个小时向某个邮箱发送一封邮件,此时便涉及到两个问题:如何发送邮件、如何定时执行某项任务;此时,前台界面在邮件发送时是静止不动的,还是前台先响应,然后后台自己去发送邮件。因此,异步任务、定时任务、邮件任务应运而生。
1)异步任务
- 在SpringBoot引导类加上
@EnableAsync
注解 - 在需要异步执行的方法的加上
@Async
注解
2)邮件任务
引入邮件依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
基本使用
@Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() throws MessagingException { /* SimpleMailMessage message = new SimpleMailMessage(); message.setSubject("测试邮件功能"); message.setText("Hello, Are you ok?"); message.setTo("gaowl@stu.jiangnan.edu.cn"); message.setFrom("462549693@qq.com"); mailSender.send(message);*/ MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("假设这里是主题"); helper.setText("<b style='color:red'>有无格式啊</b>",true); helper.addAttachment("1.jpg",new File("C:\\Users\\GWL\\Desktop\\springboot-10-tasks\\src\\main\\resources\\1.jpg")); helper.setTo("gaowl@stu.jiangnan.edu.cn"); helper.setFrom("462549693@qq.com"); mailSender.send(mimeMessage); }
3)定时执行任务
在SpringBoot引导类上,加上
@EnableScheduling
注解在要定时执行的方法上,加上
@Scheduled(cron = "0 56 14 * * *")
注解- cron表达式:
- 6位分别为:
秒 分 时 日 月 周几
- 在线生成与校验:http://www.bejson.com/othertools/cron/
- 6位分别为:
4)练习
在网页上设置一个邮件按钮,当开启按钮时,定时发送邮件
视图层
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <form th:action="@{/autoMail}" method="post" > <div> 自动发送邮件 <input type="checkbox" name="autoMail" value="on"> 开启 </div> <div> <input type="submit" value="保存"> </div> <p style="color: red" th:text="${msg}" ></p> </form> </body> </html>
控制层
package com.gaowl.controller; import com.gaowl.service.AsyncService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.mail.MessagingException; @Controller public class AsyncController { AsyncService asyncService; @Autowired public void setAsyncService(AsyncService asyncService) { this.asyncService = asyncService; } @RequestMapping("/") public String index(){ return "index"; } @RequestMapping("/autoMail") public String autoMail(@RequestParam("autoMail") String autoMail, Model model) throws MessagingException { if(autoMail == "on"){ model.addAttribute("msg","开启成功..."); asyncService.autoMail(); } return "index"; } }
服务层
package com.gaowl.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; @Service // 将该类标注为service类,并注入到spring容器中 public class AsyncService { @Autowired JavaMailSenderImpl mailSender; @Async @Scheduled(cron = "0/10 * * * * *") public void autoMail() throws MessagingException { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("假设这里是主题"); helper.setText("<b style='color:red'>有无格式啊</b>",true); helper.setTo("gaowl@stu.jiangnan.edu.cn"); helper.setFrom("462549693@qq.com"); mailSender.send(mimeMessage); } }
改进处:ajax、邮件发送的日志
- 一看就会,一练就废。太tm菜了
十一、分布式
1)概述
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。
- 分布式和集群的区别:待补充
- RPC,远程过程调用Remote Procedure Call,其允许程序调用另一个地址空间的过程或者函数。
- 推荐文章:https://www.jianshu.com/p/2accc2840a1b
- RPC的两个核心:通信以及序列化
2)Dubbo + ZooKeeper
🟡Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
- 下载地址:
- 官方文档:https://dubbo.apache.org/zh/docs/v2.7/user/
服务提供商(Provider)在注册中心(Registy)注册自己的服务,然后服务消费者(Consumer)需要使用该服务时,先向注册中心订阅该服务,注册中心批准后,用户才可以调用该服务;期间,监控中心(Monitor)会对消费者和提供商均进行监控,监控调用时间和调用次数
🟡 常采用zookeeper作为注册中心,其在Windows下的安装使用流程如下:
- 在清华源下载对应的文件
apache-zookeeper-3.5.8-bin.tar.gz
, - 解压缩,到
/conf
文件夹下根据zoo_sample.cfg
生成zoo.cfg
- 然后,到bin文件夹下,在
zkServer.cmd
中加入pause,双击即可启动 - 通过双击
zkCli.cmd
测试是否安装成功
🟡 Dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。为了让用户更好的管理监控众多的dubbo服务,可以安装dubbo-admin进行可视化
- 在项目目录下打包文件(跳过测试):
mvn clean package -Dmaven.test.skip=true
- 执行jar包:
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
直接双击注册为服务也可以,但注册为服务后,就在后台一直跑了,再执行jar包可能就会报端口错误
浏览器界面:http://localhost:7001/,账号密码均为root。该端口可通过`src\main\resources \application.properties`指定
🟡 SpringBoot + Dubbo + zookeeper
(1)启动注册中心zookeeper
(2)配置服务提供商
新建项目provider-server,导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> <!--排除这个slf4j-log4j12--> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
新建 服务及其实现类
package com.gaowl.service; public interface TicketService { public String getTicket(); }
package com.gaowl.service; import org.apache.dubbo.config.annotation.Service; import org.springframework.stereotype.Component; @Service // 发布服务(此处导入的是dubbo的service注解) @Component // 注册到Spring中 public class TicketServiceImpl implements TicketService { @Override public String getTicket() { return "垃圾IDEA"; } }
配置spring的application.properties
#当前应用名字 dubbo.application.name=provider-server #注册中心地址 dubbo.registry.address=zookeeper://127.0.0.1:2181 #扫描指定包下服务 dubbo.scan.base-packages=com.gaowl.service server.port=8081
运行SpringBoot的引导类,向zookeeper注册服务
(3)配置用户端
新建项目consumer-server,导入和
provider-server
相同的依赖编写用户服务
package com.gaowl.service; import org.apache.dubbo.config.annotation.Reference; import org.springframework.stereotype.Service; @Service //注入到容器中 public class UserService { @Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名 TicketService ticketService; public void bugTicket(){ String ticket = ticketService.getTicket(); System.out.println("在注册中心买到"+ticket); } }
将服务提供商的的接口文件
TicketService.java
拷贝过来,放在相同的路径开始测试
package com.gaowl; import com.gaowl.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ConsumerServerApplicationTests { @Autowired UserService userService; @Test void contextLoads() { userService.bugTicket(); } }
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!