SpringBoot 学习
本文最后更新于:1 年前
SpringBoot 学习
1 Spring Boot 是什么?
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。
在 Spring 框架这个大家族中,产生了很多衍生框架,比如 Spring、SpringMVC 框架等,Spring 的核心内容在于控制反转( IOC )和依赖注入( DI ),所谓控制反转并非是一种技术,而是一种思想,在操作方面是指在 Spring 配置文件中创建,依赖注入即为由 Spring 容器为应用程序的某个对象提供资源,比如 引用对象、常量数据等。
Spring Boot 是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring众多框架中所需的大量且繁琐的配置文件,所以 Spring Boot 是一个服务于框架的框架,服务范围是简化配置文件。
Spring Boot 最明显的特点是,让文件配置变的相当简单、让应用部署变的简单( Spring Boot 内置服务器,并装备启动类代码),可以快速开启一个 Web 容器进行开发。
1.1 程序打包
打包可能需要在 build 的 plugins 中加入以下插件,也可以通过降低 Spring Boot 的版本2.3。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.4.3</version> </plugin> <!--spring-boot默认的配置,没有这个打包插件--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> </plugin>
2 配置如何编写 yaml
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak注意:冒号之后必须加空格
3 自动装配原理
3.1 自动配置
pom.xml
- spring-boot-dependencies: 核心依赖在父工程中
- 我们在写或者引入一些 Spring Boot 依赖时候,不需要指定版本,就是因为有这些版本仓库
3.2 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>- 说白了,启动器就是 Spring Boot 启动场景。
- 比如 spring-boot-starter-web ,他就会帮我们自动导入 web 环境的所有依赖!
- Spring Boot 会将所有的功能场景,都变成一个个启动器
- 我们要使用什么功能,就只需找到对应的启动器就可以 stater
3.3 主程序
// @SpringBootApplication: 标注这个类是一个Spring Boot 应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}3.4 注解
@SpringBootConfiguration // Spring Boot 的配置
@Configuration // Spring 的配置类
@Component // 说明这也是一个Spring的组件
@EnableAutoConfiguration // 自动配置包
@AutoConfigurationPackage // 自动配置包
@Import({AutoConfigurationPackages.Registrar.class}) // 自动配置包注册
@Import({AutoConfigurationImportSelector.class}) // 自动配置导入选择// 获取所有的配置
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}META-INF/spring.factories // 自动配置的核心文件
4 Web开发
4.0 配置 log4j2
修改 pom.xm
<!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions><!-- 去掉springboot默认配置 --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!--log4j2--> <dependency> <!-- 引入log4j2依赖 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>编写配置文件
需要加入 JVM 参数 :-Dlog4j.skipJansi=false
如果自定义了文件名,需要在application.yml中配置
logging: config: xxxx.xml level: cn.jay.repository: trace可以直接在yml中配置日志输出格式
logging: # 控制台打印格式 pattern: console: "%style{%d{yyyy-MM-dd HH:mm:ss}}{red} %style{[%thread]}{blue} %style{%-5level}{cyan}:%highlight{%m%n}" # log文件输出路径 file: path: log这样就不用编写专门的log配置文件
默认文件名 log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration monitorInterval="5"> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置--> <Properties> <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%style{%d{yyyy-MM-dd HH:mm:ss}}{red} %style{[%thread]}{blue} %style{%-5level}{cyan}:%highlight{%m%n}"/> <!-- 定义日志存储的路径 --> <property name="FILE_PATH" value="log"/> <property name="FILE_NAME" value="springboot_04_web_db"/> </Properties> <appenders> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用--> <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false"> <PatternLayout pattern="${LOG_PATTERN}"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.mybatis" level="info" additivity="false"> <AppenderRef ref="Console"/> </logger> <!--监控系统信息--> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <Logger name="org.springframework" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> <root level="info"> <appender-ref ref="Console"/> <appender-ref ref="Filelog"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>
- 配置参数说明
- 机制:如果一条日志信息的级别大于等于配置文件的级别,就记录。
- trace:追踪,就是程序推进一下,可以写个trace输出
- trace:追踪,就是程序推进一下,可以写个trace输出
- debug:调试,一般作为最低级别,trace基本不用。
- info:输出重要的信息,使用较多
- warn:警告,有些信息不是错误信息,但也要给程序员一些提示。
- error:错误信息。用的也很多。
- fatal:致命错误。
- 输出源
- CONSOLE(输出到控制台)
- FILE(输出到文件)
- 格式
- SimpleLayout:以简单的形式显示
- HTMLLayout:以HTML表格显示
- PatternLayout:自定义形式显示
%d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间,输出到毫秒的时间
%-5level : 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
%c : logger的名称(%logger)
%t : 输出当前线程名称
%p : 日志输出格式
%m : 日志内容,即 logger.info("message")
%n : 换行符
%C : Java类名(%F)
%L : 行号
%M : 方法名
%l : 输出语句所在的行数, 包括类名、方法名、文件名、行数
hostName : 本地机器名
hostAddress : 本地ip地址
PatternLayout pattern = ""%style{%d{yyyy-MM-dd HH:mm:ss}}{red} %style{[%thread]}{blue} %style{%-5level}{cyan}:%highlight{%m%n}"使用
获取logger对象
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(Log4j2Controller.class);使用 @Slf4j 注解
// 使用 @Slf4j 注解 , 可以简化 logger 的获取 @Slf4j public class UserController { @Resource private UserMapper userMapper; @GetMapping(value = "/list") public List<User> queryList() { List<User> users = userMapper.queryUserList(); log.info("查询所有用户成功."); return users; } }
4.1 静态资源导入
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
ServletContext servletContext = this.getServletContext();
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (servletContext != null) {
registration.addResourceLocations(new Resource[]{new ServletContextResource(servletContext, "/")});
}
});
}
}总结:
在springboot,我们可以使用以下方式处理静态资源
- webjars localhost:8080/webjars/
- public, static, /**, resources localhost:8080/
优先级:resources > static(默认) > public
4.2 首页如何定制
模板引擎
使用thymeleaf, 只需要导入对应的依赖就行了
<!--Thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>在自己写的 controller 上加注解 @Controller
@RestController注解
相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面// 如果,你想自定义一些定制化功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动装配 // 扩展springmvc配置 @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public ViewResolver myViewResolver() { return new MyViewResolver(); } // 自定义了一个自己的视图解析器 public static class MyViewResolver implements ViewResolver { @Override public View resolveViewName(String s, Locale locale) throws Exception { return null; } } }
4.3 国际化
新建文件夹 i8n (international)
login.properties
login_zh_CN.properties
login.btn=登录 login.password=密码 login.remember=记住我 login.tip=请登录 login.username=用户名login_en_US.properties
login.btn=Sign in login.password=Password login.remember=Remember me login.tip=Please sign in login.username=Username编写自己的本地解析器 MyLocaleResolver
public class MyLocaleResolver implements LocaleResolver { // 解析请求 @Override public Locale resolveLocale(HttpServletRequest httpServletRequest) { // 获取请求中的参数 String language = httpServletRequest.getParameter("l"); Locale locale = Locale.getDefault(); // 如果没有就使用默认的 // 如果请求的链接携带了国际化的参数 if (!StringUtils.isEmpty(language)) { // zh_CN String[] split = language.split("_"); // 国家 地区 locale = new Locale(split[0], split[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } }在 MyMvcConfig 配置增加
// 自定义的国际化组件 @Bean public LocaleResolver localeResolver() { return new MyLocaleResolver(); }修改静态资源
<form class="form-signin" th:action="@{/user/login}"> <img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> <!--如果msg的值为空, 则不显示错误消息--> <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> <label class="sr-only">Username</label> <label> <input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus=""> </label> <label class="sr-only">Password</label> <label> <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required=""> </label> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"> [[#{login.remember}]] </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">[[#{login.btn}]]</button> <p class="mt-5 mb-3 text-muted">© 2020-2021</p> <a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a> </form>
4.4 用户登录
编写 LoginController
package com.pnca.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.thymeleaf.util.StringUtils; import javax.servlet.http.HttpSession; @Controller public class LoginController { @RequestMapping(value = "/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Model model, HttpSession session) { // 具体的业务 if (!StringUtils.isEmpty(username) && "123456".equals(password)) { session.setAttribute("loginUser", username); return "redirect:/main.html"; } else { // 告诉用户, 你登陆失败 model.addAttribute("msg", "用户名或者密码错误!"); return "index"; } } }在 MyMvcConfig 配置增加
@Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); registry.addViewController("/main.html").setViewName("dashboard"); }
4.5 登录拦截
编写 LoginHandlerInterceptor 拦截器
package com.pnca.config; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 登录成功之后, 应该有用户的Session Object loginUser = request.getSession().getAttribute("loginUser"); if (loginUser == null) { request.setAttribute("msg", "没有权限, 请先登录!"); request.getRequestDispatcher("/index.html").forward(request, response); return false; } return true; } }在 MyMvcConfig 配置增加
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**").excludePathPatterns("/index.html", "/", "/user/login", "/asserts/**"); }
注意:看一下静态资源是否正常导出,以及所有的bean是否注入,以防止空指针异常。
5 集成数据库: Druid
注意 thymeleaf 版本可能导致视图无法解析最好直接导入springboot的启动器
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>5.0 Druid简介
Java程序很大一部分要操作数据库,为了提高性能操作数据库的时候,又不得不使用数据库连接池。
Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。
Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。
Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Spring Boot 2.0 以上默认使用 Hikari 数据源,可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源,我们来重点介绍 Spring Boot 如何集成 Druid 数据源,如何实现数据库监控。
Github地址:https://github.com/alibaba/druid/



5.1 配置数据源
添加上 Druid 数据源依赖。
<!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>切换数据源;之前已经说过 Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/springbootweb?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=GMT type: com.alibaba.druid.pool.DruidDataSource数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;

切换成功!既然切换成功,就可以设置数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;可以查看源码
spring: datasource: username: root password: root #?serverTimezone=UTC解决时区的报错 url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #Spring Boot 默认是不注入这些属性值的,需要自己绑定 #druid 数据源专有配置 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入 #如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority #则导入 log4j2 依赖即可,需要加 虚拟机参数 :-Dlog4j.skipJansi=false 彩色才能生效 filters: stat,wall,log4j2 maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;
public class DruidConfig { /* 将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建 绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效 @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中 前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中 */ @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource druidDataSource() { return new DruidDataSource(); } }去测试类中测试一下;看是否成功!
@Test public 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(); }
5.2 配置Druid数据源监控
Druid 数据源具有监控的功能,并提供了一个 web 界面方便用户查看,类似安装 路由器 时,人家也提供了一个默认的 web 页面。
所以第一步需要设置 Druid 的后台管理页面,比如 登录账号、密码 等;配置后台管理;
//配置 Druid 监控管理后台的Servlet; //内置 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"); //后台管理界面的登录密码 //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问 //设置初始化参数 bean.setInitParameters(initParams); return bean; }配置完毕后,我们可以选择访问 :http://localhost:8080/druid/login.html

进入之后

配置 Druid web 监控 filter 过滤器
//配置 Druid 监控 之 web 监控的 filter //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; }平时在工作中,按需求进行配置即可,主要用作监控!
6 整合Mybatis框架
6.1 POM
<dependencies>
<!--
依赖的顺序最后不要变,因为引入log4j2, 需要排除web自带的logging
所以web在前, log4j2在后, 约定大于配置
-->
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--log4j2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.4.5</version>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.3.8.RELEASE</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>6.2 写YML
spring:
banner:
location: banner.txt
thymeleaf:
cache: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=GMT
type: com.alibaba.druid.pool.DruidDataSource
filters: stat,wall,log4j2
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
logging:
# 控制台打印格式
pattern:
console: "%style{%d{yyyy-MM-dd HH:mm:ss}}{red} %style{[%thread]}{blue} %style{%-5level}{cyan}:%highlight{%m%n}"
# log文件输出路径
file:
path: log
mybatis:
type-aliases-package: com.pnca.pojo
mapper-locations: classpath:com/pnca/mapper/*Mapper.xml测试数据能否连接

6.3 实体类
package com.pnca.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author pncalbl
* @date 2021/5/17 19:49
* @e-mail pncalbl@qq.com
* @description
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}6.4 Mapper
// @Mapper, 表示这是一个 mybatis 的 mapper 类
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}<?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.pnca.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select u.id, u.name, u.pwd
from mybatis.user u;
</select>
<select id="queryUserById" resultType="User">
select u.id, u.name, u.pwd
from mybatis.user u
where u.id = #{id}
</select>
<insert id="addUser" parameterType="User">
insert into mybatis.user (id, name, pwd)
values (#{id}, #{name}, #{pwd})
</insert>
<update id="updateUser" parameterType="User">
update mybatis.user u
set u.name = #{name},
u.pwd = #{pwd}
where u.id = #{id}
</update>
<delete id="deleteUser">
delete
from mybatis.user u
where u.id = #{id}
</delete>
</mapper>6.5 Controller
@Controller
@RequestMapping(value = "/user")
@Slf4j
public class UserController {
@Resource
private UserMapper userMapper;
@GetMapping(value = "/list")
public String queryList(Model model) {
List<User> users = userMapper.queryUserList();
log.info("=============> 查询所有用户成功.");
log.info(String.valueOf(users));
model.addAttribute("users", users);
return "user/list";
}
@GetMapping(value = "/query/{id}")
public String queryUserById(@PathVariable("id") Integer id, Model model) {
User user = userMapper.queryUserById(id);
log.info("=============> 查询用户" + id + " 成功.");
model.addAttribute("user", user);
return "redirect:/user/list";
}
@GetMapping(value = "/add")
public String addUser(User user) {
int i = userMapper.addUser(user);
if (i > 0) {
log.info("=============> 增加用户" + user.getName() + "成功.");
} else {
log.info("=============> 增加用户" + user.getName() + "失败.");
}
return "redirect:/user/list";
}
@GetMapping(value = "/update")
public String updateUser(User user) {
int i = userMapper.updateUser(user);
if (i > 0) {
log.info("=============> 更新用户" + user.getName() + " 成功.");
} else {
log.info("=============> 更新用户" + user.getName() + " 失败.");
}
return "redirect:/user/list";
}
@GetMapping(value = "/delete/{id}")
public String deleteUser(@PathVariable("id") Integer id) {
int i = userMapper.deleteUser(id);
if (i > 0) {
log.info("=============> 删除用户" + id + " 成功.");
} else {
log.info("=============> 删除用户" + id + " 失败.");
}
return "redirect:/user/list";
}
}list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
<tr th:each="user:${users}">
<td th:text="${user.getId()}"></td>
<td th:text="${user.getName()}"></td>
<td th:text="${user.getPwd()}"></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>6.6 测试




7 SpringSecurity
7.1 为什么使用安全框架?
在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:
- 一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;
- 另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。
市面上存在比较有名的:Shiro,Spring Security !
这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?
首先我们看下它的官网介绍:Spring Security官网地址
- Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
- Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。
- Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
- Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。
怎么解决之前写权限代码繁琐,冗余的问题,一些主流框架就应运而生而Spring Scecurity就是其中的一种。
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户(Authentication)和用户授权(Authorization)两个部分。
- 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
- 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。
- 在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。
- 在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
7.2 搭建基础环境
POM
<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.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>导入静态资源

Controller
package com.pnca.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; /** * @author pncalbl * @date 2021/5/20 23:46 * @e-mail pncalbl@qq.com * @description **/ @Controller public class RouterController { @RequestMapping(value = {"/", "/index"}) public String index() { return "index"; } @RequestMapping(value = "/toLogin") public String toLogin() { return "views/login"; } @RequestMapping(value = "/level1/{id}") public String toLogin1(@PathVariable("id") int id) { return "views/level1/" + id; } @RequestMapping(value = "/level2/{id}") public String toLogin2(@PathVariable("id") int id) { return "views/level2/" + id; } @RequestMapping(value = "/level3/{id}") public String toLogin3(@PathVariable("id") int id) { return "views/level3/" + id; } }启动测试
用户名: user
密码: 控制台打印



7.3用户认证和授权
认证: Authenticate
授权: Authorization
WebSecurityConfigAdapter: 自定义Security策略
AuthenticationManagerBuilder: 自定义认证策略
@EnableWebSecurity: 开启WebSecurity模式
自定义Security策略
模板
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); } }完整
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // 授权 @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(); } // 认证 springboot 2.1.x 可以直接使用 // 过高版本, 密码需要编码: PasswordEncoder // 在 Spring Security 5.0+ 新增了很多的加密方法 MD5 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 这些数据正常应该从数据中读取 auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("pnca").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"); } }

自定义认证策略
7.4 注销及权限控制
开启自动配置的注销的功能
//定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //.... //开启自动配置的注销的功能 // /logout 注销请求 http.logout(); }我们在前端,增加一个注销的按钮,index.html 导航栏中
<a class="item" th:href="@{/logout}"> <i class="address card icon"></i> 注销 </a>我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!
但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?
// .logoutSuccessUrl("/"); 注销成功来到首页 http.logout().logoutSuccessUrl("/");测试,注销完毕后,发现跳转到首页OK
我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?
我们需要结合thymeleaf中的一些功能
sec:authorize=”isAuthenticated()”:是否认证登录!来显示不同的页面
Maven依赖:
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.2.RELEASE</version> </dependency>
修改我们的 前端页面
导入命名空间
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"修改导航栏,增加认证判断
<!--登录注销--> <div class="right menu"> <!--如果未登录--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/login}"> <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="address card icon"></i> 注销 </a> </div> </div>
重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;
如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加
http.csrf().disable();http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求 http.logout().logoutSuccessUrl("/");我们继续将下面的角色功能块认证完成!
<!-- sec:authorize="hasRole('vip1')" --> <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>测试一下!
权限控制和注销搞定!
7.5 记住我
现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单
开启记住我功能
//定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //。。。。。。。。。。。 //记住我 http.rememberMe(); }我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!
思考:如何实现的呢?其实非常简单
我们可以查看浏览器的cookie

我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!
7.6 首页定制
现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?
在刚才的登录页配置后面指定 loginpage
http.formLogin().loginPage("/toLogin");然后前端也需要指向我们自己定义的 login请求
<a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a>我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:
在 loginPage()源码中的注释上有写明:

<form th:action="@{/login}" method="post"> <div class="field"> <label>Username</label> <div class="ui left icon input"> <input type="text" placeholder="Username" name="username"> <i class="user icon"></i> </div> </div> <div class="field"> <label>Password</label> <div class="ui left icon input"> <input type="password" name="password"> <i class="lock icon"></i> </div> </div> <input type="submit" class="ui blue submit button"/> </form>这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!
http.formLogin() .usernameParameter("username") .passwordParameter("password") .loginPage("/toLogin") .loginProcessingUrl("/login"); // 登陆表单提交请求在登录页增加记住我的多选框
<input type="checkbox" name="remember"> 记住我后端验证处理!
//定制记住我的参数! http.rememberMe().rememberMeParameter("remember");
8 Shiro
8.1 什么是 Shiro?
● Apache Shiro 是一个 Java 的安全(权限)框架。
● Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
● Shiro 可以完成,认证,授权,加密,会话管理,Web集成,缓存等。
● 下载地址: http://shiro.apache.org/
8.1.1 有哪些功能

● Authentication: 身份认证、登录,验证用户是不是拥有相应的身份;
● Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限!
● Session Manager: 会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;
● Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;
● Web Support : Web支持,可以非常容易的集成到Web环境;
● Caching : 缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率。
● Concurrency: Shiro支持多线程应用的并发验证,即:如在一个线程中开启另一个线程,能把权限自动的传播过去。
● Testing: 提供测试支持;
● RunAs: 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
● Remember Me: 记住我,这个是非常常见的功能,即一次登录后, 下次再来的话不用登录了。
8.1.2 Shiro架构(外部)

● subject: 应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,Subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager;Subject其实是一个门面,SecurityManageer 才是实际的执行者。
● SecurityManager: 安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色。
● Realm: Shiro从Realm获取安全数据 (如用户,角色,权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看DataSource。
8.1.3 Shiro架构(内部)

● Subject: 任何可以与应用交互的用户;
● Security Manager: 相当于SpringMVC中的DispatcherSerlet;是Shiro的心脏, 所有具体的交互都通过Security Manager进行控制,它管理者所有的Subject, 且负责进行认证,授权,会话,及缓存的管理。
● Authenticator: 负责Subject认证, 是-一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
● Authorizer: 授权器,即访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的那些功能;
● Realm: 可以有一个或者多个的realm,可以认为是安全实体数据源,即用于获取安全实体的,可以用JDBC实现,也可以是内存实现等等,由用户提供;所以一般在应用中都需要实现自己的realm。
● SessionManager: 管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在普通的JavaSE环境中。
● CacheManager: 缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能。
● Cryptography: 密码模块,Shiro 提高了一些常见的加密组件用于密码加密, 解密等。
8.2 Shiro快速开始
创建一个普通maven项目springboot-06-shiro,然后删除src目录,这样的话就可以在这个项目里新建很多model.
在springboot-06-shiro里新建model shiro-01-helloshiro
父依赖pom.xml
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<!-- configure logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>runtime</scope>
</dependency>
</dependencies>log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5Quickstart.java
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;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
defaultSecurityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 获得当前用户对象 Subject
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户拿到session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// 判断当前用户是否被认证
if (!currentUser.isAuthenticated()) {
//Token:令牌
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token); //执行登录操作
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//粗粒度
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'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//注销
currentUser.logout();
//结束
System.exit(0);
}
}执行

8.3 springboot整合shiro
8.3.1 项目结构图

8.3.2 数据库

8.3.3 程序代码
新建一个springboot项目,勾选web,thymeleaf模块
pom.xml
<dependencies> <!--shiro-thymeleaf--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.7.0</version> </dependency> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!--log4j2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> <version>2.4.5</version> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.18</version> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>2.3.8.RELEASE</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <!--Thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> </dependencies>application.yml
spring: banner: location: banner.txt thymeleaf: cache: false # 配置国际化 messages: basename: i18n.login datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/springbootweb?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=GMT type: com.alibaba.druid.pool.DruidDataSource filters: stat,wall,log4j2 maxPoolPreparedStatementPerConnectionSize: 20 useGlobalDataSourceStat: true connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 logging: # 控制台打印格式 pattern: console: "%style{%d{yyyy-MM-dd HH:mm:ss}}{red} %style{[%thread]}{blue} %style{%-5level}{cyan}:%highlight{%m%n}" mybatis: type-aliases-package: com.pnca.pojo mapper-locations: classpath:mapper/*.xmlUserRealm.java
package com.pnca.config; import com.pnca.pojo.User; import com.pnca.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; public class UserRealm extends AuthorizingRealm { @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了授权"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo (); //拿到当前登录的对象 Subject subject = SecurityUtils.getSubject(); //拿到user对象 User currentUser = (User) subject.getPrincipal(); //添加权限(数据库中拿的) info.addStringPermission(currentUser.getPerms()); return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("执行了认证"); UsernamePasswordToken userToken=(UsernamePasswordToken)token; // //用户名、密码 模拟从数据库中获取 // String name = "root"; // String password = "1111"; // // //用户名认证 // if (!userToken.getUsername().equals(name)){ // return null;//抛出异常 UnknownAccountException // } // // //密码认证,shiro做 // return new SimpleAuthenticationInfo("",password,""); //连接真实数据库 User user = userService.queryUserByName(userToken.getUsername()); if (user==null){//没有这个人 return null;//抛出异常 UnknownAccountException } Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); session.setAttribute("loginUser",user); return new SimpleAuthenticationInfo(user,user.getPwd(),""); } }ShiroConfig.java
package com.pnca.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig{ //ShiroFilterFactoryBean 第三步 @Bean public ShiroFilterFactoryBean getShiroFilterBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //设置安全管理器 bean.setSecurityManager(defaultWebSecurityManager); Map<String, String> filterMap =new LinkedHashMap<>(); // 添加Shiro内置过滤器 /** * Shiro内置过滤器,可以实现权限相关的拦截器 * 常用的过滤器: * anon:无需认证(登陆)可以访问 * authc:必须认证才可以访问 * user:如果使用rememberMe的功能,可以直接访问 * perms:该资源必须得到资源权限才可以访问 * role:该资源必须得到角色权限才可以访问 */ //拦截,必须有什么权限才能访问 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; } //DefaultWebSecurityManager 第二步 @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") Realm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联userRealm securityManager.setRealm(realm); return securityManager; } //realm 第一步 @Bean public Realm userRealm(){ return new UserRealm(); } //整合ShiroDialect:用来整合shiro thymeleaf @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }MyController.java
package com.pnca.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 MyController { @RequestMapping({"/","/index"}) public String toIndex(Model model){ model.addAttribute("msg","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("/noauth") @ResponseBody public String unauthorized(){ return "未授权无法访问此页面"; } @RequestMapping("/login") public String login(String username,String password,Model model) { //获取当前用户 Subject subject = SecurityUtils.getSubject(); //封装用户的登录数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token);//执行登录的方法,如果没有异常就说明ok了 return "index";//登录成功跳到首页 } catch (UnknownAccountException e) { //用户名不存在 model.addAttribute("msg", "用户名不存在!"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg", "密码错误!"); return "login"; } } @RequestMapping("/logout") public String logout(){ //获取当前用户 Subject subject = SecurityUtils.getSubject(); subject.logout(); // session 会销毁,在SessionListener监听session销毁,清理权限缓存 System.out.println("执行了退出"); return "redirect:index"; } }User.java
package com.pnca.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; private String perms; }UserMapper.java
package com.pnca.mapper; import com.kuang.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @Repository @Mapper public interface UserMapper { public User queryUserByName(String name); }UserServiceImpl.java
package com.pnca.service; import com.kuang.mapper.UserMapper; import com.kuang.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService{ @Autowired UserMapper userMapper; @Override public User queryUserByName(String name) { return userMapper.queryUserByName(name); } }index.html
<!DOCTYPE html> <html lang="en" xmlns:th="https://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <p th:text="${msg}"></p> <hr> <div th:if="${session.loginUser==null}"> <a th:href="@{/toLogin}">登录</a> </div> <div th:if="${session.loginUser!=null}"> <a th:href="@{/logout}">注销</a> </div> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add</a> </div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a> </div> </body> </html>login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml?"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录</h1> <hr> <p th:text="${msg}" style="color:red;"></p> <form th:action="@{/login}"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"> <br> <input type="submit" name="提交"> </form> </body> </html>add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加</h1> </body> </html>update.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>修改</h1> </body> </html>
8.4 总结
8.4.1 Shiro 三大要素
subject->ShiroFilterFactoryBeansecurityManager->DefaultWebSecurityManagerrealm实际操作中对象创建的顺序 :
realm -> securityManager -> subject
8.4.2 流程梳理
用户进入首页点击跳转,Shiro内置过滤器进行拦截,看过滤器的设置,未认证跳转到登录页面,未授权跳转到未授权界面;
认证
用户进入登录页面,输入用户名密码准备进行认证,点击登录按钮后,会请求/login,
首先调用Subject.login(token) 进行登录,其会自动委托给SecurityManager,
SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
Authenticator 才是真正的身份验证者,Authenticator 会把相应的token 传入Realm,从Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了,如果有就返回AuthenticationInfo验证信息,此信息中包含了身份(pricipals)及凭证,也就是账号密码。

授权
- 对subject进行授权,调用方法isPermitted(“permission串”),递交给SecurityManager
- SecurityManager将权限检测操作委托给Authorizer授权管理器对象
- Authorizer执行Realm(自定义的Realm)从数据库查询权限数据并封装
- Authorizer对用户授权信息进行判定(判断用户访问资源时需要什么权限,假如用户所具有的权限包含这个资源访问时所需要的权限,那么用户就可以访问这个资源了)。
9 swagger
9.1 概念
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。
9.2 特点
- 号称世界上最流行的API框架
- Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
- 直接运行,在线测试API
- 支持多种语言 (如:Java,PHP等)
- 官网:https://swagger.io/
9.3 springboot 集成 swagger
- 1 引入依赖
以maven为例:
<dependencies>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>- 2 自定义配置信息
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
}- 3 测试
http://localhost:8080/swagger-ui.html

1 配置swagger
Swagger实例Bean是
Docket,所以通过配置Docket实例来配置Swaggger。@Bean //配置docket以配置Swagger具体参数 public Docket docket() { return new Docket(DocumentationType.SWAGGER_2); }可以通过
apiInfo()属性配置文档信息//配置文档信息 private ApiInfo apiInfo() { Contact contact = new Contact("联系人名字", "http://xxx.xxx.com/联系人访问链接", "联系人邮箱"); return new ApiInfo( "Swagger学习", // 标题 "学习演示如何配置Swagger", // 描述 "v1.0", // 版本 "http://terms.service.url/组织链接", // 组织链接 contact, // 联系人信息 "Apach 2.0 许可", // 许可 "许可链接", // 许可连接 new ArrayList<>()// 扩展 ); }Docket 实例关联上 apiInfo()
@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()); }重启项目,访问测试
http://localhost:8080/swagger-ui.html看下效果
2 配置扫描接口
构建Docket时通过
select()方法配置怎么扫描接口。@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.controller")) .build(); }重启项目测试,由于我们配置根据包的路径扫描接口,所以我们只能看到一个类

除了通过包路径配置扫描接口外,还可以通过配置其他方式扫描接口,这里注释一下所有的配置方式:
//RequestHandlerSelectors 配置要扫描接口的方式 //basePackage 指定要扫描的包 //any() 扫描全部 //none() 不扫描 //withMethodAnnotation 扫描方法上的注解,参数是注解的反射对象(GetMapping.class) //withClassAnnotation 扫描类上的注解(RestController.class)除此之外,我们还可以配置接口扫描过滤:
@Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.pnca.controller")) .paths(PathSelectors.ant("/pnca/**")) //请求中有pnca的 .build(); }

3 配置Swagger开关
通过
enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了@Bean public Docket docket(Environment environment){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(false) .select() .apis(RequestHandlerSelectors.basePackage("com.pnca.controller")) .paths(PathSelectors.ant("/hello")) .build(); }如何动态配置当项目处于test、dev环境时显示swagger,处于prod时不显示?
@Bean public Docket docket(Environment environment) { // 设置要显示swagger的环境 Profiles of = Profiles.of("dev", "test"); // 判断当前是否处于该环境 // 通过 enable() 接收此参数判断是否要显示 boolean flag = environment.acceptsProfiles(of); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(flag) //配置是否启用Swagger,如果是false,在浏览器将无法访问 .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.kuang.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/kuang开头的接口 .paths(PathSelectors.ant("/kuang/**")) .build(); }springboot切换使用环境,如果是
Profiles.of("dev", "test")这里有的环境,则正常显示,如果是这里没有的则无法使用。
4 配置API分组
如果没有配置分组,默认是default。通过
groupName()方法即可配置分组
@Bean public Docket docket(Environment environment) { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) .groupName("hello") // 配置分组 // 省略配置.... }如何
配置多个分组?配置多个分组只需要配置多个docket即可@Bean public Docket docket(Environment environment){ Profiles of = Profiles.of("dev", "test"); boolean flag = environment.acceptsProfiles(of); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("狂神") .enable(f) .select() .apis(RequestHandlerSelectors.basePackage("com.kuang.controller")) .paths(PathSelectors.ant("/hello")) .build(); } @Bean public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group1"); } @Bean public Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2).groupName("group2"); }
5 配置注释信息
实体类上注解
@ApiModel("用户实体类") public class User { @ApiModelProperty("用户名") public String name; @ApiModelProperty("密码") public String password; }方法返回值中有存在实体类,就会被扫描
@GetMapping("/user") public User hello2(){ return new User(); }方法上注解
@ApiOperation("测试方法") @GetMapping("/test") public String hello3(@ApiParam("用户名") String name){ return name; }常用注解
- @Api(tags = “xxx模块说明”):作用在模块类上
- @ApiOperation(“xxx接口说明”):作用在接口方法上
- @ApiModel(“xxxPOJO说明”):作用在模型类上:如VO、BO
- @ApiModelProperty(value = “xxx属性说明”,hidden = true):作用在方法和属性上,hidden设置为true可以隐藏该属性
- @ApiParam(“xxx参数说明”):作用在参数、方法和字段上,类似@ApiModelProperty
相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。
6 其他皮肤
默认
访问http://localhost:8080/swagger-ui.html<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
bootstrap-ui 访问 http://localhost:8080/doc.html
<!-- 引入swagger-bootstrap-ui包 /doc.html--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.1</version> </dependency>
Layui-ui 访问 http://localhost:8080/docs.html
<!-- 引入swagger-ui-layer包 /docs.html--> <dependency> <groupId>com.github.caspar-chen</groupId> <artifactId>swagger-ui-layer</artifactId> <version>1.1.3</version> </dependency>
mg-ui 访问 http://localhost:8080/document.html
<!-- 引入swagger-ui-layer包 /document.html--> <dependency> <groupId>com.zyplayer</groupId> <artifactId>swagger-mg-ui</artifactId> <version>1.0.6</version> </dependency>
10 任务
10.1 异步任务
创建一个AsuncService类
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;
@Service public class AsyncService { public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中......"); } }
创建一个AsyncController类
@RestController public class AsyncController { @Autowired AsyncService asyncService; @GetMapping("/hello") public String hello(){ asyncService.hello(); return "sucess"; } }测试,发现界面没有立即返回结果,而是等后台执行完才返回。
给hello方法添加@Async注解;
@Service public class AsyncService { @Async //告诉spring这是一个异步方法 public void hello(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("业务进行中......"); } }SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;
@EnableAsync //开启异步注解功能 @SpringBootApplication public class Springboot08TaskApplication { public static void main(String[] args) { SpringApplication.run(Springboot08TaskApplication.class, args); } }重启测试,网页瞬间响应,后台代码继续执行!
10.2 定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
- TaskExecutor接口
- TaskScheduler接口
两个注解:
- @EnableScheduling
- @Scheduled
cron表达式:


创建一个ScheduledService类
@Service public class ScheduledService { //cron表达式 @Scheduled(cron = "0 9 20 * * 0-7") //每天20:09执行 public void hello(){ System.out.println("hello Scheduled...."); } }需要在主程序上增加@EnableScheduling 开启定时任务功能
@EnableScheduling //开启基于注解的定时任务 @SpringBootApplication public class Springboot08TaskApplication { public static void main(String[] args) { SpringApplication.run(Springboot08TaskApplication.class, args); } }测试
了解下cron表达式
常用的表达式
(1)0/2 * * * * ? 表示每2秒 执行任务 (1)0 0/2 * * * ? 表示每2分钟 执行任务 (1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务 (2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 (3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作 (4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 (5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 (6)0 0 12 ? * WED 表示每个星期三中午12点 (7)0 0 12 * * ? 每天中午12点触发 (8)0 15 10 ? * * 每天上午10:15触发 (9)0 15 10 * * ? 每天上午10:15触发 (10)0 15 10 * * ? 每天上午10:15触发 (11)0 15 10 * * ? 2005 2005年的每天上午10:15触发 (12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发 (13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发 (14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 (15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发 (16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发 (17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发 (18)0 15 10 15 * ? 每月15日上午10:15触发 (19)0 15 10 L * ? 每月最后一日的上午10:15触发 (20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发 (21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发 (22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
10.3 邮件发送
邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持
- 邮件发送需要引入spring-boot-start-mail
- SpringBoot 自动配置MailSenderAutoConfiguration
- 定义MailProperties内容,配置在application.yml中
- 自动装配JavaMailSender
- 测试邮件发送
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>配置文件
查看一下MailProperties
@ConfigurationProperties( prefix = "spring.mail" ) public class MailProperties { private static final Charset DEFAULT_CHARSET; private String host; private Integer port; private String username; private String password; private String protocol = "smtp"; private Charset defaultEncoding; private Map<String, String> properties; private String jndiName; }spring.mail.username=24736743@qq.com spring.mail.password=你的qq授权码 spring.mail.host=smtp.qq.com # qq需要配置ssl spring.mail.properties.mail.smtp.ssl.enable=true获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务

Spring单元测试
@SpringBootTest class Springboot08TaskApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() { //方式一:简单的邮件 SimpleMailMessage message = new SimpleMailMessage(); message.setSubject("通知-明天放假"); message.setText("今晚7:30开会"); message.setTo("24736743@qq.com"); message.setFrom("24736743@qq.com"); mailSender.send(message); } @Test void contextLoads2() throws MessagingException { //方式二:复杂的邮件 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("通知-明天上课"); helper.setText("<b style='color:red'>今天 7:30来开会</b>",true); helper.addAttachment("1.jpg",new File("C:\\Users\\Lenovo\\Desktop\\1.jpg")); //helper.addAttachment("2.jpg",new File("")); helper.setTo("24736743@qq.com"); helper.setFrom("24736743@qq.com"); mailSender.send(mimeMessage); } }查看邮箱,邮件接收成功!
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!