feat: 更新电子书模板
This commit is contained in:
parent
55801ad382
commit
f0398dd9f4
|
@ -19,7 +19,7 @@ insert_final_newline = true
|
|||
[*.{bat, cmd}]
|
||||
end_of_line = crlf
|
||||
|
||||
[*.{java, gradle, groovy, kt, sh}]
|
||||
[*.{java, gradle, groovy, kt, sh, xml}]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
name: CI
|
||||
|
||||
# 在master分支发生push事件时触发。
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env: # 设置环境变量
|
||||
TZ: Asia/Shanghai # 时区(设置时区可使页面中的`最近更新时间`使用时区时间)
|
||||
|
||||
jobs:
|
||||
build: # 自定义名称
|
||||
runs-on: ubuntu-latest # 运行在虚拟机环境ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
# 使用的动作。格式:userName/repoName。作用:检出仓库,获取源码。 官方actions库:https://github.com/actions
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
# 指定 nodejs 版本
|
||||
- name: Use Nodejs ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
# 部署
|
||||
- name: Deploy
|
||||
env: # 设置环境变量
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
|
||||
run: npm install && npm run deploy
|
|
@ -37,10 +37,12 @@ package-lock.json
|
|||
node_modules
|
||||
|
||||
# temp folders
|
||||
.temp
|
||||
build
|
||||
dist
|
||||
_book
|
||||
_jsdoc
|
||||
.temp
|
||||
.deploy*/
|
||||
|
||||
# temp files
|
||||
*.log
|
||||
|
@ -48,6 +50,10 @@ npm-debug.log*
|
|||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
bundle*.js
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
db.json
|
||||
package-lock.json
|
||||
book.pdf
|
||||
|
||||
|
||||
|
|
24
.travis.yml
24
.travis.yml
|
@ -1,24 +0,0 @@
|
|||
# 持续集成 CI
|
||||
# @see https://docs.travis-ci.com/user/tutorial/
|
||||
|
||||
language: node_js
|
||||
|
||||
sudo: required
|
||||
|
||||
node_js: '16.13.0'
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- export TZ=Asia/Shanghai
|
||||
|
||||
script: bash ./scripts/deploy.sh
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- forbreak@163.com
|
||||
on_success: change
|
||||
on_failure: always
|
69
README.md
69
README.md
|
@ -24,43 +24,50 @@
|
|||
|
||||
### 综合
|
||||
|
||||
- [Spring 概述](docs/summary/Spring概述.md)
|
||||
- [Spring 常见面试题](docs/summary/Spring常见面试题.md)
|
||||
- [Spring 概述](docs/01.Java/13.框架/01.Spring/00.Spring综合/01.Spring概述.md)
|
||||
- [SpringBoot 知识图谱](docs/01.Java/13.框架/01.Spring/00.Spring综合/21.SpringBoot知识图谱.md)
|
||||
- [SpringBoot 基本原理](docs/01.Java/13.框架/01.Spring/00.Spring综合/22.SpringBoot基本原理.md)
|
||||
- [Spring 常见面试题](docs/01.Java/13.框架/01.Spring/00.Spring综合/99.Spring常见面试题.md)
|
||||
|
||||
### 核心
|
||||
|
||||
> [核心](docs/core/README.md) 章节主要针对:Spring 框架的核心技术。如;IOC 依赖注入、AOP、数据绑定等。
|
||||
|
||||
- [Spring 依赖注入(IoC)](docs/core/Spring依赖注入.md)
|
||||
- [Spring 生命周期](docs/core/Spring生命周期.md)
|
||||
- [Spring AOP](docs/core/spring-aop.md)
|
||||
- [Spring 资源管理](docs/core/Spring资源管理.md)
|
||||
- [Spring 依赖注入(IoC)](docs/01.Java/13.框架/01.Spring/01.Spring核心/01.Spring依赖注入.md)
|
||||
- [Spring Bean 生命周期](docs/01.Java/13.框架/01.Spring/01.Spring核心/02.Spring生命周期.md)
|
||||
- [Spring AOP](docs/01.Java/13.框架/01.Spring/01.Spring核心/03.SpringAop.md)
|
||||
- [Spring 资源管理](docs/01.Java/13.框架/01.Spring/01.Spring核心/04.Spring资源管理.md)
|
||||
- [SpringBoot 教程之快速入门](docs/01.Java/13.框架/01.Spring/01.Spring核心/21.SpringBoot之快速入门.md)
|
||||
- [SpringBoot 之属性加载](docs/01.Java/13.框架/01.Spring/01.Spring核心/22.SpringBoot之属性加载.md)
|
||||
- [SpringBoot 之 Profile](docs/01.Java/13.框架/01.Spring/01.Spring核心/23.SpringBoot之Profile.md)
|
||||
|
||||
### 数据
|
||||
|
||||
> [数据](docs/data/README.md) 章节主要针对:Spring 在数据库领域的应用。如:JDBC、ORM、事务等。
|
||||
|
||||
- [Spring 的数据访问策略](docs/data/Spring数据访问策略.md)
|
||||
- [Spring 中使用 JDBC 访问数据](docs/data/Spring中使用JDBC访问数据.md)
|
||||
- [Spring 事务管理](docs/data/Spring事务管理.md)
|
||||
- [SpringBoot 之 JDBC](docs/01.Java/13.框架/01.Spring/02.Spring数据/21.SpringBoot之JDBC.md)
|
||||
- [SpringBoot 之 Mybatis](docs/01.Java/13.框架/01.Spring/02.Spring数据/22.SpringBoot之Mybatis.md)
|
||||
- [SpringBoot 之 MongoDB](docs/01.Java/13.框架/01.Spring/02.Spring数据/23.SpringBoot之MongoDB.md)
|
||||
- [SpringBoot 之 Elasticsearch](docs/01.Java/13.框架/01.Spring/02.Spring数据/24.SpringBoot之Elasticsearch.md)
|
||||
|
||||
### Web
|
||||
|
||||
> [Web](docs/web/README.md) 章节主要针对:Spring 在 web 领域的应用。如:Spring MVC、WebSocket 等。
|
||||
- [Spring WebMvc](docs/01.Java/13.框架/01.Spring/03.SpringWeb/01.SpringWebMvc.md)
|
||||
- [SpringBoot 之应用 EasyUI](docs/01.Java/13.框架/01.Spring/03.SpringWeb/21.SpringBoot之应用EasyUI.md)
|
||||
|
||||
- [Spring MVC](docs/web/spring-mvc.md)
|
||||
### IO
|
||||
|
||||
- [SpringBoot 之异步请求](docs/01.Java/13.框架/01.Spring/04.SpringIO/01.SpringBoot之异步请求.md)
|
||||
- [SpringBoot 之 Json](docs/01.Java/13.框架/01.Spring/04.SpringIO/02.SpringBoot之Json.md)
|
||||
- [SpringBoot 之邮件](docs/01.Java/13.框架/01.Spring/04.SpringIO/03.SpringBoot之邮件.md)
|
||||
|
||||
### 集成
|
||||
|
||||
> [集成](docs/integration/README.md) 章节主要针对:Spring 与第三方框架、库集成。如:Cache、Scheduling、JMS、JMX 等。
|
||||
|
||||
- [Spring 集成 Dubbo](docs/integration/Spring集成Dubbo.md)
|
||||
- [Spring 集成缓存中间件](docs/integration/Spring集成缓存中间件.md)
|
||||
- [Spring 集成定时任务中间件](docs/integration/Spring集成定时任务中间件.md)
|
||||
- [Spring 集成缓存中间件](docs/01.Java/13.框架/01.Spring/05.Spring集成/01.Spring集成缓存.md)
|
||||
- [Spring 集成定时任务中间件](docs/01.Java/13.框架/01.Spring/05.Spring集成/02.Spring集成调度器.md)
|
||||
- [Spring 集成 Dubbo](docs/01.Java/13.框架/01.Spring/05.Spring集成/03.Spring集成Dubbo.md)
|
||||
|
||||
### 其他
|
||||
|
||||
- [Spring4 升级踩雷指南](docs/others/spring4-upgrade.md)
|
||||
- [Spring4 升级](docs/01.Java/13.框架/01.Spring/99.Spring其他/01.Spring4升级.md)
|
||||
- [SpringBoot 之 banner](docs/01.Java/13.框架/01.Spring/99.Spring其他/21.SpringBoot之banner.md)
|
||||
- [SpringBoot 之 Actuator](docs/01.Java/13.框架/01.Spring/99.Spring其他/22.SpringBoot之Actuator.md)
|
||||
|
||||
## 💻 示例
|
||||
|
||||
|
@ -105,7 +112,7 @@ $ mvn jetty:run -Dmaven.test.skip=true
|
|||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《 Spring 实战(第 5 版)》](https://book.douban.com/subject/34949443/)
|
||||
- **教程**
|
||||
|
@ -115,20 +122,4 @@ $ mvn jetty:run -Dmaven.test.skip=true
|
|||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [SPRING-TUTORIAL 首页](https://github.com/dunwu/spring-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
|
||||
> 你可能会感兴趣:
|
||||
|
||||
- [Java 教程](https://github.com/dunwu/java-tutorial) 📚
|
||||
- [JavaCore 教程](https://dunwu.github.io/javacore/) 📚
|
||||
- [JavaTech 教程](https://dunwu.github.io/javatech/) 📚
|
||||
- [Spring 教程](https://dunwu.github.io/spring-tutorial/) 📚
|
||||
- [Spring Boot 教程](https://dunwu.github.io/spring-boot-tutorial/) 📚
|
||||
- [数据库教程](https://dunwu.github.io/db-tutorial/) 📚
|
||||
- [数据结构和算法教程](https://dunwu.github.io/algorithm-tutorial/) 📚
|
||||
- [Linux 教程](https://dunwu.github.io/linux-tutorial/) 📚
|
||||
- [Nginx 教程](https://github.com/dunwu/nginx-tutorial/) 📚
|
||||
|
||||
## License
|
||||
|
||||
本博客所有文章除特别声明外,均采用 [![License: CC BY-NC-SA 4.0](https://licensebuttons.net/l/by-nc-sa/4.0/80x15.png)](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可协议。
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"default": true,
|
||||
"MD002": false,
|
||||
"MD004": { "style": "dash" },
|
||||
"ul-indent": { "indent": 2 },
|
||||
"MD013": { "line_length": 600 },
|
||||
"MD024": false,
|
||||
"MD025": false,
|
||||
"MD026": false,
|
||||
"MD029": { "style": "ordered" },
|
||||
"MD033": false,
|
||||
"MD034": false,
|
||||
"MD036": false,
|
||||
"fenced-code-language": false,
|
||||
"no-hard-tabs": false,
|
||||
"whitespace": false,
|
||||
"emphasis-style": { "style": "consistent" }
|
||||
}
|
|
@ -1,150 +1,217 @@
|
|||
/**
|
||||
* @see https://vuepress.vuejs.org/zh/
|
||||
*/
|
||||
const htmlModules = require('./config/htmlModules.js')
|
||||
|
||||
module.exports = {
|
||||
port: '4000',
|
||||
dest: 'dist',
|
||||
base: '/spring-tutorial/',
|
||||
title: 'SPRING-TUTORIAL',
|
||||
description: 'Spring 教程',
|
||||
head: [['link', { rel: 'icon', href: `/favicon.ico` }]],
|
||||
dest: 'docs/.temp',
|
||||
base: '/spring-tutorial/', // 默认'/'。如果你想将你的网站部署到如 https://foo.github.io/bar/,那么 base 应该被设置成 "/bar/",(否则页面将失去样式等文件)
|
||||
title: 'SPRING TUTORIAL',
|
||||
description: '💧 spring-tutorial 是一个 Spring 教程。',
|
||||
theme: 'vdoing', // 使用依赖包主题
|
||||
// theme: require.resolve('../../vdoing'), // 使用本地主题
|
||||
head: [
|
||||
// 注入到页面<head> 中的标签,格式[tagName, { attrName: attrValue }, innerHTML?]
|
||||
['link', { rel: 'icon', href: '/img/favicon.ico' }], //favicons,资源放在public文件夹
|
||||
['meta', { name: 'keywords', content: 'vuepress,theme,blog,vdoing' }],
|
||||
['meta', { name: 'theme-color', content: '#11a8cd' }] // 移动浏览器主题颜色
|
||||
],
|
||||
markdown: {
|
||||
// lineNumbers: true,
|
||||
extractHeaders: ['h2', 'h3', 'h4', 'h5', 'h6'], // 提取标题到侧边栏的级别,默认['h2', 'h3']
|
||||
externalLinks: {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
},
|
||||
rel: 'noopener noreferrer'
|
||||
}
|
||||
},
|
||||
// 主题配置
|
||||
themeConfig: {
|
||||
logo: 'https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo-200.png',
|
||||
repo: 'dunwu/spring-tutorial',
|
||||
repoLabel: 'Github',
|
||||
docsDir: 'docs',
|
||||
docsBranch: 'master',
|
||||
editLinks: true,
|
||||
smoothScroll: true,
|
||||
locales: {
|
||||
'/': {
|
||||
label: '简体中文',
|
||||
selectText: 'Languages',
|
||||
editLinkText: '帮助我们改善此页面!',
|
||||
lastUpdated: '上次更新',
|
||||
nav: [
|
||||
{
|
||||
text: '综合',
|
||||
link: '/summary/',
|
||||
},
|
||||
{
|
||||
text: '核心',
|
||||
link: '/core/',
|
||||
},
|
||||
{
|
||||
text: '数据',
|
||||
link: '/data/',
|
||||
},
|
||||
{
|
||||
text: '集成',
|
||||
link: '/integration/',
|
||||
},
|
||||
{
|
||||
text: 'Web',
|
||||
link: '/web/',
|
||||
},
|
||||
{
|
||||
text: '其他',
|
||||
link: '/others/',
|
||||
},
|
||||
{
|
||||
text: '✨ Java系列',
|
||||
ariaLabel: 'Java',
|
||||
items: [
|
||||
{
|
||||
text: 'Java 教程 📚',
|
||||
link: 'https://dunwu.github.io/spring-tutorial/',
|
||||
target: '_blank',
|
||||
rel: '',
|
||||
},
|
||||
{
|
||||
text: 'JavaCore 教程 📚',
|
||||
link: 'https://dunwu.github.io/javacore/',
|
||||
target: '_blank',
|
||||
rel: '',
|
||||
},
|
||||
{
|
||||
text: 'JavaTech 教程 📚',
|
||||
link: 'https://dunwu.github.io/javatech/',
|
||||
target: '_blank',
|
||||
rel: '',
|
||||
},
|
||||
{
|
||||
text: 'Spring 教程 📚',
|
||||
link: 'https://dunwu.github.io/spring-tutorial/',
|
||||
target: '_blank',
|
||||
rel: '',
|
||||
},
|
||||
{
|
||||
text: 'Spring Boot 教程 📚',
|
||||
link: 'https://dunwu.github.io/spring-boot-tutorial/',
|
||||
target: '_blank',
|
||||
rel: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '🎯 博客',
|
||||
link: 'https://github.com/dunwu/blog',
|
||||
target: '_blank',
|
||||
rel: '',
|
||||
},
|
||||
],
|
||||
sidebar: 'auto',
|
||||
sidebarDepth: 2,
|
||||
nav: [
|
||||
{
|
||||
text: 'Spring综合',
|
||||
link: '/01.Java/13.框架/01.Spring/00.Spring综合/'
|
||||
},
|
||||
{
|
||||
text: 'Spring核心',
|
||||
link: '/01.Java/13.框架/01.Spring/01.Spring核心/'
|
||||
},
|
||||
{
|
||||
text: 'Spring数据',
|
||||
link: '/01.Java/13.框架/01.Spring/02.Spring数据/'
|
||||
},
|
||||
{
|
||||
text: 'SpringIO',
|
||||
link: '/01.Java/13.框架/01.Spring/04.SpringIO/'
|
||||
},
|
||||
{
|
||||
text: 'Spring集成',
|
||||
link: '/01.Java/13.框架/01.Spring/05.Spring集成/'
|
||||
},
|
||||
{
|
||||
text: 'Spring其他',
|
||||
link: '/01.Java/13.框架/01.Spring/99.Spring其他/'
|
||||
}
|
||||
],
|
||||
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
|
||||
logo: 'https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo.png', // 导航栏logo
|
||||
repo: 'dunwu/spring-tutorial', // 导航栏右侧生成Github链接
|
||||
searchMaxSuggestions: 10, // 搜索结果显示最大数
|
||||
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
|
||||
|
||||
docsDir: 'docs', // 编辑的文件夹
|
||||
editLinks: true, // 编辑链接
|
||||
editLinkText: '📝 帮助改善此页面!',
|
||||
|
||||
// 以下配置是Vdoing主题改动的和新增的配置
|
||||
sidebar: { mode: 'structuring', collapsable: true }, // 侧边栏 'structuring' | { mode: 'structuring', collapsable:
|
||||
// Boolean} | 'auto' | 自定义 温馨提示:目录页数据依赖于结构化的侧边栏数据,如果你不设置为'structuring',将无法使用目录页
|
||||
|
||||
sidebarOpen: true, // 初始状态是否打开侧边栏,默认true
|
||||
updateBar: {
|
||||
// 最近更新栏
|
||||
showToArticle: true // 显示到文章页底部,默认true
|
||||
// moreArticle: '/archives' // “更多文章”跳转的页面,默认'/archives'
|
||||
},
|
||||
// titleBadge: false, // 文章标题前的图标是否显示,默认true
|
||||
// titleBadgeIcons: [ // 文章标题前图标的地址,默认主题内置图标
|
||||
// '图标地址1',
|
||||
// '图标地址2'
|
||||
// ],
|
||||
// bodyBgImg: [
|
||||
// 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175828.jpeg',
|
||||
// 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175845.jpeg',
|
||||
// 'https://cdn.jsdelivr.net/gh/xugaoyi/image_store/blog/20200507175846.jpeg'
|
||||
// ], // body背景大图,默认无。 单张图片 String || 多张图片 Array, 多张图片时每隔15秒换一张。
|
||||
|
||||
// categoryText: '随笔', // 碎片化文章(_posts文件夹的文章)预设生成的分类值,默认'随笔'
|
||||
|
||||
// contentBgStyle: 1,
|
||||
|
||||
category: true, // 是否打开分类功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含分类字段 2.页面中显示与分类相关的信息和模块 3.自动生成分类页面(在@pages文件夹)。如关闭,则反之。
|
||||
tag: true, // 是否打开标签功能,默认true。 如打开,会做的事情有:1. 自动生成的frontmatter包含标签字段 2.页面中显示与标签相关的信息和模块 3.自动生成标签页面(在@pages文件夹)。如关闭,则反之。
|
||||
archive: true, // 是否打开归档功能,默认true。 如打开,会做的事情有:1.自动生成归档页面(在@pages文件夹)。如关闭,则反之。
|
||||
|
||||
author: {
|
||||
// 文章默认的作者信息,可在md文件中单独配置此信息 String | {name: String, href: String}
|
||||
name: 'dunwu', // 必需
|
||||
href: 'https://github.com/dunwu' // 可选的
|
||||
},
|
||||
social: {
|
||||
// 社交图标,显示于博主信息栏和页脚栏
|
||||
// iconfontCssFile: '//at.alicdn.com/t/font_1678482_u4nrnp8xp6g.css', // 可选,阿里图标库在线css文件地址,对于主题没有的图标可自由添加
|
||||
icons: [
|
||||
{
|
||||
iconClass: 'icon-youjian',
|
||||
title: '发邮件',
|
||||
link: 'mailto:forbreak@163.com'
|
||||
},
|
||||
{
|
||||
iconClass: 'icon-github',
|
||||
title: 'GitHub',
|
||||
link: 'https://github.com/dunwu'
|
||||
}
|
||||
]
|
||||
},
|
||||
footer: {
|
||||
// 页脚信息
|
||||
createYear: 2019, // 博客创建年份
|
||||
copyrightInfo: '钝悟(dunwu) | CC-BY-SA-4.0' // 博客版权信息,支持a标签
|
||||
},
|
||||
htmlModules
|
||||
},
|
||||
|
||||
// 插件
|
||||
plugins: [
|
||||
[
|
||||
'@vuepress/active-header-links',
|
||||
require('./plugins/love-me'),
|
||||
{
|
||||
sidebarLinkSelector: '.sidebar-link',
|
||||
headerAnchorSelector: '.header-anchor',
|
||||
},
|
||||
// 鼠标点击爱心特效
|
||||
color: '#11a8cd', // 爱心颜色,默认随机色
|
||||
excludeClassName: 'theme-vdoing-content' // 要排除元素的class, 默认空''
|
||||
}
|
||||
],
|
||||
['@vuepress/back-to-top', true],
|
||||
|
||||
['fulltext-search'], // 全文搜索
|
||||
|
||||
// ['thirdparty-search', { // 可以添加第三方搜索链接的搜索框(原官方搜索框的参数仍可用)
|
||||
// thirdparty: [ // 可选,默认 []
|
||||
// {
|
||||
// title: '在GitHub中搜索',
|
||||
// frontUrl: 'https://github.com/search?q=', // 搜索链接的前面部分
|
||||
// behindUrl: '' // 搜索链接的后面部分,可选,默认 ''
|
||||
// },
|
||||
// {
|
||||
// title: '在npm中搜索',
|
||||
// frontUrl: 'https://www.npmjs.com/search?q=',
|
||||
// },
|
||||
// {
|
||||
// title: '在Bing中搜索',
|
||||
// frontUrl: 'https://cn.bing.com/search?q='
|
||||
// }
|
||||
// ]
|
||||
// }],
|
||||
|
||||
[
|
||||
'@vuepress/pwa',
|
||||
'one-click-copy',
|
||||
{
|
||||
serviceWorker: true,
|
||||
updatePopup: true,
|
||||
},
|
||||
// 代码块复制按钮
|
||||
copySelector: ['div[class*="language-"] pre', 'div[class*="aside-code"] aside'], // String or Array
|
||||
copyMessage: '复制成功', // default is 'Copy successfully and then paste it for use.'
|
||||
duration: 1000, // prompt message display time.
|
||||
showInMobile: false // whether to display on the mobile side, default: false.
|
||||
}
|
||||
],
|
||||
[
|
||||
'@vuepress/last-updated',
|
||||
'demo-block',
|
||||
{
|
||||
// demo演示模块 https://github.com/xiguaxigua/vuepress-plugin-demo-block
|
||||
settings: {
|
||||
// jsLib: ['http://xxx'], // 在线示例(jsfiddle, codepen)中的js依赖
|
||||
// cssLib: ['http://xxx'], // 在线示例中的css依赖
|
||||
// vue: 'https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js', // 在线示例中的vue依赖
|
||||
jsfiddle: false, // 是否显示 jsfiddle 链接
|
||||
codepen: true, // 是否显示 codepen 链接
|
||||
horizontal: false // 是否展示为横向样式
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'vuepress-plugin-zooming', // 放大图片
|
||||
{
|
||||
selector: '.theme-vdoing-content img:not(.no-zoom)',
|
||||
options: {
|
||||
bgColor: 'rgba(0,0,0,0.6)'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'@vuepress/last-updated', // "上次更新"时间格式
|
||||
{
|
||||
transformer: (timestamp, lang) => {
|
||||
// 不要忘了安装 moment
|
||||
const moment = require('moment')
|
||||
moment.locale(lang)
|
||||
return moment(timestamp).fromNow()
|
||||
},
|
||||
},
|
||||
],
|
||||
['@vuepress/medium-zoom', true],
|
||||
[
|
||||
'container',
|
||||
{
|
||||
type: 'vue',
|
||||
before: '<pre class="vue-container"><code>',
|
||||
after: '</code></pre>',
|
||||
},
|
||||
const dayjs = require('dayjs') // https://day.js.org/
|
||||
return dayjs(timestamp).format('YYYY/MM/DD, HH:mm:ss')
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'container',
|
||||
'vuepress-plugin-comment', // 评论
|
||||
{
|
||||
type: 'upgrade',
|
||||
before: (info) => `<UpgradePath title="${info}">`,
|
||||
after: '</UpgradePath>',
|
||||
},
|
||||
],
|
||||
['flowchart'],
|
||||
choosen: 'gitalk',
|
||||
options: {
|
||||
clientID: '01d530004cf00567e0ee',
|
||||
clientSecret: '7dd472f49b47521f80838d810b568c8084b7f1fa',
|
||||
repo: 'spring-tutorial', // GitHub 仓库
|
||||
owner: 'dunwu', // GitHub仓库所有者
|
||||
admin: ['dunwu'], // 对仓库有写权限的人
|
||||
// distractionFreeMode: true,
|
||||
pagerDirection: 'last', // 'first'正序 | 'last'倒序
|
||||
id: '<%- (frontmatter.permalink || frontmatter.to.path).slice(-16) %>', // 页面的唯一标识,长度不能超过50
|
||||
title: '「评论」<%- frontmatter.title %>', // GitHub issue 的标题
|
||||
labels: ['Gitalk', 'Comment'], // GitHub issue 的标签
|
||||
body: '页面:<%- window.location.origin + (frontmatter.to.path || window.location.pathname) %>' // GitHub issue 的内容
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
// 监听文件变化并重新构建
|
||||
extraWatchFiles: ['.vuepress/config.js', '.vuepress/config/htmlModules.js']
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = ''
|
|
@ -0,0 +1,52 @@
|
|||
/** 插入自定义html模块 (可用于插入广告模块等)
|
||||
* {
|
||||
* homeSidebarB: htmlString, 首页侧边栏底部
|
||||
*
|
||||
* sidebarT: htmlString, 全局左侧边栏顶部
|
||||
* sidebarB: htmlString, 全局左侧边栏底部
|
||||
*
|
||||
* pageT: htmlString, 全局页面顶部
|
||||
* pageB: htmlString, 全局页面底部
|
||||
* pageTshowMode: string, 页面顶部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页①
|
||||
* pageBshowMode: string, 页面底部-显示方式:未配置默认全局;'article' => 仅文章页①; 'custom' => 仅自定义页①
|
||||
*
|
||||
* windowLB: htmlString, 全局左下角②
|
||||
* windowRB: htmlString, 全局右下角②
|
||||
* }
|
||||
*
|
||||
* ①注:在.md文件front matter配置`article: false`的页面是自定义页,未配置的默认是文章页(首页除外)。
|
||||
* ②注:windowLB 和 windowRB:1.展示区块最大宽高200px*400px。2.请给自定义元素定一个不超过200px*400px的宽高。3.在屏幕宽度小于960px时无论如何都不会显示。
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// 万维广告
|
||||
pageB: `
|
||||
<div class="wwads-cn wwads-horizontal pageB" data-id="136" style="width:100%;max-height:80px;min-height:auto;"></div>
|
||||
<style>
|
||||
.pageB img{width:80px!important;}
|
||||
.wwads-horizontal .wwads-text, .wwads-content .wwads-text{line-height:1;}
|
||||
</style>
|
||||
`,
|
||||
windowRB: `
|
||||
<div class="wwads-cn wwads-vertical windowRB" data-id="136" style="max-width:160px;
|
||||
min-width: auto;min-height:auto;"></div>
|
||||
<style>
|
||||
.windowRB{ padding: 0;}
|
||||
.windowRB .wwads-img{margin-top: 10px;}
|
||||
.windowRB .wwads-content{margin: 0 10px 10px 10px;}
|
||||
.custom-html-window-rb .close-but{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
}
|
||||
|
||||
// module.exports = {
|
||||
// homeSidebarB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// sidebarT: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// sidebarB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// pageT: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// pageB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// windowLB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// windowRB: `<div style="width:100%;height:100px;color:#fff;background: #eee;">自定义模块测试</div>`,
|
||||
// }
|
|
@ -1,7 +1,9 @@
|
|||
export default ({ Vue, isServer }) => {
|
||||
if (!isServer) {
|
||||
import('vue-toasted' /* webpackChunkName: "notification" */).then(module => {
|
||||
Vue.use(module.default)
|
||||
})
|
||||
}
|
||||
// import vue from 'vue/dist/vue.esm.browser'
|
||||
export default ({
|
||||
Vue, // VuePress 正在使用的 Vue 构造函数
|
||||
options, // 附加到根实例的一些选项
|
||||
router, // 当前应用的路由实例
|
||||
siteData // 站点元数据
|
||||
}) => {
|
||||
// window.Vue = vue // 使页面中可以使用Vue构造函数 (使页面中的vue demo生效)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
const path = require('path')
|
||||
const LoveMyPlugin = (options = {}) => ({
|
||||
define() {
|
||||
const COLOR =
|
||||
options.color ||
|
||||
'rgb(' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ',' + ~~(255 * Math.random()) + ')'
|
||||
const EXCLUDECLASS = options.excludeClassName || ''
|
||||
return { COLOR, EXCLUDECLASS }
|
||||
},
|
||||
enhanceAppFiles: [path.resolve(__dirname, 'love-me.js')]
|
||||
})
|
||||
module.exports = LoveMyPlugin
|
|
@ -0,0 +1,89 @@
|
|||
export default () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
;(function(e, t, a) {
|
||||
function r() {
|
||||
for (var e = 0; e < s.length; e++)
|
||||
s[e].alpha <= 0
|
||||
? (t.body.removeChild(s[e].el), s.splice(e, 1))
|
||||
: (s[e].y--,
|
||||
(s[e].scale += 0.004),
|
||||
(s[e].alpha -= 0.013),
|
||||
(s[e].el.style.cssText =
|
||||
'left:' +
|
||||
s[e].x +
|
||||
'px;top:' +
|
||||
s[e].y +
|
||||
'px;opacity:' +
|
||||
s[e].alpha +
|
||||
';transform:scale(' +
|
||||
s[e].scale +
|
||||
',' +
|
||||
s[e].scale +
|
||||
') rotate(45deg);background:' +
|
||||
s[e].color +
|
||||
';z-index:99999'))
|
||||
requestAnimationFrame(r)
|
||||
}
|
||||
function n() {
|
||||
var t = 'function' == typeof e.onclick && e.onclick
|
||||
|
||||
e.onclick = function(e) {
|
||||
// 过滤指定元素
|
||||
let mark = true
|
||||
EXCLUDECLASS &&
|
||||
e.path &&
|
||||
e.path.forEach(item => {
|
||||
if (item.nodeType === 1) {
|
||||
typeof item.className === 'string' && item.className.indexOf(EXCLUDECLASS) > -1 ? (mark = false) : ''
|
||||
}
|
||||
})
|
||||
|
||||
if (mark) {
|
||||
t && t(), o(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
function o(e) {
|
||||
var a = t.createElement('div')
|
||||
;(a.className = 'heart'),
|
||||
s.push({
|
||||
el: a,
|
||||
x: e.clientX - 5,
|
||||
y: e.clientY - 5,
|
||||
scale: 1,
|
||||
alpha: 1,
|
||||
color: COLOR
|
||||
}),
|
||||
t.body.appendChild(a)
|
||||
}
|
||||
function i(e) {
|
||||
var a = t.createElement('style')
|
||||
a.type = 'text/css'
|
||||
try {
|
||||
a.appendChild(t.createTextNode(e))
|
||||
} catch (t) {
|
||||
a.styleSheet.cssText = e
|
||||
}
|
||||
t.getElementsByTagName('head')[0].appendChild(a)
|
||||
}
|
||||
// function c() {
|
||||
// return "rgb(" + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + "," + ~~ (255 * Math.random()) + ")"
|
||||
// }
|
||||
var s = []
|
||||
;(e.requestAnimationFrame =
|
||||
e.requestAnimationFrame ||
|
||||
e.webkitRequestAnimationFrame ||
|
||||
e.mozRequestAnimationFrame ||
|
||||
e.oRequestAnimationFrame ||
|
||||
e.msRequestAnimationFrame ||
|
||||
function(e) {
|
||||
setTimeout(e, 1e3 / 60)
|
||||
}),
|
||||
i(
|
||||
".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"
|
||||
),
|
||||
n(),
|
||||
r()
|
||||
})(window, document)
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Markmap</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#mindmap {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg id="mindmap"></svg>
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@5"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/markmap-lib@0.8.0/dist/browser/view.min.js"></script>
|
||||
<script>
|
||||
;((a, t, e, n) => {
|
||||
const { Markmap: s, loadPlugins: o } = window.markmap
|
||||
;(t ? t(o, e, n) : Promise.resolve()).then(() => {
|
||||
window.mm = s.create('svg#mindmap', null, a)
|
||||
})
|
||||
})(
|
||||
{
|
||||
t: 'heading',
|
||||
d: 1,
|
||||
p: {},
|
||||
v: 'markmap-lib',
|
||||
c: [
|
||||
{
|
||||
t: 'heading',
|
||||
d: 2,
|
||||
p: {},
|
||||
v: 'Links',
|
||||
c: [
|
||||
{
|
||||
t: 'list_item',
|
||||
d: 3,
|
||||
p: {},
|
||||
v:
|
||||
'<a href="https://markmap.js.org/" target="_blank" rel="noopener noreferrer">https://markmap.js.org/</a>'
|
||||
},
|
||||
{
|
||||
t: 'list_item',
|
||||
d: 3,
|
||||
p: {},
|
||||
v:
|
||||
'<a href="https://github.com/gera2ld/markmap-lib" title="" target="_blank" rel="noopener noreferrer">GitHub</a>'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
t: 'heading',
|
||||
d: 2,
|
||||
p: {},
|
||||
v: 'Related',
|
||||
c: [
|
||||
{
|
||||
t: 'list_item',
|
||||
d: 3,
|
||||
p: {},
|
||||
v:
|
||||
'<a href="https://github.com/gera2ld/coc-markmap" title="" target="_blank" rel="noopener noreferrer">coc-markmap</a>'
|
||||
},
|
||||
{
|
||||
t: 'list_item',
|
||||
d: 3,
|
||||
p: {},
|
||||
v:
|
||||
'<a href="https://github.com/gera2ld/gatsby-remark-markmap" title="" target="_blank" rel="noopener noreferrer">gatsby-remark-markmap</a>'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
t: 'heading',
|
||||
d: 2,
|
||||
p: {},
|
||||
v: 'Features',
|
||||
c: [
|
||||
{ t: 'list_item', d: 3, p: {}, v: 'links' },
|
||||
{ t: 'list_item', d: 3, p: {}, v: '<strong>inline</strong> <del>text</del> <em>styles</em>' },
|
||||
{ t: 'list_item', d: 3, p: {}, v: 'multiline<br/>text' },
|
||||
{ t: 'list_item', d: 3, p: {}, v: '<code>inline code</code>' },
|
||||
{
|
||||
t: 'list_item',
|
||||
d: 3,
|
||||
p: {},
|
||||
v: '<pre><code class="language-js">console.log(\'code block\');\n</code></pre>'
|
||||
},
|
||||
{
|
||||
t: 'list_item',
|
||||
d: 3,
|
||||
p: {},
|
||||
v: 'MathJax - <code>\\(x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}\\)</code>'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
(a, t, e) => a(t, e),
|
||||
['mathJax', 'prism'],
|
||||
{ mathJax: true, prism: true }
|
||||
)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,93 @@
|
|||
.home-wrapper .banner .banner-conent .hero h1{
|
||||
font-size 2.8rem!important
|
||||
}
|
||||
// 文档中适配
|
||||
table
|
||||
width auto
|
||||
.page >*:not(.footer),.card-box
|
||||
box-shadow: none!important
|
||||
|
||||
.page
|
||||
@media (min-width $contentWidth + 80)
|
||||
padding-top $navbarHeight!important
|
||||
.home-wrapper .banner .banner-conent
|
||||
padding 0 2.9rem
|
||||
box-sizing border-box
|
||||
.home-wrapper .banner .slide-banner .slide-banner-wrapper .slide-item a
|
||||
h2
|
||||
margin-top 2rem
|
||||
font-size 1.2rem!important
|
||||
p
|
||||
padding 0 1rem
|
||||
|
||||
// 评论区颜色重置
|
||||
.gt-container
|
||||
.gt-ico-tip
|
||||
&::after
|
||||
content: '。( Win + . ) or ( ⌃ + ⌘ + ␣ ) open Emoji'
|
||||
color: #999
|
||||
.gt-meta
|
||||
border-color var(--borderColor)!important
|
||||
.gt-comments-null
|
||||
color var(--textColor)
|
||||
opacity .5
|
||||
.gt-header-textarea
|
||||
color var(--textColor)
|
||||
background rgba(180,180,180,0.1)!important
|
||||
.gt-btn
|
||||
border-color $accentColor!important
|
||||
background-color $accentColor!important
|
||||
.gt-btn-preview
|
||||
background-color rgba(255,255,255,0)!important
|
||||
color $accentColor!important
|
||||
a
|
||||
color $accentColor!important
|
||||
.gt-svg svg
|
||||
fill $accentColor!important
|
||||
.gt-comment-content,.gt-comment-admin .gt-comment-content
|
||||
background-color rgba(150,150,150,0.1)!important
|
||||
&:hover
|
||||
box-shadow 0 0 25px rgba(150,150,150,.5)!important
|
||||
.gt-comment-body
|
||||
color var(--textColor)!important
|
||||
|
||||
|
||||
// qq徽章
|
||||
.qq
|
||||
position: relative;
|
||||
.qq::after
|
||||
content: "可撩";
|
||||
background: $accentColor;
|
||||
color:#fff;
|
||||
padding: 0 5px;
|
||||
border-radius: 10px;
|
||||
font-size:12px;
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -35px;
|
||||
transform:scale(0.85);
|
||||
|
||||
// demo模块图标颜色
|
||||
body .vuepress-plugin-demo-block__wrapper
|
||||
&,.vuepress-plugin-demo-block__display
|
||||
border-color rgba(160,160,160,.3)
|
||||
.vuepress-plugin-demo-block__footer:hover
|
||||
.vuepress-plugin-demo-block__expand::before
|
||||
border-top-color: $accentColor !important;
|
||||
border-bottom-color: $accentColor !important;
|
||||
svg
|
||||
fill: $accentColor !important;
|
||||
|
||||
|
||||
// 全文搜索框
|
||||
.suggestions
|
||||
overflow: auto
|
||||
max-height: calc(100vh - 6rem)
|
||||
@media (max-width: 719px) {
|
||||
width: 90vw;
|
||||
min-width: 90vw!important;
|
||||
margin-right: -20px;
|
||||
}
|
||||
.highlight
|
||||
color: $accentColor
|
||||
font-weight: bold
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
// 原主题变量已弃用,以下是vdoing使用的变量,你可以在这个文件内修改它们。
|
||||
|
||||
//***vdoing主题-变量***//
|
||||
|
||||
// // 颜色
|
||||
|
||||
// $bannerTextColor = #fff // 首页banner区(博客标题)文本颜色
|
||||
// $accentColor = #11A8CD
|
||||
// $arrowBgColor = #ccc
|
||||
// $badgeTipColor = #42b983
|
||||
// $badgeWarningColor = darken(#ffe564, 35%)
|
||||
// $badgeErrorColor = #DA5961
|
||||
|
||||
// // 布局
|
||||
// $navbarHeight = 3.6rem
|
||||
// $sidebarWidth = 18rem
|
||||
// $contentWidth = 860px
|
||||
// $homePageWidth = 1100px
|
||||
// $rightMenuWidth = 230px // 右侧菜单
|
||||
|
||||
// // 代码块
|
||||
// $lineNumbersWrapperWidth = 2.5rem
|
||||
|
||||
// 浅色模式
|
||||
.theme-mode-light
|
||||
--bodyBg: rgba(255,255,255,1)
|
||||
--mainBg: rgba(255,255,255,1)
|
||||
--sidebarBg: rgba(255,255,255,.8)
|
||||
--blurBg: rgba(255,255,255,.9)
|
||||
--textColor: #004050
|
||||
--textLightenColor: #0085AD
|
||||
--borderColor: rgba(0,0,0,.15)
|
||||
--codeBg: #f6f6f6
|
||||
--codeColor: #525252
|
||||
codeThemeLight()
|
||||
|
||||
// 深色模式
|
||||
.theme-mode-dark
|
||||
--bodyBg: rgba(30,30,34,1)
|
||||
--mainBg: rgba(30,30,34,1)
|
||||
--sidebarBg: rgba(30,30,34,.8)
|
||||
--blurBg: rgba(30,30,34,.8)
|
||||
--textColor: rgb(140,140,150)
|
||||
--textLightenColor: #0085AD
|
||||
--borderColor: #2C2C3A
|
||||
--codeBg: #252526
|
||||
--codeColor: #fff
|
||||
codeThemeDark()
|
||||
|
||||
// 阅读模式
|
||||
.theme-mode-read
|
||||
--bodyBg: rgba(245,245,213,1)
|
||||
--mainBg: rgba(245,245,213,1)
|
||||
--sidebarBg: rgba(245,245,213,.8)
|
||||
--blurBg: rgba(245,245,213,.9)
|
||||
--textColor: #004050
|
||||
--textLightenColor: #0085AD
|
||||
--borderColor: rgba(0,0,0,.15)
|
||||
--codeBg: #282c34
|
||||
--codeColor: #fff
|
||||
codeThemeDark()
|
|
@ -1,3 +1,18 @@
|
|||
---
|
||||
title: Spring 概述
|
||||
date: 2019-11-22 10:46:02
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring综合
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
permalink: /pages/9d3091/
|
||||
---
|
||||
|
||||
# Spring 概述
|
||||
|
||||
> Spring 是最受欢迎的企业级 Java 应用程序开发框架。
|
|
@ -0,0 +1,952 @@
|
|||
---
|
||||
title: SpringBoot 知识图谱
|
||||
date: 2020-08-12 07:01:26
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring综合
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/430f53/
|
||||
---
|
||||
|
||||
# SpringBoot 知识图谱
|
||||
|
||||
> 1. 预警:本文非常长,建议先 mark 后看,也许是最后一次写这么长的文章
|
||||
> 2. 说明:前面有 4 个小节关于 Spring 的基础知识,分别是:IOC 容器、JavaConfig、事件监听、SpringFactoriesLoader 详解,它们占据了本文的大部分内容,虽然它们之间可能没有太多的联系,但这些知识对于理解 Spring Boot 的核心原理至关重要,如果你对 Spring 框架烂熟于心,完全可以跳过这 4 个小节。正是因为这个系列的文章是由这些看似不相关的知识点组成,因此取名知识清单。
|
||||
|
||||
在过去两三年的 Spring 生态圈,最让人兴奋的莫过于 Spring Boot 框架。或许从命名上就能看出这个框架的设计初衷:快速的启动 Spring 应用。因而 Spring Boot 应用本质上就是一个基于 Spring 框架的应用,它是 Spring 对“约定优先于配置”理念的最佳实践产物,它能够帮助开发者更快速高效地构建基于 Spring 生态圈的应用。
|
||||
|
||||
那 Spring Boot 有何魔法?**自动配置**、**起步依赖**、**Actuator**、**命令行界面(CLI)** 是 Spring Boot 最重要的 4 大核心特性,其中 CLI 是 Spring Boot 的可选特性,虽然它功能强大,但也引入了一套不太常规的开发模型,因而这个系列的文章仅关注其它 3 种特性。如文章标题,本文是这个系列的第一部分,将为你打开 Spring Boot 的大门,重点为你剖析其启动流程以及自动配置实现原理。要掌握这部分核心内容,理解一些 Spring 框架的基础知识,将会让你事半功倍。
|
||||
|
||||
## 一、抛砖引玉:探索 Spring IoC 容器
|
||||
|
||||
如果有看过`SpringApplication.run()`方法的源码,Spring Boot 冗长无比的启动流程一定会让你抓狂,透过现象看本质,SpringApplication 只是将一个典型的 Spring 应用的启动流程进行了扩展,因此,透彻理解 Spring 容器是打开 Spring Boot 大门的一把钥匙。
|
||||
|
||||
### 1.1、Spring IoC 容器
|
||||
|
||||
可以把 Spring IoC 容器比作一间餐馆,当你来到餐馆,通常会直接招呼服务员:点菜!至于菜的原料是什么?如何用原料把菜做出来?可能你根本就不关心。IoC 容器也是一样,你只需要告诉它需要某个 bean,它就把对应的实例(instance)扔给你,至于这个 bean 是否依赖其他组件,怎样完成它的初始化,根本就不需要你关心。
|
||||
|
||||
作为餐馆,想要做出菜肴,得知道菜的原料和菜谱,同样地,IoC 容器想要管理各个业务对象以及它们之间的依赖关系,需要通过某种途径来记录和管理这些信息。`BeanDefinition`对象就承担了这个责任:容器中的每一个 bean 都会有一个对应的 BeanDefinition 实例,该实例负责保存 bean 对象的所有必要信息,包括 bean 对象的 class 类型、是否是抽象类、构造方法和参数、其它属性等等。当客户端向容器请求相应对象时,容器就会通过这些信息为客户端返回一个完整可用的 bean 实例。
|
||||
|
||||
原材料已经准备好(把 BeanDefinition 看着原料),开始做菜吧,等等,你还需要一份菜谱,`BeanDefinitionRegistry`和`BeanFactory`就是这份菜谱,BeanDefinitionRegistry 抽象出 bean 的注册逻辑,而 BeanFactory 则抽象出了 bean 的管理逻辑,而各个 BeanFactory 的实现类就具体承担了 bean 的注册以及管理工作。它们之间的关系就如下图:
|
||||
|
||||
![img](https://user-gold-cdn.xitu.io/2018/9/9/165bd49d06649b0b?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) _BeanFactory、BeanDefinitionRegistry 关系图(来自:Spring 揭秘)_
|
||||
|
||||
`DefaultListableBeanFactory`作为一个比较通用的 BeanFactory 实现,它同时也实现了 BeanDefinitionRegistry 接口,因此它就承担了 Bean 的注册管理工作。从图中也可以看出,BeanFactory 接口中主要包含 getBean、containBean、getType、getAliases 等管理 bean 的方法,而 BeanDefinitionRegistry 接口则包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注册管理 BeanDefinition 的方法。
|
||||
|
||||
下面通过一段简单的代码来模拟 BeanFactory 底层是如何工作的:
|
||||
|
||||
```
|
||||
// 默认容器实现
|
||||
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
|
||||
// 根据业务对象构造相应的BeanDefinition
|
||||
AbstractBeanDefinition definition = new RootBeanDefinition(Business.class,true);
|
||||
// 将bean定义注册到容器中
|
||||
beanRegistry.registerBeanDefinition("beanName",definition);
|
||||
// 如果有多个bean,还可以指定各个bean之间的依赖关系
|
||||
// ........
|
||||
|
||||
// 然后可以从容器中获取这个bean的实例
|
||||
// 注意:这里的beanRegistry其实实现了BeanFactory接口,所以可以强转,
|
||||
// 单纯的BeanDefinitionRegistry是无法强制转换到BeanFactory类型的
|
||||
BeanFactory container = (BeanFactory)beanRegistry;
|
||||
Business business = (Business)container.getBean("beanName");
|
||||
```
|
||||
|
||||
这段代码仅为了说明 BeanFactory 底层的大致工作流程,实际情况会更加复杂,比如 bean 之间的依赖关系可能定义在外部配置文件(XML/Properties)中、也可能是注解方式。Spring IoC 容器的整个工作流程大致可以分为两个阶段:
|
||||
|
||||
①、容器启动阶段
|
||||
|
||||
容器启动时,会通过某种途径加载`Configuration MetaData`。除了代码方式比较直接外,在大部分情况下,容器需要依赖某些工具类,比如:`BeanDefinitionReader`,BeanDefinitionReader 会对加载的`Configuration MetaData`进行解析和分析,并将分析后的信息组装为相应的 BeanDefinition,最后把这些保存了 bean 定义的 BeanDefinition,注册到相应的 BeanDefinitionRegistry,这样容器的启动工作就完成了。这个阶段主要完成一些准备性工作,更侧重于 bean 对象管理信息的收集,当然一些验证性或者辅助性的工作也在这一阶段完成。
|
||||
|
||||
来看一个简单的例子吧,过往,所有的 bean 都定义在 XML 配置文件中,下面的代码将模拟 BeanFactory 如何从配置文件中加载 bean 的定义以及依赖关系:
|
||||
|
||||
```
|
||||
// 通常为BeanDefinitionRegistry的实现类,这里以DeFaultListabeBeanFactory为例
|
||||
BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();
|
||||
// XmlBeanDefinitionReader实现了BeanDefinitionReader接口,用于解析XML文件
|
||||
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);
|
||||
// 加载配置文件
|
||||
beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
|
||||
|
||||
// 从容器中获取bean实例
|
||||
BeanFactory container = (BeanFactory)beanRegistry;
|
||||
Business business = (Business)container.getBean("beanName");
|
||||
```
|
||||
|
||||
②、Bean 的实例化阶段
|
||||
|
||||
经过第一阶段,所有 bean 定义都通过 BeanDefinition 的方式注册到 BeanDefinitionRegistry 中,当某个请求通过容器的 getBean 方法请求某个对象,或者因为依赖关系容器需要隐式的调用 getBean 时,就会触发第二阶段的活动:容器会首先检查所请求的对象之前是否已经实例化完成。如果没有,则会根据注册的 BeanDefinition 所提供的信息实例化被请求对象,并为其注入依赖。当该对象装配完毕后,容器会立即将其返回给请求方法使用。
|
||||
|
||||
BeanFactory 只是 Spring IoC 容器的一种实现,如果没有特殊指定,它采用采用延迟初始化策略:只有当访问容器中的某个对象时,才对该对象进行初始化和依赖注入操作。而在实际场景下,我们更多的使用另外一种类型的容器:`ApplicationContext`,它构建在 BeanFactory 之上,属于更高级的容器,除了具有 BeanFactory 的所有能力之外,还提供对事件监听机制以及国际化的支持等。它管理的 bean,在容器启动时全部完成初始化和依赖注入操作。
|
||||
|
||||
### 1.2、Spring 容器扩展机制
|
||||
|
||||
IoC 容器负责管理容器中所有 bean 的生命周期,而在 bean 生命周期的不同阶段,Spring 提供了不同的扩展点来改变 bean 的命运。在容器的启动阶段,`BeanFactoryPostProcessor`允许我们在容器实例化相应对象之前,对注册到容器的 BeanDefinition 所保存的信息做一些额外的操作,比如修改 bean 定义的某些属性或者增加其他信息等。
|
||||
|
||||
如果要自定义扩展类,通常需要实现`org.springframework.beans.factory.config.BeanFactoryPostProcessor`接口,与此同时,因为容器中可能有多个 BeanFactoryPostProcessor,可能还需要实现`org.springframework.core.Ordered`接口,以保证 BeanFactoryPostProcessor 按照顺序执行。Spring 提供了为数不多的 BeanFactoryPostProcessor 实现,我们以`PropertyPlaceholderConfigurer`来说明其大致的工作流程。
|
||||
|
||||
在 Spring 项目的 XML 配置文件中,经常可以看到许多配置项的值使用占位符,而将占位符所代表的值单独配置到独立的 properties 文件,这样可以将散落在不同 XML 文件中的配置集中管理,而且也方便运维根据不同的环境进行配置不同的值。这个非常实用的功能就是由 PropertyPlaceholderConfigurer 负责实现的。
|
||||
|
||||
根据前文,当 BeanFactory 在第一阶段加载完所有配置信息时,BeanFactory 中保存的对象的属性还是以占位符方式存在的,比如`${jdbc.mysql.url}`。当 PropertyPlaceholderConfigurer 作为 BeanFactoryPostProcessor 被应用时,它会使用 properties 配置文件中的值来替换相应的 BeanDefinition 中占位符所表示的属性值。当需要实例化 bean 时,bean 定义中的属性值就已经被替换成我们配置的值。当然其实现比上面描述的要复杂一些,这里仅说明其大致工作原理,更详细的实现可以参考其源码。
|
||||
|
||||
与之相似的,还有`BeanPostProcessor`,其存在于对象实例化阶段。跟 BeanFactoryPostProcessor 类似,它会处理容器内所有符合条件并且已经实例化后的对象。简单的对比,BeanFactoryPostProcessor 处理 bean 的定义,而 BeanPostProcessor 则处理 bean 完成实例化后的对象。BeanPostProcessor 定义了两个接口:
|
||||
|
||||
```
|
||||
public interface BeanPostProcessor {
|
||||
// 前置处理
|
||||
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
|
||||
// 后置处理
|
||||
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
|
||||
}
|
||||
```
|
||||
|
||||
为了理解这两个方法执行的时机,简单的了解下 bean 的整个生命周期:
|
||||
|
||||
![img](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="810" height="384"></svg>) _Bean 的实例化过程(来自:Spring 揭秘)_
|
||||
|
||||
`postProcessBeforeInitialization()`方法与`postProcessAfterInitialization()`分别对应图中前置处理和后置处理两个步骤将执行的方法。这两个方法中都传入了 bean 对象实例的引用,为扩展容器的对象实例化过程提供了很大便利,在这儿几乎可以对传入的实例执行任何操作。注解、AOP 等功能的实现均大量使用了`BeanPostProcessor`,比如有一个自定义注解,你完全可以实现 BeanPostProcessor 的接口,在其中判断 bean 对象的脑袋上是否有该注解,如果有,你可以对这个 bean 实例执行任何操作,想想是不是非常的简单?
|
||||
|
||||
再来看一个更常见的例子,在 Spring 中经常能够看到各种各样的 Aware 接口,其作用就是在对象实例化完成以后将 Aware 接口定义中规定的依赖注入到当前实例中。比如最常见的`ApplicationContextAware`接口,实现了这个接口的类都可以获取到一个 ApplicationContext 对象。当容器中每个对象的实例化过程走到 BeanPostProcessor 前置处理这一步时,容器会检测到之前注册到容器的 ApplicationContextAwareProcessor,然后就会调用其 postProcessBeforeInitialization()方法,检查并设置 Aware 相关依赖。看看代码吧,是不是很简单:
|
||||
|
||||
```
|
||||
// 代码来自:org.springframework.context.support.ApplicationContextAwareProcessor
|
||||
// 其postProcessBeforeInitialization方法调用了invokeAwareInterfaces方法
|
||||
private void invokeAwareInterfaces(Object bean) {
|
||||
if (bean instanceof EnvironmentAware) {
|
||||
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
|
||||
}
|
||||
if (bean instanceof ApplicationContextAware) {
|
||||
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
|
||||
}
|
||||
// ......
|
||||
}
|
||||
```
|
||||
|
||||
最后总结一下,本小节内容和你一起回顾了 Spring 容器的部分核心内容,限于篇幅不能写更多,但理解这部分内容,足以让您轻松理解 Spring Boot 的启动原理,如果在后续的学习过程中遇到一些晦涩难懂的知识,再回过头来看看 Spring 的核心知识,也许有意想不到的效果。也许 Spring Boot 的中文资料很少,但 Spring 的中文资料和书籍有太多太多,总有东西能给你启发。
|
||||
|
||||
## 二、夯实基础:JavaConfig 与常见 Annotation
|
||||
|
||||
### 2.1、JavaConfig
|
||||
|
||||
我们知道`bean`是 Spring IOC 中非常核心的概念,Spring 容器负责 bean 的生命周期的管理。在最初,Spring 使用 XML 配置文件的方式来描述 bean 的定义以及相互间的依赖关系,但随着 Spring 的发展,越来越多的人对这种方式表示不满,因为 Spring 项目的所有业务类均以 bean 的形式配置在 XML 文件中,造成了大量的 XML 文件,使项目变得复杂且难以管理。
|
||||
|
||||
后来,基于纯 Java Annotation 依赖注入框架`Guice`出世,其性能明显优于采用 XML 方式的 Spring,甚至有部分人认为,`Guice`可以完全取代 Spring(`Guice`仅是一个轻量级 IOC 框架,取代 Spring 还差的挺远)。正是这样的危机感,促使 Spring 及社区推出并持续完善了`JavaConfig`子项目,它基于 Java 代码和 Annotation 注解来描述 bean 之间的依赖绑定关系。比如,下面是使用 XML 配置方式来描述 bean 的定义:
|
||||
|
||||
```
|
||||
<bean id="bookService" class="cn.moondev.service.BookServiceImpl"></bean>
|
||||
```
|
||||
|
||||
而基于 JavaConfig 的配置形式是这样的:
|
||||
|
||||
```
|
||||
@Configuration
|
||||
public class MoonBookConfiguration {
|
||||
|
||||
// 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中
|
||||
// 方法名默认成为该bean定义的id
|
||||
@Bean
|
||||
public BookService bookService() {
|
||||
return new BookServiceImpl();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果两个 bean 之间有依赖关系的话,在 XML 配置中应该是这样:
|
||||
|
||||
```
|
||||
<bean id="bookService" class="cn.moondev.service.BookServiceImpl">
|
||||
<property name="dependencyService" ref="dependencyService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="otherService" class="cn.moondev.service.OtherServiceImpl">
|
||||
<property name="dependencyService" ref="dependencyService"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dependencyService" class="DependencyServiceImpl"/>
|
||||
```
|
||||
|
||||
而在 JavaConfig 中则是这样:
|
||||
|
||||
```
|
||||
@Configuration
|
||||
public class MoonBookConfiguration {
|
||||
|
||||
// 如果一个bean依赖另一个bean,则直接调用对应JavaConfig类中依赖bean的创建方法即可
|
||||
// 这里直接调用dependencyService()
|
||||
@Bean
|
||||
public BookService bookService() {
|
||||
return new BookServiceImpl(dependencyService());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OtherService otherService() {
|
||||
return new OtherServiceImpl(dependencyService());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DependencyService dependencyService() {
|
||||
return new DependencyServiceImpl();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你可能注意到这个示例中,有两个 bean 都依赖于 dependencyService,也就是说当初始化 bookService 时会调用`dependencyService()`,在初始化 otherService 时也会调用`dependencyService()`,那么问题来了?这时候 IOC 容器中是有一个 dependencyService 实例还是两个?这个问题留着大家思考吧,这里不再赘述。
|
||||
|
||||
### 2.2、@ComponentScan
|
||||
|
||||
`@ComponentScan`注解对应 XML 配置形式中的``元素,表示启用组件扫描,Spring 会自动扫描所有通过注解配置的 bean,然后将其注册到 IOC 容器中。我们可以通过`basePackages`等属性来指定`@ComponentScan`自动扫描的范围,如果不指定,默认从声明`@ComponentScan`所在类的`package`进行扫描。正因为如此,SpringBoot 的启动类都默认在`src/main/java`下。
|
||||
|
||||
### 2.3、@Import
|
||||
|
||||
`@Import`注解用于导入配置类,举个简单的例子:
|
||||
|
||||
```
|
||||
@Configuration
|
||||
public class MoonBookConfiguration {
|
||||
@Bean
|
||||
public BookService bookService() {
|
||||
return new BookServiceImpl();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在有另外一个配置类,比如:`MoonUserConfiguration`,这个配置类中有一个 bean 依赖于`MoonBookConfiguration`中的 bookService,如何将这两个 bean 组合在一起?借助`@Import`即可:
|
||||
|
||||
```
|
||||
@Configuration
|
||||
// 可以同时导入多个配置类,比如:@Import({A.class,B.class})
|
||||
@Import(MoonBookConfiguration.class)
|
||||
public class MoonUserConfiguration {
|
||||
@Bean
|
||||
public UserService userService(BookService bookService) {
|
||||
return new BookServiceImpl(bookService);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
需要注意的是,在 4.2 之前,`@Import`注解只支持导入配置类,但是在 4.2 之后,它支持导入普通类,并将这个类作为一个 bean 的定义注册到 IOC 容器中。
|
||||
|
||||
### 2.4、@Conditional
|
||||
|
||||
`@Conditional`注解表示在满足某种条件后才初始化一个 bean 或者启用某些配置。它一般用在由`@Component`、`@Service`、`@Configuration`等注解标识的类上面,或者由`@Bean`标记的方法上。如果一个`@Configuration`类标记了`@Conditional`,则该类中所有标识了`@Bean`的方法和`@Import`注解导入的相关类将遵从这些条件。
|
||||
|
||||
在 Spring 里可以很方便的编写你自己的条件类,所要做的就是实现`Condition`接口,并覆盖它的`matches()`方法。举个例子,下面的简单条件类表示只有在`Classpath`里存在`JdbcTemplate`类时才生效:
|
||||
|
||||
```
|
||||
public class JdbcTemplateCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
|
||||
try {
|
||||
conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当你用 Java 来声明 bean 的时候,可以使用这个自定义条件类:
|
||||
|
||||
```
|
||||
@Conditional(JdbcTemplateCondition.class)
|
||||
@Service
|
||||
public MyService service() {
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
这个例子中只有当`JdbcTemplateCondition`类的条件成立时才会创建 MyService 这个 bean。也就是说 MyService 这 bean 的创建条件是`classpath`里面包含`JdbcTemplate`,否则这个 bean 的声明就会被忽略掉。
|
||||
|
||||
`Spring Boot`定义了很多有趣的条件,并把他们运用到了配置类上,这些配置类构成了`Spring Boot`的自动配置的基础。`Spring Boot`运用条件化配置的方法是:定义多个特殊的条件化注解,并将它们用到配置类上。下面列出了`Spring Boot`提供的部分条件化注解:
|
||||
|
||||
| 条件化注解 | 配置生效条件 |
|
||||
| ------------------------------- | ------------------------------------------------------- |
|
||||
| @ConditionalOnBean | 配置了某个特定 bean |
|
||||
| @ConditionalOnMissingBean | 没有配置特定的 bean |
|
||||
| @ConditionalOnClass | Classpath 里有指定的类 |
|
||||
| @ConditionalOnMissingClass | Classpath 里没有指定的类 |
|
||||
| @ConditionalOnExpression | 给定的 Spring Expression Language 表达式计算结果为 true |
|
||||
| @ConditionalOnJava | Java 的版本匹配特定指或者一个范围值 |
|
||||
| @ConditionalOnProperty | 指定的配置属性要有一个明确的值 |
|
||||
| @ConditionalOnResource | Classpath 里有指定的资源 |
|
||||
| @ConditionalOnWebApplication | 这是一个 Web 应用程序 |
|
||||
| @ConditionalOnNotWebApplication | 这不是一个 Web 应用程序 |
|
||||
|
||||
### 2.5、@ConfigurationProperties 与@EnableConfigurationProperties
|
||||
|
||||
当某些属性的值需要配置的时候,我们一般会在`application.properties`文件中新建配置项,然后在 bean 中使用`@Value`注解来获取配置的值,比如下面配置数据源的代码。
|
||||
|
||||
```
|
||||
// jdbc config
|
||||
jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
|
||||
jdbc.mysql.username=root
|
||||
jdbc.mysql.password=123456
|
||||
......
|
||||
|
||||
// 配置数据源
|
||||
@Configuration
|
||||
public class HikariDataSourceConfiguration {
|
||||
|
||||
@Value("jdbc.mysql.url")
|
||||
public String url;
|
||||
@Value("jdbc.mysql.username")
|
||||
public String user;
|
||||
@Value("jdbc.mysql.password")
|
||||
public String password;
|
||||
|
||||
@Bean
|
||||
public HikariDataSource dataSource() {
|
||||
HikariConfig hikariConfig = new HikariConfig();
|
||||
hikariConfig.setJdbcUrl(url);
|
||||
hikariConfig.setUsername(user);
|
||||
hikariConfig.setPassword(password);
|
||||
// 省略部分代码
|
||||
return new HikariDataSource(hikariConfig);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用`@Value`注解注入的属性通常都比较简单,如果同一个配置在多个地方使用,也存在不方便维护的问题(考虑下,如果有几十个地方在使用某个配置,而现在你想改下名字,你改怎么做?)。对于更为复杂的配置,Spring Boot 提供了更优雅的实现方式,那就是`@ConfigurationProperties`注解。我们可以通过下面的方式来改写上面的代码:
|
||||
|
||||
```
|
||||
@Component
|
||||
// 还可以通过@PropertySource("classpath:jdbc.properties")来指定配置文件
|
||||
@ConfigurationProperties("jdbc.mysql")
|
||||
// 前缀=jdbc.mysql,会在配置文件中寻找jdbc.mysql.*的配置项
|
||||
pulic class JdbcConfig {
|
||||
public String url;
|
||||
public String username;
|
||||
public String password;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public class HikariDataSourceConfiguration {
|
||||
|
||||
@AutoWired
|
||||
public JdbcConfig config;
|
||||
|
||||
@Bean
|
||||
public HikariDataSource dataSource() {
|
||||
HikariConfig hikariConfig = new HikariConfig();
|
||||
hikariConfig.setJdbcUrl(config.url);
|
||||
hikariConfig.setUsername(config.username);
|
||||
hikariConfig.setPassword(config.password);
|
||||
// 省略部分代码
|
||||
return new HikariDataSource(hikariConfig);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@ConfigurationProperties`对于更为复杂的配置,处理起来也是得心应手,比如有如下配置文件:
|
||||
|
||||
```
|
||||
#App
|
||||
app.menus[0].title=Home
|
||||
app.menus[0].name=Home
|
||||
app.menus[0].path=/
|
||||
app.menus[1].title=Login
|
||||
app.menus[1].name=Login
|
||||
app.menus[1].path=/login
|
||||
|
||||
app.compiler.timeout=5
|
||||
app.compiler.output-folder=/temp/
|
||||
|
||||
app.error=/error/
|
||||
```
|
||||
|
||||
可以定义如下配置类来接收这些属性
|
||||
|
||||
```
|
||||
@Component
|
||||
@ConfigurationProperties("app")
|
||||
public class AppProperties {
|
||||
|
||||
public String error;
|
||||
public List<Menu> menus = new ArrayList<>();
|
||||
public Compiler compiler = new Compiler();
|
||||
|
||||
public static class Menu {
|
||||
public String name;
|
||||
public String path;
|
||||
public String title;
|
||||
}
|
||||
|
||||
public static class Compiler {
|
||||
public String timeout;
|
||||
public String outputFolder;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@EnableConfigurationProperties`注解表示对`@ConfigurationProperties`的内嵌支持,默认会将对应 Properties Class 作为 bean 注入的 IOC 容器中,即在相应的 Properties 类上不用加`@Component`注解。
|
||||
|
||||
## 三、削铁如泥:SpringFactoriesLoader 详解
|
||||
|
||||
JVM 提供了 3 种类加载器:`BootstrapClassLoader`、`ExtClassLoader`、`AppClassLoader`分别加载 Java 核心类库、扩展类库以及应用的类路径(`CLASSPATH`)下的类库。JVM 通过双亲委派模型进行类的加载,我们也可以通过继承`java.lang.classloader`实现自己的类加载器。
|
||||
|
||||
何为双亲委派模型?当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的 BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。
|
||||
|
||||
采用双亲委派模型的一个好处是保证使用不同类加载器最终得到的都是同一个对象,这样就可以保证 Java 核心库的类型安全,比如,加载位于 rt.jar 包中的`java.lang.Object`类,不管是哪个加载器加载这个类,最终都是委托给顶层的 BootstrapClassLoader 来加载的,这样就可以保证任何的类加载器最终得到的都是同样一个 Object 对象。查看 ClassLoader 的源码,对双亲委派模型会有更直观的认识:
|
||||
|
||||
```
|
||||
protected Class<?> loadClass(String name, boolean resolve) {
|
||||
synchronized (getClassLoadingLock(name)) {
|
||||
// 首先,检查该类是否已经被加载,如果从JVM缓存中找到该类,则直接返回
|
||||
Class<?> c = findLoadedClass(name);
|
||||
if (c == null) {
|
||||
try {
|
||||
// 遵循双亲委派的模型,首先会通过递归从父加载器开始找,
|
||||
// 直到父类加载器是BootstrapClassLoader为止
|
||||
if (parent != null) {
|
||||
c = parent.loadClass(name, false);
|
||||
} else {
|
||||
c = findBootstrapClassOrNull(name);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {}
|
||||
if (c == null) {
|
||||
// 如果还找不到,尝试通过findClass方法去寻找
|
||||
// findClass是留给开发者自己实现的,也就是说
|
||||
// 自定义类加载器时,重写此方法即可
|
||||
c = findClass(name);
|
||||
}
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
但双亲委派模型并不能解决所有的类加载器问题,比如,Java 提供了很多服务提供者接口(`Service Provider Interface`,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等,这些 SPI 的接口由核心类库提供,却由第三方实现,这样就存在一个问题:SPI 的接口是 Java 核心库的一部分,是由 BootstrapClassLoader 加载的;SPI 实现的 Java 类一般是由 AppClassLoader 来加载的。BootstrapClassLoader 是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给 AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。
|
||||
|
||||
线程上下文类加载器(`ContextClassLoader`)正好解决了这个问题。从名称上看,可能会误解为它是一种新的类加载器,实际上,它仅仅是 Thread 类的一个变量而已,可以通过`setContextClassLoader(ClassLoader cl)`和`getContextClassLoader()`来设置和获取该对象。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是 AppClassLoader。在核心类库使用 SPI 接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。但在 JDBC 中,你可能会看到一种更直接的实现方式,比如,JDBC 驱动管理`java.sql.Driver`中的`loadInitialDrivers()`方法中,你可以直接看到 JDK 是如何加载驱动的:
|
||||
|
||||
```
|
||||
for (String aDriver : driversList) {
|
||||
try {
|
||||
// 直接使用AppClassLoader
|
||||
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
|
||||
} catch (Exception ex) {
|
||||
println("DriverManager.Initialize: load failed: " + ex);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其实讲解线程上下文类加载器,最主要是让大家在看到`Thread.currentThread().getClassLoader()`和`Thread.currentThread().getContextClassLoader()`时不会一脸懵逼,这两者除了在许多底层框架中取得的 ClassLoader 可能会有所不同外,其他大多数业务场景下都是一样的,大家只要知道它是为了解决什么问题而存在的即可。
|
||||
|
||||
类加载器除了加载 class 外,还有一个非常重要功能,就是加载资源,它可以从 jar 包中读取任何资源文件,比如,`ClassLoader.getResources(String name)`方法就是用于读取 jar 包中的资源文件,其代码如下:
|
||||
|
||||
```
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
|
||||
if (parent != null) {
|
||||
tmp[0] = parent.getResources(name);
|
||||
} else {
|
||||
tmp[0] = getBootstrapResources(name);
|
||||
}
|
||||
tmp[1] = findResources(name);
|
||||
return new CompoundEnumeration<>(tmp);
|
||||
}
|
||||
```
|
||||
|
||||
是不是觉得有点眼熟,不错,它的逻辑其实跟类加载的逻辑是一样的,首先判断父类加载器是否为空,不为空则委托父类加载器执行资源查找任务,直到 BootstrapClassLoader,最后才轮到自己查找。而不同的类加载器负责扫描不同路径下的 jar 包,就如同加载 class 一样,最后会扫描所有的 jar 包,找到符合条件的资源文件。
|
||||
|
||||
类加载器的`findResources(name)`方法会遍历其负责加载的所有 jar 包,找到 jar 包中名称为 name 的资源文件,这里的资源可以是任何文件,甚至是.class 文件,比如下面的示例,用于查找 Array.class 文件:
|
||||
|
||||
```
|
||||
// 寻找Array.class文件
|
||||
public static void main(String[] args) throws Exception{
|
||||
// Array.class的完整路径
|
||||
String name = "java/sql/Array.class";
|
||||
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
System.out.println(url.toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
运行后可以得到如下结果:
|
||||
|
||||
```
|
||||
$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class
|
||||
```
|
||||
|
||||
根据资源文件的 URL,可以构造相应的文件来读取资源内容。
|
||||
|
||||
看到这里,你可能会感到挺奇怪的,你不是要详解`SpringFactoriesLoader`吗?上来讲了一堆 ClassLoader 是几个意思?看下它的源码你就知道了:
|
||||
|
||||
```
|
||||
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
|
||||
// spring.factories文件的格式为:key=value1,value2,value3
|
||||
// 从所有的jar包中找到META-INF/spring.factories文件
|
||||
// 然后从文件中解析出key=factoryClass类名称的所有value值
|
||||
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
|
||||
String factoryClassName = factoryClass.getName();
|
||||
// 取得资源文件的URL
|
||||
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
|
||||
List<String> result = new ArrayList<String>();
|
||||
// 遍历所有的URL
|
||||
while (urls.hasMoreElements()) {
|
||||
URL url = urls.nextElement();
|
||||
// 根据资源文件URL解析properties文件
|
||||
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
|
||||
String factoryClassNames = properties.getProperty(factoryClassName);
|
||||
// 组装数据,并返回
|
||||
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
有了前面关于 ClassLoader 的知识,再来理解这段代码,是不是感觉豁然开朗:从`CLASSPATH`下的每个 Jar 包中搜寻所有`META-INF/spring.factories`配置文件,然后将解析 properties 文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去 ClassPath 路径下查找,会扫描所有路径下的 Jar 包,只不过这个文件只会在 Classpath 下的 jar 包中。来简单看下`spring.factories`文件的内容吧:
|
||||
|
||||
```
|
||||
// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
|
||||
// EnableAutoConfiguration后文会讲到,它用于开启Spring Boot自动配置功能
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
|
||||
```
|
||||
|
||||
执行`loadFactoryNames(EnableAutoConfiguration.class, classLoader)`后,得到对应的一组`@Configuration`类,
|
||||
我们就可以通过反射实例化这些类然后注入到 IOC 容器中,最后容器里就有了一系列标注了`@Configuration`的 JavaConfig 形式的配置类。
|
||||
|
||||
这就是`SpringFactoriesLoader`,它本质上属于 Spring 框架私有的一种扩展方案,类似于 SPI,Spring Boot 在 Spring 基础上的很多核心功能都是基于此,希望大家可以理解。
|
||||
|
||||
## 四、另一件武器:Spring 容器的事件监听机制
|
||||
|
||||
过去,事件监听机制多用于图形界面编程,比如:**点击**按钮、在文本框**输入**内容等操作被称为事件,而当事件触发时,应用程序作出一定的响应则表示应用监听了这个事件,而在服务器端,事件的监听机制更多的用于异步通知以及监控和异常处理。Java 提供了实现事件监听机制的两个基础类:自定义事件类型扩展自`java.util.EventObject`、事件的监听器扩展自`java.util.EventListener`。来看一个简单的实例:简单的监控一个方法的耗时。
|
||||
|
||||
首先定义事件类型,通常的做法是扩展 EventObject,随着事件的发生,相应的状态通常都封装在此类中:
|
||||
|
||||
```
|
||||
public class MethodMonitorEvent extends EventObject {
|
||||
// 时间戳,用于记录方法开始执行的时间
|
||||
public long timestamp;
|
||||
|
||||
public MethodMonitorEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
事件发布之后,相应的监听器即可对该类型的事件进行处理,我们可以在方法开始执行之前发布一个 begin 事件,在方法执行结束之后发布一个 end 事件,相应地,事件监听器需要提供方法对这两种情况下接收到的事件进行处理:
|
||||
|
||||
```
|
||||
// 1、定义事件监听接口
|
||||
public interface MethodMonitorEventListener extends EventListener {
|
||||
// 处理方法执行之前发布的事件
|
||||
public void onMethodBegin(MethodMonitorEvent event);
|
||||
// 处理方法结束时发布的事件
|
||||
public void onMethodEnd(MethodMonitorEvent event);
|
||||
}
|
||||
// 2、事件监听接口的实现:如何处理
|
||||
public class AbstractMethodMonitorEventListener implements MethodMonitorEventListener {
|
||||
|
||||
@Override
|
||||
public void onMethodBegin(MethodMonitorEvent event) {
|
||||
// 记录方法开始执行时的时间
|
||||
event.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodEnd(MethodMonitorEvent event) {
|
||||
// 计算方法耗时
|
||||
long duration = System.currentTimeMillis() - event.timestamp;
|
||||
System.out.println("耗时:" + duration);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
事件监听器接口针对不同的事件发布实际提供相应的处理方法定义,最重要的是,其方法只接收 MethodMonitorEvent 参数,说明这个监听器类只负责监听器对应的事件并进行处理。有了事件和监听器,剩下的就是发布事件,然后让相应的监听器监听并处理。通常情况,我们会有一个事件发布者,它本身作为事件源,在合适的时机,将相应的事件发布给对应的事件监听器:
|
||||
|
||||
```
|
||||
public class MethodMonitorEventPublisher {
|
||||
|
||||
private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();
|
||||
|
||||
public void methodMonitor() {
|
||||
MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
|
||||
publishEvent("begin",eventObject);
|
||||
// 模拟方法执行:休眠5秒钟
|
||||
TimeUnit.SECONDS.sleep(5);
|
||||
publishEvent("end",eventObject);
|
||||
|
||||
}
|
||||
|
||||
private void publishEvent(String status,MethodMonitorEvent event) {
|
||||
// 避免在事件处理期间,监听器被移除,这里为了安全做一个复制操作
|
||||
List<MethodMonitorEventListener> copyListeners = ➥ new ArrayList<MethodMonitorEventListener>(listeners);
|
||||
for (MethodMonitorEventListener listener : copyListeners) {
|
||||
if ("begin".equals(status)) {
|
||||
listener.onMethodBegin(event);
|
||||
} else {
|
||||
listener.onMethodEnd(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
|
||||
publisher.addEventListener(new AbstractMethodMonitorEventListener());
|
||||
publisher.methodMonitor();
|
||||
}
|
||||
// 省略实现
|
||||
public void addEventListener(MethodMonitorEventListener listener) {}
|
||||
public void removeEventListener(MethodMonitorEventListener listener) {}
|
||||
public void removeAllListeners() {}
|
||||
```
|
||||
|
||||
对于事件发布者(事件源)通常需要关注两点:
|
||||
|
||||
1. 在合适的时机发布事件。此例中的 methodMonitor()方法是事件发布的源头,其在方法执行之前和结束之后两个时间点发布 MethodMonitorEvent 事件,每个时间点发布的事件都会传给相应的监听器进行处理。在具体实现时需要注意的是,事件发布是顺序执行,为了不影响处理性能,事件监听器的处理逻辑应尽量简单。
|
||||
2. 事件监听器的管理。publisher 类中提供了事件监听器的注册与移除方法,这样客户端可以根据实际情况决定是否需要注册新的监听器或者移除某个监听器。如果这里没有提供 remove 方法,那么注册的监听器示例将一直被 MethodMonitorEventPublisher 引用,即使已经废弃不用了,也依然在发布者的监听器列表中,这会导致隐性的内存泄漏。
|
||||
|
||||
#### Spring 容器内的事件监听机制
|
||||
|
||||
Spring 的 ApplicationContext 容器内部中的所有事件类型均继承自`org.springframework.context.AppliationEvent`,容器中的所有监听器都实现`org.springframework.context.ApplicationListener`接口,并且以 bean 的形式注册在容器中。一旦在容器内发布 ApplicationEvent 及其子类型的事件,注册到容器的 ApplicationListener 就会对这些事件进行处理。
|
||||
|
||||
你应该已经猜到是怎么回事了。
|
||||
|
||||
ApplicationEvent 继承自 EventObject,Spring 提供了一些默认的实现,比如:`ContextClosedEvent`表示容器在即将关闭时发布的事件类型,`ContextRefreshedEvent`表示容器在初始化或者刷新的时候发布的事件类型......
|
||||
|
||||
容器内部使用 ApplicationListener 作为事件监听器接口定义,它继承自 EventListener。ApplicationContext 容器在启动时,会自动识别并加载 EventListener 类型的 bean,一旦容器内有事件发布,将通知这些注册到容器的 EventListener。
|
||||
|
||||
ApplicationContext 接口继承了 ApplicationEventPublisher 接口,该接口提供了`void publishEvent(ApplicationEvent event)`方法定义,不难看出,ApplicationContext 容器担当的就是事件发布者的角色。如果有兴趣可以查看`AbstractApplicationContext.publishEvent(ApplicationEvent event)`方法的源码:ApplicationContext 将事件的发布以及监听器的管理工作委托给`ApplicationEventMulticaster`接口的实现类。在容器启动时,会检查容器内是否存在名为 applicationEventMulticaster 的 ApplicationEventMulticaster 对象实例。如果有就使用其提供的实现,没有就默认初始化一个 SimpleApplicationEventMulticaster 作为实现。
|
||||
|
||||
最后,如果我们业务需要在容器内部发布事件,只需要为其注入 ApplicationEventPublisher 依赖即可:实现 ApplicationEventPublisherAware 接口或者 ApplicationContextAware 接口(Aware 接口相关内容请回顾上文)。
|
||||
|
||||
## 五、出神入化:揭秘自动配置原理
|
||||
|
||||
典型的 Spring Boot 应用的启动类一般均位于`src/main/java`根路径下,比如`MoonApplication`类:
|
||||
|
||||
```
|
||||
@SpringBootApplication
|
||||
public class MoonApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MoonApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中`@SpringBootApplication`开启组件扫描和自动配置,而`SpringApplication.run`则负责启动引导应用程序。`@SpringBootApplication`是一个复合`Annotation`,它将三个有用的注解组合在一起:
|
||||
|
||||
```
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan(excludeFilters = {
|
||||
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
||||
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
||||
public @interface SpringBootApplication {
|
||||
// ......
|
||||
}
|
||||
```
|
||||
|
||||
`@SpringBootConfiguration`就是`@Configuration`,它是 Spring 框架的注解,标明该类是一个`JavaConfig`配置类。而`@ComponentScan`启用组件扫描,前文已经详细讲解过,这里着重关注`@EnableAutoConfiguration`。
|
||||
|
||||
`@EnableAutoConfiguration`注解表示开启 Spring Boot 自动配置功能,Spring Boot 会根据应用的依赖、自定义的 bean、classpath 下有没有某个类 等等因素来猜测你需要的 bean,然后注册到 IOC 容器中。那`@EnableAutoConfiguration`是如何推算出你的需求?首先看下它的定义:
|
||||
|
||||
```
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@AutoConfigurationPackage
|
||||
@Import(EnableAutoConfigurationImportSelector.class)
|
||||
public @interface EnableAutoConfiguration {
|
||||
// ......
|
||||
}
|
||||
```
|
||||
|
||||
你的关注点应该在`@Import(EnableAutoConfigurationImportSelector.class)`上了,前文说过,`@Import`注解用于导入类,并将这个类作为一个 bean 的定义注册到容器中,这里它将把`EnableAutoConfigurationImportSelector`作为 bean 注入到容器中,而这个类会将所有符合条件的@Configuration 配置都加载到容器中,看看它的代码:
|
||||
|
||||
```
|
||||
public String[] selectImports(AnnotationMetadata annotationMetadata) {
|
||||
// 省略了大部分代码,保留一句核心代码
|
||||
// 注意:SpringBoot最近版本中,这句代码被封装在一个单独的方法中
|
||||
// SpringFactoriesLoader相关知识请参考前文
|
||||
List<String> factories = new ArrayList<String>(new LinkedHashSet<String>(
|
||||
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader)));
|
||||
}
|
||||
```
|
||||
|
||||
这个类会扫描所有的 jar 包,将所有符合条件的@Configuration 配置类注入的容器中,何为符合条件,看看`META-INF/spring.factories`的文件内容:
|
||||
|
||||
```
|
||||
// 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
|
||||
// 配置的key = EnableAutoConfiguration,与代码中一致
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
|
||||
.....
|
||||
```
|
||||
|
||||
以`DataSourceAutoConfiguration`为例,看看 Spring Boot 是如何自动配置的:
|
||||
|
||||
```
|
||||
@Configuration
|
||||
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
|
||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
|
||||
public class DataSourceAutoConfiguration {
|
||||
}
|
||||
```
|
||||
|
||||
分别说一说:
|
||||
|
||||
- `@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })`:当 Classpath 中存在 DataSource 或者 EmbeddedDatabaseType 类时才启用这个配置,否则这个配置将被忽略。
|
||||
- `@EnableConfigurationProperties(DataSourceProperties.class)`:将 DataSource 的默认配置类注入到 IOC 容器中,DataSourceproperties 定义为:
|
||||
|
||||
```
|
||||
// 提供对datasource配置信息的支持,所有的配置前缀为:spring.datasource
|
||||
@ConfigurationProperties(prefix = "spring.datasource")
|
||||
public class DataSourceProperties {
|
||||
private ClassLoader classLoader;
|
||||
private Environment environment;
|
||||
private String name = "testdb";
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
- `@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })`:导入其他额外的配置,就以`DataSourcePoolMetadataProvidersConfiguration`为例吧。
|
||||
|
||||
```
|
||||
@Configuration
|
||||
public class DataSourcePoolMetadataProvidersConfiguration {
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
|
||||
static class TomcatDataSourcePoolMetadataProviderConfiguration {
|
||||
@Bean
|
||||
public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() {
|
||||
.....
|
||||
}
|
||||
}
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
DataSourcePoolMetadataProvidersConfiguration 是数据库连接池提供者的一个配置类,即 Classpath 中存在`org.apache.tomcat.jdbc.pool.DataSource.class`,则使用 tomcat-jdbc 连接池,如果 Classpath 中存在`HikariDataSource.class`则使用 Hikari 连接池。
|
||||
|
||||
这里仅描述了 DataSourceAutoConfiguration 的冰山一角,但足以说明 Spring Boot 如何利用条件话配置来实现自动配置的。回顾一下,`@EnableAutoConfiguration`中导入了 EnableAutoConfigurationImportSelector 类,而这个类的`selectImports()`通过 SpringFactoriesLoader 得到了大量的配置类,而每一个配置类则根据条件化配置来做出决策,以实现自动配置。
|
||||
|
||||
整个流程很清晰,但漏了一个大问题:`EnableAutoConfigurationImportSelector.selectImports()`是何时执行的?其实这个方法会在容器启动过程中执行:`AbstractApplicationContext.refresh()`,更多的细节在下一小节中说明。
|
||||
|
||||
## 六、启动引导:Spring Boot 应用启动的秘密
|
||||
|
||||
### 6.1 SpringApplication 初始化
|
||||
|
||||
SpringBoot 整个启动流程分为两个步骤:初始化一个 SpringApplication 对象、执行该对象的 run 方法。看下 SpringApplication 的初始化流程,SpringApplication 的构造方法中调用 initialize(Object[] sources)方法,其代码如下:
|
||||
|
||||
```
|
||||
private void initialize(Object[] sources) {
|
||||
if (sources != null && sources.length > 0) {
|
||||
this.sources.addAll(Arrays.asList(sources));
|
||||
}
|
||||
// 判断是否是Web项目
|
||||
this.webEnvironment = deduceWebEnvironment();
|
||||
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
|
||||
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
|
||||
// 找到入口类
|
||||
this.mainApplicationClass = deduceMainApplicationClass();
|
||||
}
|
||||
```
|
||||
|
||||
初始化流程中最重要的就是通过 SpringFactoriesLoader 找到`spring.factories`文件中配置的`ApplicationContextInitializer`和`ApplicationListener`两个接口的实现类名称,以便后期构造相应的实例。`ApplicationContextInitializer`的主要目的是在`ConfigurableApplicationContext`做 refresh 之前,对 ConfigurableApplicationContext 实例做进一步的设置或处理。ConfigurableApplicationContext 继承自 ApplicationContext,其主要提供了对 ApplicationContext 进行设置的能力。
|
||||
|
||||
实现一个 ApplicationContextInitializer 非常简单,因为它只有一个方法,但大多数情况下我们没有必要自定义一个 ApplicationContextInitializer,即便是 Spring Boot 框架,它默认也只是注册了两个实现,毕竟 Spring 的容器已经非常成熟和稳定,你没有必要来改变它。
|
||||
|
||||
而`ApplicationListener`的目的就没什么好说的了,它是 Spring 框架对 Java 事件监听机制的一种框架实现,具体内容在前文 Spring 事件监听机制这个小节有详细讲解。这里主要说说,如果你想为 Spring Boot 应用添加监听器,该如何实现?
|
||||
|
||||
Spring Boot 提供两种方式来添加自定义监听器:
|
||||
|
||||
- 通过`SpringApplication.addListeners(ApplicationListener... listeners)`或者`SpringApplication.setListeners(Collection> listeners)`两个方法来添加一个或者多个自定义监听器
|
||||
- 既然 SpringApplication 的初始化流程中已经从`spring.factories`中获取到`ApplicationListener`的实现类,那么我们直接在自己的 jar 包的`META-INF/spring.factories`文件中新增配置即可:
|
||||
|
||||
```
|
||||
org.springframework.context.ApplicationListener=\
|
||||
cn.moondev.listeners.xxxxListener\
|
||||
```
|
||||
|
||||
关于 SpringApplication 的初始化,我们就说这么多。
|
||||
|
||||
### 6.2 Spring Boot 启动流程
|
||||
|
||||
Spring Boot 应用的整个启动流程都封装在 SpringApplication.run 方法中,其整个流程真的是太长太长了,但本质上就是在 Spring 容器启动的基础上做了大量的扩展,按照这个思路来看看源码:
|
||||
|
||||
```
|
||||
public ConfigurableApplicationContext run(String... args) {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
ConfigurableApplicationContext context = null;
|
||||
FailureAnalyzers analyzers = null;
|
||||
configureHeadlessProperty();
|
||||
// ①
|
||||
SpringApplicationRunListeners listeners = getRunListeners(args);
|
||||
listeners.starting();
|
||||
try {
|
||||
// ②
|
||||
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
|
||||
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
|
||||
// ③
|
||||
Banner printedBanner = printBanner(environment);
|
||||
// ④
|
||||
context = createApplicationContext();
|
||||
// ⑤
|
||||
analyzers = new FailureAnalyzers(context);
|
||||
// ⑥
|
||||
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
|
||||
// ⑦
|
||||
refreshContext(context);
|
||||
// ⑧
|
||||
afterRefresh(context, applicationArguments);
|
||||
// ⑨
|
||||
listeners.finished(context, null);
|
||||
stopWatch.stop();
|
||||
return context;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
handleRunFailure(context, listeners, analyzers, ex);
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
① 通过 SpringFactoriesLoader 查找并加载所有的`SpringApplicationRunListeners`,通过调用 starting()方法通知所有的 SpringApplicationRunListeners:应用开始启动了。SpringApplicationRunListeners 其本质上就是一个事件发布者,它在 SpringBoot 应用启动的不同时间点发布不同应用事件类型(ApplicationEvent),如果有哪些事件监听者(ApplicationListener)对这些事件感兴趣,则可以接收并且处理。还记得初始化流程中,SpringApplication 加载了一系列 ApplicationListener 吗?这个启动流程中没有发现有发布事件的代码,其实都已经在 SpringApplicationRunListeners 这儿实现了。
|
||||
|
||||
简单的分析一下其实现流程,首先看下 SpringApplicationRunListener 的源码:
|
||||
|
||||
```
|
||||
public interface SpringApplicationRunListener {
|
||||
|
||||
// 运行run方法时立即调用此方法,可以用户非常早期的初始化工作
|
||||
void starting();
|
||||
|
||||
// Environment准备好后,并且ApplicationContext创建之前调用
|
||||
void environmentPrepared(ConfigurableEnvironment environment);
|
||||
|
||||
// ApplicationContext创建好后立即调用
|
||||
void contextPrepared(ConfigurableApplicationContext context);
|
||||
|
||||
// ApplicationContext加载完成,在refresh之前调用
|
||||
void contextLoaded(ConfigurableApplicationContext context);
|
||||
|
||||
// 当run方法结束之前调用
|
||||
void finished(ConfigurableApplicationContext context, Throwable exception);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
SpringApplicationRunListener 只有一个实现类:`EventPublishingRunListener`。① 处的代码只会获取到一个 EventPublishingRunListener 的实例,我们来看看 starting()方法的内容:
|
||||
|
||||
```
|
||||
public void starting() {
|
||||
// 发布一个ApplicationStartedEvent
|
||||
this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
|
||||
}
|
||||
```
|
||||
|
||||
顺着这个逻辑,你可以在 ② 处的`prepareEnvironment()`方法的源码中找到`listeners.environmentPrepared(environment);`即 SpringApplicationRunListener 接口的第二个方法,那不出你所料,`environmentPrepared()`又发布了另外一个事件`ApplicationEnvironmentPreparedEvent`。接下来会发生什么,就不用我多说了吧。
|
||||
|
||||
② 创建并配置当前应用将要使用的`Environment`,Environment 用于描述应用程序当前的运行环境,其抽象了两个方面的内容:配置文件(profile)和属性(properties),开发经验丰富的同学对这两个东西一定不会陌生:不同的环境(eg:生产环境、预发布环境)可以使用不同的配置文件,而属性则可以从配置文件、环境变量、命令行参数等来源获取。因此,当 Environment 准备好后,在整个应用的任何时候,都可以从 Environment 中获取资源。
|
||||
|
||||
总结起来,② 处的两句代码,主要完成以下几件事:
|
||||
|
||||
- 判断 Environment 是否存在,不存在就创建(如果是 web 项目就创建`StandardServletEnvironment`,否则创建`StandardEnvironment`)
|
||||
- 配置 Environment:配置 profile 以及 properties
|
||||
- 调用 SpringApplicationRunListener 的`environmentPrepared()`方法,通知事件监听者:应用的 Environment 已经准备好
|
||||
|
||||
③、SpringBoot 应用在启动时会输出这样的东西:
|
||||
|
||||
```
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v1.5.6.RELEASE)
|
||||
```
|
||||
|
||||
如果想把这个东西改成自己的涂鸦,你可以研究以下 Banner 的实现,这个任务就留给你们吧。
|
||||
|
||||
④、根据是否是 web 项目,来创建不同的 ApplicationContext 容器。
|
||||
|
||||
⑤、创建一系列`FailureAnalyzer`,创建流程依然是通过 SpringFactoriesLoader 获取到所有实现 FailureAnalyzer 接口的 class,然后在创建对应的实例。FailureAnalyzer 用于分析故障并提供相关诊断信息。
|
||||
|
||||
⑥、初始化 ApplicationContext,主要完成以下工作:
|
||||
|
||||
- 将准备好的 Environment 设置给 ApplicationContext
|
||||
- 遍历调用所有的 ApplicationContextInitializer 的`initialize()`方法来对已经创建好的 ApplicationContext 进行进一步的处理
|
||||
- 调用 SpringApplicationRunListener 的`contextPrepared()`方法,通知所有的监听者:ApplicationContext 已经准备完毕
|
||||
- 将所有的 bean 加载到容器中
|
||||
- 调用 SpringApplicationRunListener 的`contextLoaded()`方法,通知所有的监听者:ApplicationContext 已经装载完毕
|
||||
|
||||
⑦、调用 ApplicationContext 的`refresh()`方法,完成 IoC 容器可用的最后一道工序。从名字上理解为刷新容器,那何为刷新?就是插手容器的启动,联系一下第一小节的内容。那如何刷新呢?且看下面代码:
|
||||
|
||||
```
|
||||
// 摘自refresh()方法中一句代码
|
||||
invokeBeanFactoryPostProcessors(beanFactory);
|
||||
```
|
||||
|
||||
看看这个方法的实现:
|
||||
|
||||
```
|
||||
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
|
||||
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
|
||||
......
|
||||
}
|
||||
```
|
||||
|
||||
获取到所有的`BeanFactoryPostProcessor`来对容器做一些额外的操作。BeanFactoryPostProcessor 允许我们在容器实例化相应对象之前,对注册到容器的 BeanDefinition 所保存的信息做一些额外的操作。这里的 getBeanFactoryPostProcessors()方法可以获取到 3 个 Processor:
|
||||
|
||||
```
|
||||
ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
|
||||
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
|
||||
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
|
||||
```
|
||||
|
||||
不是有那么多 BeanFactoryPostProcessor 的实现类,为什么这儿只有这 3 个?因为在初始化流程获取到的各种 ApplicationContextInitializer 和 ApplicationListener 中,只有上文 3 个做了类似于如下操作:
|
||||
|
||||
```
|
||||
public void initialize(ConfigurableApplicationContext context) {
|
||||
context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
|
||||
}
|
||||
```
|
||||
|
||||
然后你就可以进入到`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`方法了,这个方法除了会遍历上面的 3 个 BeanFactoryPostProcessor 处理外,还会获取类型为`BeanDefinitionRegistryPostProcessor`的 bean:`org.springframework.context.annotation.internalConfigurationAnnotationProcessor`,对应的 Class 为`ConfigurationClassPostProcessor`。`ConfigurationClassPostProcessor`用于解析处理各种注解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。当处理`@import`注解的时候,就会调用<自动配置>这一小节中的`EnableAutoConfigurationImportSelector.selectImports()`来完成自动配置功能。其他的这里不再多讲,如果你有兴趣,可以查阅参考资料 6。
|
||||
|
||||
⑧、查找当前 context 中是否注册有 CommandLineRunner 和 ApplicationRunner,如果有则遍历执行它们。
|
||||
|
||||
⑨、执行所有 SpringApplicationRunListener 的 finished()方法。
|
||||
|
||||
这就是 Spring Boot 的整个启动流程,其核心就是在 Spring 容器初始化并启动的基础上加入各种扩展点,这些扩展点包括:ApplicationContextInitializer、ApplicationListener 以及各种 BeanFactoryPostProcessor 等等。你对整个流程的细节不必太过关注,甚至没弄明白也没有关系,你只要理解这些扩展点是在何时如何工作的,能让它们为你所用即可。
|
||||
|
||||
整个启动流程确实非常复杂,可以查询参考资料中的部分章节和内容,对照着源码,多看看,我想最终你都能弄清楚的。言而总之,Spring 才是核心,理解清楚 Spring 容器的启动流程,那 Spring Boot 启动流程就不在话下了。
|
||||
|
||||
## 参考资料
|
||||
|
||||
[1][王福强 著;springboot 揭秘:快速构建微服务体系; 机械工业出版社, 2016](https://link.jianshu.com/?t=http%3A%2F%2Funion-click.jd.com%2Fjdc%3Fd%3D4jESQ9)
|
||||
[2][王福强 著;spring 揭秘; 人民邮件出版社, 2009](https://link.jianshu.com/?t=http%3A%2F%2Funion-click.jd.com%2Fjdc%3Fd%3DyzfgeF)
|
||||
[3][craig walls 著;丁雪丰 译;spring boot 实战;中国工信出版集团 人民邮电出版社,2016](https://link.jianshu.com/?t=http%3A%2F%2Funion-click.jd.com%2Fjdc%3Fd%3DAQ6oHO)
|
||||
[4][深入探讨 java 类加载器](https://link.jianshu.com/?t=https%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Fjava%2Fj-lo-classloader%2F) : [www.ibm.com/developerwo…](https://link.jianshu.com/?t=https%3A%2F%2Fwww.ibm.com%2Fdeveloperworks%2Fcn%2Fjava%2Fj-lo-classloader%2F)
|
||||
[5][spring boot 实战:自动配置原理分析](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49559951) : [blog.csdn.net/liaokailin/…](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49559951)
|
||||
[6][spring boot实战:spring boot bean加载源码分析](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49107209): [blog.csdn.net/liaokailin/…](https://link.jianshu.com/?t=http%3A%2F%2Fblog.csdn.net%2Fliaokailin%2Farticle%2Fdetails%2F49107209)
|
|
@ -0,0 +1,285 @@
|
|||
---
|
||||
title: SpringBoot 基本原理
|
||||
date: 2020-08-12 07:01:26
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring综合
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/dbf521/
|
||||
---
|
||||
|
||||
# SpringBoot 基本原理
|
||||
|
||||
SpringBoot 为我们做的自动配置,确实方便快捷,但一直搞不明白它的内部启动原理,这次就来一步步解开 SpringBoot 的神秘面纱,让它不再神秘。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-ebcb376f96103703.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
|
||||
|
||||
---
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
从上面代码可以看出,**Annotation 定义(@SpringBootApplication)和类定义(SpringApplication.run)**最为耀眼,所以要揭开 SpringBoot 的神秘面纱,我们要从这两位开始就可以了。
|
||||
|
||||
## SpringBootApplication 背后的秘密
|
||||
|
||||
```kotlin
|
||||
@Target(ElementType.TYPE) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
|
||||
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
|
||||
@Documented // 表明这个注解应该被javadoc记录
|
||||
@Inherited // 子类可以继承该注解
|
||||
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
|
||||
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
|
||||
@ComponentScan(excludeFilters = { // 扫描路径设置(具体使用待确认)
|
||||
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
||||
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
||||
public @interface SpringBootApplication {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
虽然定义使用了多个 Annotation 进行了原信息标注,但实际上重要的只有三个 Annotation:
|
||||
|
||||
**@Configuration**(@SpringBootConfiguration 点开查看发现里面还是应用了@Configuration)
|
||||
**@EnableAutoConfiguration
|
||||
@ComponentScan**
|
||||
所以,如果我们使用如下的 SpringBoot 启动类,整个 SpringBoot 应用依然可以与之前的启动类功能对等:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每次写这 3 个比较累,所以写一个@SpringBootApplication 方便点。接下来分别介绍这 3 个 Annotation。
|
||||
|
||||
## @Configuration
|
||||
|
||||
这里的@Configuration 对我们来说不陌生,**它就是 JavaConfig 形式的 Spring Ioc 容器的配置类使用的那个@Configuration**,SpringBoot 社区推荐使用基于 JavaConfig 的配置形式,所以,这里的启动类标注了@Configuration 之后,本身其实也是一个 IoC 容器的配置类。
|
||||
举几个简单例子回顾下,XML 跟 config 配置方式的区别:
|
||||
|
||||
表达形式层面
|
||||
基于 XML 配置的方式是这样:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
|
||||
default-lazy-init="true">
|
||||
<!--bean定义-->
|
||||
</beans>
|
||||
```
|
||||
|
||||
而基于 JavaConfig 的配置方式是这样:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class MockConfiguration{
|
||||
//bean定义
|
||||
}
|
||||
```
|
||||
|
||||
**任何一个标注了@Configuration 的 Java 类定义都是一个 JavaConfig 配置类。**
|
||||
|
||||
注册 bean 定义层面
|
||||
基于 XML 的配置形式是这样:
|
||||
|
||||
```csharp
|
||||
<bean id="mockService" class="..MockServiceImpl">
|
||||
...
|
||||
</bean>
|
||||
```
|
||||
|
||||
而基于 JavaConfig 的配置形式是这样的:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class MockConfiguration{
|
||||
@Bean
|
||||
public MockService mockService(){
|
||||
return new MockServiceImpl();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**任何一个标注了@Bean 的方法,其返回值将作为一个 bean 定义注册到 Spring 的 IoC 容器,方法名将默认成该 bean 定义的 id。**
|
||||
|
||||
表达依赖注入关系层面
|
||||
为了表达 bean 与 bean 之间的依赖关系,在 XML 形式中一般是这样:
|
||||
|
||||
```jsx
|
||||
<bean id="mockService" class="..MockServiceImpl">
|
||||
<propery name ="dependencyService" ref="dependencyService" />
|
||||
</bean>
|
||||
|
||||
<bean id="dependencyService" class="DependencyServiceImpl"></bean>
|
||||
```
|
||||
|
||||
而基于 JavaConfig 的配置形式是这样的:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class MockConfiguration{
|
||||
@Bean
|
||||
public MockService mockService(){
|
||||
return new MockServiceImpl(dependencyService());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DependencyService dependencyService(){
|
||||
return new DependencyServiceImpl();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**如果一个 bean 的定义依赖其他 bean,则直接调用对应的 JavaConfig 类中依赖 bean 的创建方法就可以了。**
|
||||
|
||||
## @ComponentScan
|
||||
|
||||
**@ComponentScan 这个注解在 Spring 中很重要,它对应 XML 配置中的元素,@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件(比如@Component 和@Repository 等)或者 bean 定义,最终将这些 bean 定义加载到 IoC 容器中。**
|
||||
|
||||
我们可以通过 basePackages 等属性来细粒度的定制@ComponentScan 自动扫描的范围,如果不指定,则默认 Spring 框架实现会从声明@ComponentScan 所在类的 package 进行扫描。
|
||||
|
||||
> 注:所以 SpringBoot 的启动类最好是放在 root package 下,因为默认不指定 basePackages。
|
||||
|
||||
## @EnableAutoConfiguration
|
||||
|
||||
个人感觉**@EnableAutoConfiguration 这个 Annotation 最为重要**,所以放在最后来解读,大家是否还记得 Spring 框架提供的各种名字为@Enable 开头的 Annotation 定义?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport 等,@EnableAutoConfiguration 的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import 的支持,收集和注册特定场景相关的 bean 定义。
|
||||
|
||||
**@EnableScheduling**是通过@Import 将 Spring 调度框架相关的 bean 定义都加载到 IoC 容器。
|
||||
**@EnableMBeanExport**是通过@Import 将 JMX 相关的 bean 定义加载到 IoC 容器。
|
||||
而**@EnableAutoConfiguration**也是借助@Import 的帮助,将所有符合自动配置条件的 bean 定义加载到 IoC 容器,仅此而已!
|
||||
|
||||
@EnableAutoConfiguration 作为一个复合 Annotation,其自身定义关键信息如下:
|
||||
|
||||
```kotlin
|
||||
@SuppressWarnings("deprecation")
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@AutoConfigurationPackage
|
||||
@Import(EnableAutoConfigurationImportSelector.class)
|
||||
public @interface EnableAutoConfiguration {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
两个比较重要的注解:
|
||||
|
||||
**@AutoConfigurationPackage:自动配置包**
|
||||
|
||||
**@Import: 导入自动配置的组件**
|
||||
|
||||
#### AutoConfigurationPackage 注解:
|
||||
|
||||
```java
|
||||
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
register(registry, new PackageImport(metadata).getPackageName());
|
||||
}
|
||||
```
|
||||
|
||||
它其实是注册了一个 Bean 的定义。
|
||||
|
||||
new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的 同级以及子级 的包组件。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-439283a70a24c7a0.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/281/format/webp)
|
||||
|
||||
以上图为例,DemoApplication 是和 demo 包同级,但是 demo2 这个类是 DemoApplication 的父级,和 example 包同级
|
||||
|
||||
也就是说,DemoApplication 启动加载的 Bean 中,并不会加载 demo2,这也就是为什么,我们要把 DemoApplication 放在项目的最高级中。
|
||||
|
||||
#### Import(AutoConfigurationImportSelector.class)注解:
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-1c448a69c41dc35c.png?imageMogr2/auto-orient/strip|imageView2/2/w/877/format/webp)
|
||||
|
||||
可以从图中看出 AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector
|
||||
|
||||
ImportSelector 有一个方法为:selectImports。
|
||||
|
||||
```dart
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata annotationMetadata) {
|
||||
if (!isEnabled(annotationMetadata)) {
|
||||
return NO_IMPORTS;
|
||||
}
|
||||
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
|
||||
.loadMetadata(this.beanClassLoader);
|
||||
AnnotationAttributes attributes = getAttributes(annotationMetadata);
|
||||
List<String> configurations = getCandidateConfigurations(annotationMetadata,
|
||||
attributes);
|
||||
configurations = removeDuplicates(configurations);
|
||||
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
|
||||
checkExcludedClasses(configurations, exclusions);
|
||||
configurations.removeAll(exclusions);
|
||||
configurations = filter(configurations, autoConfigurationMetadata);
|
||||
fireAutoConfigurationImportEvents(configurations, exclusions);
|
||||
return StringUtils.toStringArray(configurations);
|
||||
}
|
||||
```
|
||||
|
||||
可以看到第九行,它其实是去加载 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。这个外部文件,有很多自动配置的类。如下:
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-250f3320c15e5c99.jpg?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
|
||||
|
||||
image
|
||||
|
||||
其中,最关键的要属**@Import(EnableAutoConfigurationImportSelector.class)**,借助**EnableAutoConfigurationImportSelector**,**@EnableAutoConfiguration**可以帮助 SpringBoot 应用将所有符合条件的**@Configuration**配置都加载到当前 SpringBoot 创建并使用的 IoC 容器。就像一只“八爪鱼”一样。
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-6f3a835755ee7710.png?imageMogr2/auto-orient/strip|imageView2/2/w/640/format/webp)
|
||||
|
||||
### 自动配置幕后英雄:SpringFactoriesLoader 详解
|
||||
|
||||
借助于 Spring 框架原有的一个工具类:SpringFactoriesLoader 的支持,@EnableAutoConfiguration 可以智能的自动配置功效才得以大功告成!
|
||||
|
||||
SpringFactoriesLoader 属于 Spring 框架私有的一种扩展方案,其主要功能就是从指定的配置文件 META-INF/spring.factories 加载配置。
|
||||
|
||||
```php
|
||||
public abstract class SpringFactoriesLoader {
|
||||
//...
|
||||
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
|
||||
....
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
配合**@EnableAutoConfiguration**使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration 的完整类名 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为查找的 Key,获取对应的一组**@Configuration**类
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-fcdfcb56828a015a?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
|
||||
|
||||
上图就是从 SpringBoot 的 autoconfigure 依赖包中的 META-INF/spring.factories 配置文件中摘录的一段内容,可以很好地说明问题。
|
||||
|
||||
所以,@EnableAutoConfiguration 自动配置的魔法骑士就变成了:**从 classpath 中搜寻所有的 META-INF/spring.factories 配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration 的 JavaConfig 形式的 IoC 容器配置类,然后汇总为一个并加载到 IoC 容器。**
|
||||
|
||||
![img](https:////upload-images.jianshu.io/upload_images/6430208-10850d62d44c95ce.png?imageMogr2/auto-orient/strip|imageView2/2/w/822/format/webp)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [一文搞懂 springboot 启动原理](https://www.jianshu.com/p/943650ab7dfd)
|
|
@ -1,3 +1,19 @@
|
|||
---
|
||||
title: Spring 常见面试题
|
||||
date: 2018-08-02 17:33:32
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring综合
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- 面试
|
||||
permalink: /pages/db33b0/
|
||||
---
|
||||
|
||||
# Spring 常见面试题
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: Spring 综述
|
||||
date: 2020-02-26 23:48:06
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring综合
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/9e0b67/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring 综述
|
||||
|
||||
## 📖 内容
|
||||
|
||||
- [Spring 概述](01.Spring概述.md)
|
||||
- [SpringBoot 知识图谱](21.SpringBoot知识图谱.md)
|
||||
- [SpringBoot 基本原理](22.SpringBoot基本原理.md)
|
||||
- [Spring 常见面试题](99.Spring常见面试题.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
- [跟我学 Spring3](http://jinnianshilongnian.iteye.com/blog/1482071)
|
||||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -1,36 +1,24 @@
|
|||
# Spring 依赖注入(IoC)
|
||||
---
|
||||
title: Spring 依赖注入
|
||||
date: 2020-08-30 16:06:10
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- IOC
|
||||
permalink: /pages/915530/
|
||||
---
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
# Spring 依赖注入
|
||||
|
||||
- [1. IoC 概念简介](#1-ioc-概念简介)
|
||||
- [1.1. IoC 是什么](#11-ioc-是什么)
|
||||
- [1.2. IoC 能做什么](#12-ioc-能做什么)
|
||||
- [1.3. IoC 和 DI](#13-ioc-和-di)
|
||||
- [1.4. IoC 容器](#14-ioc-容器)
|
||||
- [1.5. Bean](#15-bean)
|
||||
- [2. Spring IoC](#2-spring-ioc)
|
||||
- [2.1. 核心接口](#21-核心接口)
|
||||
- [2.2. IoC 容器工作步骤](#22-ioc-容器工作步骤)
|
||||
- [2.3. 配置元数据](#23-配置元数据)
|
||||
- [2.4. Spring Bean 概述](#24-spring-bean-概述)
|
||||
- [2.5. 依赖注入模式](#25-依赖注入模式)
|
||||
- [2.6. 依赖注入类型](#26-依赖注入类型)
|
||||
- [2.7. 被注入的数据类型](#27-被注入的数据类型)
|
||||
- [3. IoC 依赖查找](#3-ioc-依赖查找)
|
||||
- [4. IoC 依赖注入](#4-ioc-依赖注入)
|
||||
- [5. IoC 容器配置](#5-ioc-容器配置)
|
||||
- [5.1. Xml 配置](#51-xml-配置)
|
||||
- [5.2. 注解配置](#52-注解配置)
|
||||
- [5.3. Java 配置](#53-java-配置)
|
||||
- [6. 最佳实践](#6-最佳实践)
|
||||
- [6.1. singleton 的 Bean 如何注入 prototype 的 Bean](#61-singleton-的-bean-如何注入-prototype-的-bean)
|
||||
- [7. 参考资料](#7-参考资料)
|
||||
## IoC 概念简介
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## 1. IoC 概念简介
|
||||
|
||||
### 1.1. IoC 是什么
|
||||
### IoC 是什么
|
||||
|
||||
> **IoC,是 Inversion of Control 的缩写,即控制反转。**
|
||||
>
|
||||
|
@ -56,7 +44,7 @@ IoC 不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意
|
|||
|
||||
图 2-2 有 IoC/DI 容器后程序结构示意图
|
||||
|
||||
### 1.2. IoC 能做什么
|
||||
### IoC 能做什么
|
||||
|
||||
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
|
||||
|
||||
|
@ -64,13 +52,13 @@ IoC 不是一种技术,只是一种思想,一个重要的面向对象编程
|
|||
|
||||
IoC 很好的体现了面向对象设计法则之一—— **好莱坞法则:“别找我们,我们找你”**;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
|
||||
|
||||
### 1.3. IoC 和 DI
|
||||
### IoC 和 DI
|
||||
|
||||
其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IoC 而言,“依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。
|
||||
|
||||
> 注:如果想要更加深入的了解 IoC 和 DI,请参考大师级人物 Martin Fowler 的一篇经典文章 [Inversion of Control Containers and the Dependency Injection pattern](http://www.martinfowler.com/articles/injection.html) 。
|
||||
|
||||
### 1.4. IoC 容器
|
||||
### IoC 容器
|
||||
|
||||
IoC 容器就是具有依赖注入功能的容器。IoC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中 new 相关的对象,应用程序由 IoC 容器进行组装。在 Spring 中 BeanFactory 是 IoC 容器的实际代表者。
|
||||
|
||||
|
@ -78,15 +66,15 @@ Spring IoC 容器如何知道哪些是它管理的对象呢?这就需要配置
|
|||
|
||||
那 Spring IoC 容器管理的对象叫什么呢?
|
||||
|
||||
### 1.5. Bean
|
||||
### Bean
|
||||
|
||||
> **JavaBean** 是一种 JAVA 语言写成的可重用组件。为写成 JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean 对外部通过提供 getter / setter 方法来访问其成员。
|
||||
|
||||
由 IoC 容器管理的那些组成你应用程序的对象我们就叫它 Bean。Bean 就是由 Spring 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。那 IoC 怎样确定如何实例化 Bean、管理 Bean 之间的依赖关系以及管理 Bean 呢?这就需要配置元数据,在 Spring 中由 BeanDefinition 代表,后边会详细介绍,配置元数据指定如何实例化 Bean、如何组装 Bean 等。
|
||||
|
||||
## 2. Spring IoC
|
||||
## Spring IoC
|
||||
|
||||
### 2.1. 核心接口
|
||||
### 核心接口
|
||||
|
||||
`org.springframework.beans` 和 `org.springframework.context` 是 IoC 容器的基础。
|
||||
|
||||
|
@ -111,7 +99,7 @@ BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath.xml");
|
|||
BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.xml");
|
||||
```
|
||||
|
||||
### 2.2. IoC 容器工作步骤
|
||||
### IoC 容器工作步骤
|
||||
|
||||
使用 IoC 容器可分为三步骤:
|
||||
|
||||
|
@ -121,7 +109,7 @@ BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.
|
|||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20200723102456.png)
|
||||
|
||||
### 2.3. 配置元数据
|
||||
### 配置元数据
|
||||
|
||||
**元数据(Metadata)**又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息。
|
||||
|
||||
|
@ -137,7 +125,7 @@ BeanFactory beanFactory = new FileSystemXmlApplicationContext("fileSystemConfig.
|
|||
|
||||
为了使用这个新特性,需要用到 `@Configuration` 、`@Bean` 、`@Import` 和 `@DependsOn` 注解。
|
||||
|
||||
### 2.4. Spring Bean 概述
|
||||
### Spring Bean 概述
|
||||
|
||||
一个 Spring 容器管理一个或多个 bean。这些 bean 根据你配置的元数据(比如 xml 形式)来创建。
|
||||
|
||||
|
@ -165,7 +153,7 @@ Spring IoC 容器本身,并不能识别你配置的元数据。为此,要将
|
|||
<bean id="exampleBean" class="examples.ExampleBean"/>
|
||||
```
|
||||
|
||||
### 2.5. 依赖注入模式
|
||||
### 依赖注入模式
|
||||
|
||||
依赖注入模式可以分为手动模式和自动模式。
|
||||
|
||||
|
@ -192,7 +180,7 @@ Spring IoC 容器本身,并不能识别你配置的元数据。为此,要将
|
|||
|
||||
> 限制和不足参考:[Limitations and Disadvantages of Autowiring 小节](https://docs.spring.io/spring/docs/5.2.2.RELEASE/spring-frameworkreference/core.html#beans-autowired-exceptions)
|
||||
|
||||
### 2.6. 依赖注入类型
|
||||
### 依赖注入类型
|
||||
|
||||
| 依赖注入类型 | 配置元数据举例 |
|
||||
| ------------ | -------------------------------------------------- |
|
||||
|
@ -267,7 +255,7 @@ Aware 系列接口回调
|
|||
- 便利性:字段注入
|
||||
- 声明类:方法注入
|
||||
|
||||
### 2.7. 被注入的数据类型
|
||||
### 被注入的数据类型
|
||||
|
||||
基础类型
|
||||
|
||||
|
@ -283,7 +271,7 @@ Aware 系列接口回调
|
|||
- Collection:List、Set(SortedSet、NavigableSet、EnumSet)
|
||||
- Map:Properties
|
||||
|
||||
## 3. IoC 依赖查找
|
||||
## IoC 依赖查找
|
||||
|
||||
**依赖查找是主动或手动的依赖查找方式,通常需要依赖容器或标准 API 实现**。
|
||||
|
||||
|
@ -360,7 +348,121 @@ public class BeanFactoryDemo {
|
|||
}
|
||||
```
|
||||
|
||||
## 4. IoC 依赖注入
|
||||
### 单一类型依赖查找
|
||||
|
||||
单一类型依赖查找接口- BeanFactory
|
||||
|
||||
- 根据 Bean 名称查找
|
||||
- getBean(String)
|
||||
- Spring 2.5 覆盖默认参数:getBean(String,Object...)
|
||||
- 根据 Bean 类型查找
|
||||
- Bean 实时查找
|
||||
- Spring 3.0 getBean(Class)
|
||||
- Spring 4.1 覆盖默认参数:getBean(Class,Object...)
|
||||
- Spring 5.1 Bean 延迟查找
|
||||
- getBeanProvider(Class)
|
||||
- getBeanProvider(ResolvableType)
|
||||
- 根据 Bean 名称 + 类型查找:getBean(String,Class)
|
||||
|
||||
### 集合类型依赖查找
|
||||
|
||||
集合类型依赖查找接口- ListableBeanFactory
|
||||
|
||||
- 根据 Bean 类型查找
|
||||
- 获取同类型 Bean 名称列表
|
||||
- getBeanNamesForType(Class)
|
||||
- Spring 4.2 getBeanNamesForType(ResolvableType)
|
||||
- 获取同类型 Bean 实例列表
|
||||
- getBeansOfType(Class) 以及重载方法
|
||||
- 通过注解类型查找
|
||||
|
||||
- Spring 3.0 获取标注类型 Bean 名称列表
|
||||
|
||||
- getBeanNamesForAnnotation(Class<? extends Annotation>)
|
||||
|
||||
- Spring 3.0 获取标注类型 Bean 实例列表
|
||||
|
||||
- getBeansWithAnnotation(Class<? extends Annotation>)
|
||||
|
||||
- Spring 3.0 获取指定名称+ 标注类型 Bean 实例
|
||||
|
||||
- findAnnotationOnBean(String,Class<? extends Annotation>)
|
||||
|
||||
### 层次性依赖查找
|
||||
|
||||
层次性依赖查找接口- HierarchicalBeanFactory
|
||||
|
||||
- 双亲 BeanFactory:getParentBeanFactory()
|
||||
- 层次性查找
|
||||
- 根据 Bean 名称查找
|
||||
- 基于 containsLocalBean 方法实现
|
||||
- 根据 Bean 类型查找实例列表
|
||||
- 单一类型:BeanFactoryUtils#beanOfType
|
||||
- 集合类型:BeanFactoryUtils#beansOfTypeIncludingAncestors
|
||||
- 根据 Java 注解查找名称列表
|
||||
- BeanFactoryUtils#beanNamesForTypeIncludingAncestors
|
||||
|
||||
### 延迟依赖查找
|
||||
|
||||
Bean 延迟依赖查找接口
|
||||
|
||||
- org.springframework.beans.factory.ObjectFactory
|
||||
- org.springframework.beans.factory.ObjectProvider(Spring 5 对 Java 8 特性扩展)
|
||||
- 函数式接口
|
||||
- getIfAvailable(Supplier)
|
||||
- ifAvailable(Consumer)
|
||||
- Stream 扩展- stream()
|
||||
|
||||
### 安全依赖查找
|
||||
|
||||
| 依赖查找类型 | 代表实现 | 是否安全 |
|
||||
| ------------ | ---------------------------------- | -------- |
|
||||
| 单一类型查找 | BeanFactory#getBean | 否 |
|
||||
| | ObjectFactory#getObject | 否 |
|
||||
| | ObjectProvider#getIfAvailable | 是 |
|
||||
| | | |
|
||||
| 集合类型查找 | ListableBeanFactory#getBeansOfType | 是 |
|
||||
| | ObjectProvider#stream | 是 |
|
||||
|
||||
注意:层次性依赖查找的安全性取决于其扩展的单一或集合类型的 BeanFactory 接口
|
||||
|
||||
### 内建可查找的依赖
|
||||
|
||||
AbstractApplicationContext 内建可查找的依赖
|
||||
|
||||
| Bean | 名称 Bean | 实例使用场景 |
|
||||
| --------------------------- | -------------------------------- | ----------------------- |
|
||||
| environment | Environment 对象 | 外部化配置以及 Profiles |
|
||||
| systemProperties | java.util.Properties 对象 | Java 系统属性 |
|
||||
| systemEnvironment | java.util.Map 对象 | 操作系统环境变量 |
|
||||
| messageSource | MessageSource 对象 | 国际化文案 |
|
||||
| lifecycleProcessor | LifecycleProcessor 对象 | Lifecycle Bean 处理器 |
|
||||
| applicationEventMulticaster | ApplicationEventMulticaster 对象 | Spring 事件广播器 |
|
||||
|
||||
注解驱动 Spring 应用上下文内建可查找的依赖(部分)
|
||||
|
||||
| Bean 名称 | Bean 实例 | 使用场景 |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------- |
|
||||
| org.springframework.context.annotation.internalConfigurationAnnotationProcessor | ConfigurationClassPostProcessor 对象 | 处理 Spring 配置类 |
|
||||
| org.springframework.context.annotation.internalAutowiredAnnotationProcessor | AutowiredAnnotationBeanPostProcessor 对象 | 处理@Autowired 以及@Value 注解 |
|
||||
| org.springframework.context.annotation.internalCommonAnnotationProcessor | CommonAnnotationBeanPostProcessor 对象 | (条件激活)处理 JSR-250 注解,如@PostConstruct 等 |
|
||||
| org.springframework.context.event.internalEventListenerProcessor | EventListenerMethodProcessor 对象 | 处理标注@EventListener 的 Spring 事件监听方法 |
|
||||
| org.springframework.context.event.internalEventListenerFactory | DefaultEventListenerFactory 对象 | @EventListener 事件监听方法适配为 ApplicationListener |
|
||||
| org.springframework.context.annotation.internalPersistenceAnnotationProcessor | PersistenceAnnotationBeanPostProcessor 对象 | (条件激活)处理 JPA 注解场景 |
|
||||
|
||||
### 依赖查找中的经典异常
|
||||
|
||||
BeansException 子类型
|
||||
|
||||
| 异常类型 | 触发条件(举例) | 场景举例 |
|
||||
| ------------------------------- | ------------------------------------------ | ------------------------------------------ |
|
||||
| NoSuchBeanDefinitionException | 当查找 Bean 不存在于 IoC 容器时 | BeanFactory#getBeanObjectFactory#getObject |
|
||||
| NoUniqueBeanDefinitionException | 类型依赖查找时,IoC 容器存在多个 Bean 实例 | BeanFactory#getBean(Class) |
|
||||
| BeanInstantiationException | 当 Bean 所对应的类型非具体类时 | BeanFactory#getBean |
|
||||
| BeanCreationException | 当 Bean 初始化过程中 | Bean 初始化方法执行异常时 |
|
||||
| BeanDefinitionStoreException | 当 BeanDefinition 配置元信息非法时 | XML 配置资源无法打开时 |
|
||||
|
||||
## IoC 依赖注入
|
||||
|
||||
DI,是 Dependency Injection 的缩写,即依赖注入。依赖注入是 IoC 的最常见形式。依赖注入是手动或自动绑定的方式,无需依赖特定的容器或 API。
|
||||
|
||||
|
@ -385,7 +487,7 @@ IoC 依赖注入 API
|
|||
- 实时注入
|
||||
- 延迟注入
|
||||
|
||||
## 5. IoC 容器配置
|
||||
## IoC 容器配置
|
||||
|
||||
IoC 容器的配置有三种方式:
|
||||
|
||||
|
@ -399,7 +501,7 @@ IoC 容器的配置有三种方式:
|
|||
|
||||
**本文,将对 Java 配置 IoC 容器做详细的介绍。**
|
||||
|
||||
### 5.1. Xml 配置
|
||||
### Xml 配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
@ -460,7 +562,7 @@ PetStoreService service = context.getBean("petStore", PetStoreService.class);
|
|||
List<String> userList = service.getUsernameList();
|
||||
```
|
||||
|
||||
### 5.2. 注解配置
|
||||
### 注解配置
|
||||
|
||||
Spring2.5 引入了注解。
|
||||
于是,一个问题产生了:**使用注解方式注入 JavaBean 是不是一定完爆 xml 方式?**
|
||||
|
@ -836,7 +938,7 @@ public class AnnotationInject {
|
|||
}
|
||||
```
|
||||
|
||||
### 5.3. Java 配置
|
||||
### Java 配置
|
||||
|
||||
基于 Java 配置 Spring IoC 容器,实际上是**Spring 允许用户定义一个类,在这个类中去管理 IoC 容器的配置**。
|
||||
|
||||
|
@ -928,9 +1030,9 @@ public class AppConfig {
|
|||
|
||||
用 `AnnotationConfigApplicationContext` 实例化 IoC 容器。
|
||||
|
||||
## 6. 最佳实践
|
||||
## 最佳实践
|
||||
|
||||
### 6.1. singleton 的 Bean 如何注入 prototype 的 Bean
|
||||
### singleton 的 Bean 如何注入 prototype 的 Bean
|
||||
|
||||
Spring 创建的 Bean 默认是单例的,但当 Bean 遇到继承的时候,可能会忽略这一点。
|
||||
|
||||
|
@ -1023,6 +1125,6 @@ Bean 默认是单例的,所以单例的 Controller 注入的 Service 也是一
|
|||
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProx)
|
||||
```
|
||||
|
||||
## 7. 参考资料
|
||||
## 参考资料
|
||||
|
||||
- [Spring 官方文档之 Core Technologies](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans)
|
|
@ -1,33 +1,28 @@
|
|||
---
|
||||
title: Spring生命周期
|
||||
date: 2021-12-10 19:15:42
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Bean
|
||||
permalink: /pages/68097d/
|
||||
---
|
||||
|
||||
# Spring Bean 生命周期
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [1. Spring Bean 简介](#1-spring-bean-简介)
|
||||
- [1.1. BeanDefinition](#11-beandefinition)
|
||||
- [1.2. 命名 Spring Bean](#12-命名-spring-bean)
|
||||
- [2. Bean 生命周期流程](#2-bean-生命周期流程)
|
||||
- [3. Spring Bean 注册](#3-spring-bean-注册)
|
||||
- [3.1. XML 配置元信息](#31-xml-配置元信息)
|
||||
- [3.2. 注解配置元信息](#32-注解配置元信息)
|
||||
- [3.3. Java API 配置元信息](#33-java-api-配置元信息)
|
||||
- [4. Spring Bean 实例化](#4-spring-bean-实例化)
|
||||
- [5. Spring Bean 初始化和销毁](#5-spring-bean-初始化和销毁)
|
||||
- [5.1. `@Bean` 的 initMethod 和 destroyMethod](#51-bean-的-initmethod-和-destroymethod)
|
||||
- [5.2. `InitializingBean` 和 `DisposableBean`](#52-initializingbean-和-disposablebean)
|
||||
- [5.3. `@PostConstruct` 和 `@PreDestroy`](#53-postconstruct-和-predestroy)
|
||||
- [5.4. `BeanPostProcessor`](#54-beanpostprocessor)
|
||||
- [6. Spring Bean 垃圾回收](#6-spring-bean-垃圾回收)
|
||||
- [7. 参考资料](#7-参考资料)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## 1. Spring Bean 简介
|
||||
## Spring Bean 简介
|
||||
|
||||
如何注册一个 Spring Bean?
|
||||
|
||||
通过 BeanDefinition 和外部单体对象来注册
|
||||
|
||||
### 1.1. BeanDefinition
|
||||
### BeanDefinition
|
||||
|
||||
#### 什么是 BeanDefinition
|
||||
|
||||
|
@ -88,7 +83,7 @@ propertyValues
|
|||
genericBeanDefinition.setPropertyValues(propertyValues);
|
||||
```
|
||||
|
||||
### 1.2. 命名 Spring Bean
|
||||
### 命名 Spring Bean
|
||||
|
||||
#### Spring Bean 命名规则
|
||||
|
||||
|
@ -130,7 +125,7 @@ Bean 别名(Alias)的作用:
|
|||
- `<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>`
|
||||
- `<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>`
|
||||
|
||||
## 2. Bean 生命周期流程
|
||||
## Bean 生命周期流程
|
||||
|
||||
![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20211201102734.png)
|
||||
|
||||
|
@ -152,21 +147,21 @@ Bean 别名(Alias)的作用:
|
|||
9. 经过以上的工作后,Bean 将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
|
||||
10. 如果 Bean 实现了 `DispostbleBean` 接口,Spring 将调用它的 `destory` 方法,作用与在配置文件中对 Bean 使用 `destory-method` 属性的作用一样,都是在 Bean 实例销毁前执行的方法。
|
||||
|
||||
## 3. Spring Bean 注册
|
||||
## Spring Bean 注册
|
||||
|
||||
注册 Spring Bean 实际上是将 `BeanDefinition` 注册到 IoC 容器中。
|
||||
|
||||
### 3.1. XML 配置元信息
|
||||
### XML 配置元信息
|
||||
|
||||
Spring 的传统配置方式。在 `<bean>` 标签中配置元数据内容。
|
||||
|
||||
缺点是当 JavaBean 过多时,产生的配置文件足以让你眼花缭乱。
|
||||
|
||||
### 3.2. 注解配置元信息
|
||||
### 注解配置元信息
|
||||
|
||||
使用 `@Bean`、`@Component`、`@Import` 注解注册 Spring Bean。
|
||||
|
||||
### 3.3. Java API 配置元信息
|
||||
### Java API 配置元信息
|
||||
|
||||
- 命名方式:`BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)`
|
||||
- 非命名方式:`BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be`
|
||||
|
@ -223,7 +218,7 @@ public class AnnotationComponentScan {
|
|||
}
|
||||
```
|
||||
|
||||
## 4. Spring Bean 实例化
|
||||
## Spring Bean 实例化
|
||||
|
||||
Spring Bean 实例化方式:
|
||||
|
||||
|
@ -237,7 +232,7 @@ Spring Bean 实例化方式:
|
|||
- 通过 `AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)`
|
||||
- 通过 `BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)`
|
||||
|
||||
## 5. Spring Bean 初始化和销毁
|
||||
## Spring Bean 初始化和销毁
|
||||
|
||||
Spring Bean 初始化和销毁的方式有以下几种:
|
||||
|
||||
|
@ -250,7 +245,7 @@ Spring Bean 初始化和销毁的方式有以下几种:
|
|||
|
||||
注意:如果同时存在,执行顺序会按照序列执行。
|
||||
|
||||
### 5.1. `@Bean` 的 initMethod 和 destroyMethod
|
||||
### `@Bean` 的 initMethod 和 destroyMethod
|
||||
|
||||
(1)定义 Bean 实例
|
||||
|
||||
|
@ -307,10 +302,10 @@ AnnotationBeanDemo 示例结束
|
|||
[Pojo 销毁方法]
|
||||
```
|
||||
|
||||
### 5.2. `InitializingBean` 和 `DisposableBean`
|
||||
### `InitializingBean` 和 `DisposableBean`
|
||||
|
||||
- `InitializingBean ` 接口包含一个 `afterPropertiesSet` 方法,可以通过实现该接口,然后在这个方法中编写初始化逻辑。
|
||||
- `DisposableBean `接口包含一个 `destory` 方法,可以通过实现该接口,然后在这个方法中编写销毁逻辑。
|
||||
- `InitializingBean` 接口包含一个 `afterPropertiesSet` 方法,可以通过实现该接口,然后在这个方法中编写初始化逻辑。
|
||||
- `DisposableBean`接口包含一个 `destory` 方法,可以通过实现该接口,然后在这个方法中编写销毁逻辑。
|
||||
|
||||
(1)定义 Bean 实例
|
||||
|
||||
|
@ -367,11 +362,11 @@ AnnotationBeanDemo 示例结束
|
|||
[Pojo2 销毁方法]
|
||||
```
|
||||
|
||||
### 5.3. `@PostConstruct` 和 `@PreDestroy`
|
||||
### `@PostConstruct` 和 `@PreDestroy`
|
||||
|
||||
可以使用 `@PostConstruct` 和 `@PreDestroy` 注解修饰方法来指定相应的初始化和销毁方法。
|
||||
|
||||
### 5.4. `BeanPostProcessor`
|
||||
### `BeanPostProcessor`
|
||||
|
||||
Spring 提供了一个 `BeanPostProcessor` 接口,提供了两个方法 `postProcessBeforeInitialization` 和 `postProcessAfterInitialization`。其中`postProcessBeforeInitialization` 在组件的初始化方法调用之前执行,`postProcessAfterInitialization` 在组件的初始化方法调用之后执行。它们都包含两个入参:
|
||||
|
||||
|
@ -441,7 +436,7 @@ BeanPostProcessorDemo 示例结束
|
|||
[Pojo 销毁方法]
|
||||
```
|
||||
|
||||
## 6. Spring Bean 垃圾回收
|
||||
## Spring Bean 垃圾回收
|
||||
|
||||
Spring Bean 垃圾回收步骤:
|
||||
|
||||
|
@ -449,6 +444,6 @@ Spring Bean 垃圾回收步骤:
|
|||
2. 执行 GC
|
||||
3. Spring Bean 覆盖的 finalize() 方法被回调
|
||||
|
||||
## 7. 参考资料
|
||||
## 参考资料
|
||||
|
||||
- [Spring 官方文档之 Core Technologies](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans)
|
|
@ -1,21 +1,21 @@
|
|||
---
|
||||
title: spring-aop
|
||||
date: 2020-02-26 23:47:47
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- AOP
|
||||
permalink: /pages/53aedb/
|
||||
---
|
||||
|
||||
# Spring AOP
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [概念](#概念)
|
||||
- [什么是 AOP](#什么是-aop)
|
||||
- [术语](#术语)
|
||||
- [advice 的类型](#advice-的类型)
|
||||
- [关于 AOP Proxy](#关于-aop-proxy)
|
||||
- [彻底理解 aspect, join point, point cut, advice](#彻底理解-aspect-join-point-point-cut-advice)
|
||||
- [@AspectJ 支持](#aspectj-支持)
|
||||
- [使能 @AspectJ 支持](#使能-aspectj-支持)
|
||||
- [定义 aspect(切面)](#定义-aspect切面)
|
||||
- [声明 pointcut](#声明-pointcut)
|
||||
- [声明 advice](#声明-advice)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## AOP 概念
|
||||
|
||||
### 什么是 AOP
|
|
@ -1,22 +1,22 @@
|
|||
---
|
||||
title: Spring资源管理
|
||||
date: 2019-09-04 19:46:41
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
permalink: /pages/a1549f/
|
||||
---
|
||||
|
||||
# Spring 资源管理
|
||||
|
||||
Spring 提供了多种方法访问各种资源文件。
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [Resource 接口](#resource-接口)
|
||||
- [内置的 Resource 实现](#内置的-resource-实现)
|
||||
- [ResourceLoader 接口](#resourceloader-接口)
|
||||
- [ResourceLoaderAware 接口](#resourceloaderaware-接口)
|
||||
- [资源依赖](#资源依赖)
|
||||
- [应用上下文和资源路径](#应用上下文和资源路径)
|
||||
- [构造应用上下文](#构造应用上下文)
|
||||
- [使用通配符构造应用上下文](#使用通配符构造应用上下文)
|
||||
- [示例代码](#示例代码)
|
||||
- [参考资料](#参考资料)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## Resource 接口
|
||||
|
||||
相对标准 url 访问机制,spring 的 Resource 接口对抽象底层资源的访问提供了一套更好的机制。
|
|
@ -0,0 +1,386 @@
|
|||
---
|
||||
title: SpringBoot 之快速入门
|
||||
date: 2021-12-10 18:22:26
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/950e4d/
|
||||
---
|
||||
|
||||
# SpringBoot 之快速入门
|
||||
|
||||
## Spring Boot 简介
|
||||
|
||||
Spring Boot 可以让使用者非常方便的创建 Spring 应用。
|
||||
|
||||
Spring Boot 的目标是:
|
||||
|
||||
- 为所有 Spring 开发者提供更快且可广泛访问的入门体验。
|
||||
- 开箱即用
|
||||
- 提供一系列通用的非功能特性(例如嵌入式服务、安全、指标、健康检查和外部化配置)
|
||||
- 完全不需要代码生成,也不需要 XML 配置。
|
||||
|
||||
## Spring Boot 系统要求
|
||||
|
||||
Spring Boot 的构建工具要求:
|
||||
|
||||
| Build Tool | Version |
|
||||
| :--------- | :-------------------- |
|
||||
| Maven | 3.5+ |
|
||||
| Gradle | 6.8.x, 6.9.x, and 7.x |
|
||||
|
||||
Spring Boot 支持的 Servlet 容器:
|
||||
|
||||
| Name | Servlet Version |
|
||||
| :----------- | :-------------- |
|
||||
| Tomcat 9.0 | 4.0 |
|
||||
| Jetty 9.4 | 3.1 |
|
||||
| Jetty 10.0 | 4.0 |
|
||||
| Undertow 2.0 | 4.0 |
|
||||
|
||||
## 部署第一个 Spring Boot 项目
|
||||
|
||||
> 本节介绍如何开发一个小的“Hello World!” web 应用示例,来展示 Spring Boot 的一些关键功能。我们使用 Maven 来构建这个项目,因为大多数 IDE 都支持它。
|
||||
|
||||
### 环境检查
|
||||
|
||||
Spring Boot 项目依赖于 Java 环境和 Mave,开始项目之前需要先检查一下环境。
|
||||
|
||||
本地是否已安装 Java:
|
||||
|
||||
```shell
|
||||
$ java -version
|
||||
java version "1.8.0_102"
|
||||
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
|
||||
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)
|
||||
```
|
||||
|
||||
本地是否已安装 Maven:
|
||||
|
||||
```java
|
||||
$ mvn -v
|
||||
Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-17T14:33:14-04:00)
|
||||
Maven home: /usr/local/Cellar/maven/3.3.9/libexec
|
||||
Java version: 1.8.0_102, vendor: Oracle Corporation
|
||||
```
|
||||
|
||||
### 创建 pom
|
||||
|
||||
我们需要从创建 Maven pom.xml 文件开始。 pom.xml 是 Maven 用于构建项目的配置文件。
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>myproject</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.6.1</version>
|
||||
</parent>
|
||||
|
||||
<!-- Additional lines to be added here... -->
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
使用者可以通过运行 mvn package 来测试它
|
||||
|
||||
### 添加依赖
|
||||
|
||||
Spring Boot 提供了许多启动器(Starters)以应对不同的使用场景。使用者可将 jars 添加到类路径中。我们的示例程序在 POM 的 parent 使用 spring-boot-starter-parent。 spring-boot-starter-parent 是一个特殊的启动器,提供有用的 Maven 默认值。它还提供了一个依赖项的版本管理,可以让使用者使用时不必显示指定版本。
|
||||
|
||||
其他启动器(Starters)提供了各种针对不同使用场景的功能。比如,我们需要开发一个 Web 应用程序,就可以添加了一个 spring-boot-starter-web 依赖项。在此之前,我们可以通过运行以下命令来查看我们当前拥有的 maven 依赖:
|
||||
|
||||
```shell
|
||||
$ mvn dependency:tree
|
||||
|
||||
[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT
|
||||
```
|
||||
|
||||
mvn dependency:tree 命令打印项目依赖项的层级结构。可以看到 spring-boot-starter-parent 本身没有提供任何依赖。要添加必要的依赖,需要编辑 pom.xml 并在 `<dependencies>` 部分添加 spring-boot-starter-web 依赖项:
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
### 编写代码
|
||||
|
||||
要运行应用程序,我们需要创建一个启动类。默认情况下,Maven 从 `src/main/java` 编译源代码,因此您需要创建该目录结构,然后添加一个名为 `src/main/java/MyApplication.java` 的文件以包含以下代码:
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@EnableAutoConfiguration
|
||||
public class MyApplication {
|
||||
|
||||
@RequestMapping("/")
|
||||
String home() {
|
||||
return "Hello World!";
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
@RestController 注解告诉 Spring,这个类是用来处理 Rest 请求的。
|
||||
|
||||
`@RequestMapping` 注解提供了“路由”信息。它告诉 Spring 任何带有 `/` 路径的 HTTP 请求都应该映射到 `home` 方法。 `@RestController` 注解告诉 Spring 将结果字符串直接呈现给调用者。
|
||||
|
||||
`@EnableAutoConfiguration` 注解告诉 Spring Boot 根据你添加的 jar 依赖去自动装配 Spring。
|
||||
|
||||
> 自动配置旨在与“Starters”配合使用,但这两个概念并没有直接联系。您可以自由选择 starters 之外的 jar 依赖项。 Spring Boot 仍然尽力自动配置您的应用程序。
|
||||
|
||||
Spring Boot 的 main 方法通过调用 run 委托给 Spring Boot 的 `SpringApplication` 类。 `SpringApplication` 引导我们的应用程序,启动 Spring,进而启动自动配置的 Tomcat Web 服务器。我们需要将 `MyApplication.class` 作为参数传递给 run 方法,以告诉 `SpringApplication` 哪个是入口类。还传递 args 数组以公开任何命令行参数。
|
||||
|
||||
### 运行示例
|
||||
|
||||
此时,您的应用程序应该可以工作了。由于您使用了 spring-boot-starter-parent POM,因此您有一个有用的运行目标,可用于启动应用程序。从项目根目录键入 mvn spring-boot:run 以启动应用程序。您应该会看到类似于以下内容的输出:
|
||||
|
||||
```shell
|
||||
$ mvn spring-boot:run
|
||||
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v2.6.1)
|
||||
....... . . .
|
||||
....... . . . (log output here)
|
||||
....... . . .
|
||||
........ Started MyApplication in 2.222 seconds (JVM running for 6.514)
|
||||
```
|
||||
|
||||
如果您打开 Web 浏览器访问 localhost:8080,您应该会看到以下输出:
|
||||
|
||||
```
|
||||
Hello World!
|
||||
```
|
||||
|
||||
要正常退出应用程序,请按 `ctrl-c`。
|
||||
|
||||
### 创建可执行 jar
|
||||
|
||||
要创建一个可执行的 jar,我们需要将 spring-boot-maven-plugin 添加到我们的 pom.xml 中。为此,请在依赖项部分下方插入以下行:
|
||||
|
||||
```xml
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
```
|
||||
|
||||
保存 pom.xml 并从命令行运行 mvn package,如下所示:
|
||||
|
||||
```shell
|
||||
$ mvn package
|
||||
|
||||
[INFO] Scanning for projects...
|
||||
[INFO]
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] Building myproject 0.0.1-SNAPSHOT
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] .... ..
|
||||
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ myproject ---
|
||||
[INFO] Building jar: /Users/developer/example/spring-boot-example/target/myproject-0.0.1-SNAPSHOT.jar
|
||||
[INFO]
|
||||
[INFO] --- spring-boot-maven-plugin:2.6.1:repackage (default) @ myproject ---
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] ------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
如果您查看 target 目录,应该会看到 `myproject-0.0.1-SNAPSHOT.jar`。该文件的大小应约为 10 MB。如果想看里面,可以使用 jar tvf,如下:
|
||||
|
||||
```shell
|
||||
$ jar tvf target/myproject-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
您还应该在目标目录中看到一个更小的名为 `myproject-0.0.1-SNAPSHOT.jar.original` 的文件。这是 Maven 在 Spring Boot 重新打包之前创建的原始 jar 文件。
|
||||
|
||||
要运行该应用程序,请使用 java -jar 命令,如下所示:
|
||||
|
||||
```
|
||||
$ java -jar target/myproject-0.0.1-SNAPSHOT.jar
|
||||
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v2.6.1)
|
||||
....... . . .
|
||||
....... . . . (log output here)
|
||||
....... . . .
|
||||
........ Started MyApplication in 2.536 seconds (JVM running for 2.864)
|
||||
```
|
||||
|
||||
和以前一样,要退出应用程序,请按 `ctrl-c`。
|
||||
|
||||
## 通过 SPRING INITIALIZR 创建 Spring Boot 项目
|
||||
|
||||
### 创建项目
|
||||
|
||||
通过 `SPRING INITIALIZR` 工具产生基础项目
|
||||
|
||||
1. 访问:`http://start.spring.io/`
|
||||
2. 选择构建工具`Maven Project`、Spring Boot 版本 `1.5.10` 以及一些工程基本信息,可参考下图所示:
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/start.spring.io.png)
|
||||
|
||||
3. 点击`Generate Project`下载项目压缩包
|
||||
4. 解压压缩包,包中已是一个完整的项目。
|
||||
|
||||
如果你使用 Intellij 作为 IDE,那么你可以直接使用 SPRING INITIALIZR,参考下图操作:
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/intellij-spring-initializr.gif)
|
||||
|
||||
### 项目说明
|
||||
|
||||
**重要文件**
|
||||
|
||||
- `src/main/java` 路径下的 `Chapter1Application` 类 :程序入口
|
||||
- `src/main/resources` 路径下的 `application.properties` :项目配置文件
|
||||
- `src/test/java` 路径下的 `Chapter01ApplicationTests` :程序测试入口
|
||||
|
||||
**pom.xml**
|
||||
|
||||
pom 中指定 parent 为以下内容,表示此项目继承了 `spring-boot-starter-parent` 的 maven 配置(主要是指定了常用依赖、插件的版本)。
|
||||
|
||||
```xml
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>1.5.10.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
```
|
||||
|
||||
此外,pom 中默认引入两个依赖包,和一个插件。
|
||||
|
||||
```xml
|
||||
<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>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
```
|
||||
|
||||
- `spring-boot-starter-web`:核心模块,包括自动配置支持、日志和 YAML。
|
||||
- `spring-boot-starter-test`:测试模块,包括 JUnit、Hamcrest、Mockito。
|
||||
- `spring-boot-maven-plugin`:spring boot 插件, 提供了一系列 spring boot 相关的 maven 操作。
|
||||
- `spring-boot:build-info`,生成 Actuator 使用的构建信息文件 build-info.properties
|
||||
- `spring-boot:repackage`,默认 goal。在 mvn package 之后,再次打包可执行的 jar/war,同时保留 mvn package 生成的 jar/war 为.origin
|
||||
- `spring-boot:run`,运行 Spring Boot 应用
|
||||
- `spring-boot:start`,在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
|
||||
- `spring-boot:stop`,在 mvn integration-test 阶段,进行 Spring Boot 应用生命周期的管理
|
||||
|
||||
### 编写 REST 服务
|
||||
|
||||
- 创建 `package` ,名为 `io.github.zp.springboot.chapter1.web`(根据项目情况修改)
|
||||
- 创建 `HelloController` 类,内容如下:
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class HelloController {
|
||||
|
||||
@RequestMapping("/hello")
|
||||
public String index() {
|
||||
return "Hello World";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
- 启动主程序 `XXXApplication`,打开浏览器访问`http://localhost:8080/hello` ,可以看到页面输出`Hello World`
|
||||
|
||||
### 编写单元测试用例
|
||||
|
||||
在 `XXXApplicationTests` 类中编写一个简单的单元测试来模拟 HTTP 请求,具体如下:
|
||||
|
||||
```java
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = MockServletContext.class)
|
||||
@WebAppConfiguration
|
||||
public class SpringBootHelloWorldApplicationTest {
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getHello() throws Exception {
|
||||
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(equalTo("Hello World")));
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
使用`MockServletContext`来构建一个空的`WebApplicationContext`,这样我们创建的`HelloController`就可以在`@Before`函数中创建并传递到`MockMvcBuilders.standaloneSetup()`函数中。
|
||||
|
||||
- 注意引入下面内容,让`status`、`content`、`equalTo`函数可用
|
||||
|
||||
```java
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
```
|
||||
|
||||
至此已完成目标,通过 Maven 构建了一个空白 Spring Boot 项目,再通过引入 web 模块实现了一个简单的请求处理。
|
||||
|
||||
### 示例源码
|
||||
|
||||
> 示例源码:[spring-boot-web-helloworld](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/web/spring-boot-web-helloworld)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring Boot 官方文档之 Getting Started](https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started)
|
|
@ -0,0 +1,386 @@
|
|||
---
|
||||
title: SpringBoot 之属性加载详解
|
||||
date: 2019-01-10 11:55:54
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/0fb992/
|
||||
---
|
||||
|
||||
# SpringBoot 之属性加载详解
|
||||
|
||||
## 加载 property 顺序
|
||||
|
||||
Spring Boot 加载 property 顺序如下:
|
||||
|
||||
1. [Devtools 全局配置](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-devtools-globalsettings) (当 devtools 被激活 `~/.spring-boot-devtools.properties`).
|
||||
2. [测试环境中的 `@TestPropertySource` 注解配置](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/TestPropertySource.html)
|
||||
3. 测试环境中的属性 `properties`:[`@SpringBootTest`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/context/SpringBootTest.html) 和 [测试注解](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-tests).
|
||||
4. 命令行参数
|
||||
5. `SPRING_APPLICATION_JSON` 属性
|
||||
6. `ServletConfig` 初始化参数
|
||||
7. `ServletContext` 初始化参数
|
||||
8. JNDI attributes from 通过 `java:comp/env` 配置的 JNDI 属性
|
||||
9. Java 系统属性 (`System.getProperties()`)
|
||||
10. 操作系统环境比那里
|
||||
11. `RandomValuePropertySource` 加载 `random.*` 形式的属性
|
||||
12. jar 包外的 `application-{profile}.properties` 或 `application-{profile}.yml` 配置
|
||||
13. jar 包内的 `application-{profile}.properties` 或 `application-{profile}.yml` 配置
|
||||
14. jar 包外的 `application.properties` 或 `application.yml` 配置
|
||||
15. jar 包内的 `application.properties` 或 `application.yml` 配置
|
||||
16. `@PropertySource` 绑定的配置
|
||||
17. 默认属性 (通过 `SpringApplication.setDefaultProperties` 指定)
|
||||
|
||||
## 随机属性
|
||||
|
||||
`RandomValuePropertySource` 类用于配置随机值。
|
||||
|
||||
示例:
|
||||
|
||||
```properties
|
||||
my.secret=${random.value}
|
||||
my.number=${random.int}
|
||||
my.bignumber=${random.long}
|
||||
my.uuid=${random.uuid}
|
||||
my.number.less.than.ten=${random.int(10)}
|
||||
my.number.in.range=${random.int[1024,65536]}
|
||||
```
|
||||
|
||||
## 命令行属性
|
||||
|
||||
默认情况下, `SpringApplication` 会获取 `--` 参数(例如 `--server.port=9000` ),并将这个 `property` 添加到 Spring 的 `Environment` 中。
|
||||
|
||||
如果不想加载命令行属性,可以通过 `SpringApplication.setAddCommandLineProperties(false)` 禁用。
|
||||
|
||||
## Application 属性文件
|
||||
|
||||
`SpringApplication` 会自动加载以下路径下的 `application.properties` 配置文件,将其中的属性读到 Spring 的 `Environment` 中。
|
||||
|
||||
1. 当前目录的 `/config` 子目录
|
||||
2. 当前目录
|
||||
3. classpath 路径下的 `/config` package
|
||||
4. classpath 根路径
|
||||
|
||||
> 注:
|
||||
>
|
||||
> 以上列表的配置文件会根据顺序,后序的配置会覆盖前序的配置。
|
||||
>
|
||||
> 你可以选择 [YAML(yml)](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-yaml) 配置文件替换 properties 配置文件。
|
||||
|
||||
如果不喜欢 `application.properties` 作为配置文件名,可以使用 `spring.config.name` 环境变量替换:
|
||||
|
||||
```
|
||||
$ java -jar myproject.jar --spring.config.name=myproject
|
||||
```
|
||||
|
||||
可以使用 `spring.config.location` 环境变量指定配置文件路径:
|
||||
|
||||
```properties
|
||||
$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
|
||||
```
|
||||
|
||||
## Profile 特定属性
|
||||
|
||||
如果定义 `application-{profile}.properties` 形式的配置文件,将被视为 `profile` 环境下的特定配置。
|
||||
|
||||
可以通过 `spring.profiles.active` 参数来激活 profile,如果没有激活的 profile,默认会加载 `application-default.properties` 中的配置。
|
||||
|
||||
## 属性中的占位符
|
||||
|
||||
`application.properties` 中的值会被 `Environment` 过滤,所以,可以引用之前定义的属性。
|
||||
|
||||
```
|
||||
app.name=MyApp
|
||||
app.description=${app.name} is a Spring Boot application
|
||||
```
|
||||
|
||||
> 注:你可以使用此技术来创建 Spring Boot 属性变量。请参考: [Section 77.4, “Use ‘Short’ Command Line Arguments](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-use-short-command-line-arguments)
|
||||
|
||||
## YAML 属性
|
||||
|
||||
Spring 框架有两个类支持加载 YAML 文件。
|
||||
|
||||
- `YamlPropertiesFactoryBean` 将 YAML 文件的配置加载为 `Properties` 。
|
||||
- `YamlMapFactoryBean` 将 YAML 文件的配置加载为 `Map` 。
|
||||
|
||||
示例 1
|
||||
|
||||
```yaml
|
||||
environments:
|
||||
dev:
|
||||
url: http://dev.example.com
|
||||
name: Developer Setup
|
||||
prod:
|
||||
url: http://another.example.com
|
||||
name: My Cool App
|
||||
```
|
||||
|
||||
等价于:
|
||||
|
||||
```properties
|
||||
environments.dev.url=http://dev.example.com
|
||||
environments.dev.name=Developer Setup
|
||||
environments.prod.url=http://another.example.com
|
||||
environments.prod.name=My Cool App
|
||||
```
|
||||
|
||||
YAML 支持列表形式,等价于 property 中的 `[index]` :
|
||||
|
||||
```yaml
|
||||
my:
|
||||
servers:
|
||||
- dev.example.com
|
||||
- another.example.com
|
||||
```
|
||||
|
||||
等价于
|
||||
|
||||
```properties
|
||||
my.servers[0]=dev.example.com
|
||||
my.servers[1]=another.example.com
|
||||
```
|
||||
|
||||
### 访问属性
|
||||
|
||||
`YamlPropertySourceLoader` 类会将 YAML 配置转化为 Spring `Environment` 类中的 `PropertySource` 。然后,你可以如同 properties 文件中的属性一样,使用 `@Value` 注解来访问 YAML 中配置的属性。
|
||||
|
||||
### 多 profile 配置
|
||||
|
||||
```yaml
|
||||
server:
|
||||
address: 192.168.1.100
|
||||
---
|
||||
spring:
|
||||
profiles: development
|
||||
server:
|
||||
address: 127.0.0.1
|
||||
---
|
||||
spring:
|
||||
profiles: production & eu-central
|
||||
server:
|
||||
address: 192.168.1.120
|
||||
```
|
||||
|
||||
### YAML 的缺点
|
||||
|
||||
注:YAML 注解中的属性不能通过 `@PropertySource` 注解来访问。所以,如果你的项目中使用了一些自定义属性文件,建议不要用 YAML。
|
||||
|
||||
## 属性前缀
|
||||
|
||||
```java
|
||||
package com.example;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix="acme")
|
||||
public class AcmeProperties {
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
private InetAddress remoteAddress;
|
||||
|
||||
private final Security security = new Security();
|
||||
|
||||
public boolean isEnabled() { ... }
|
||||
|
||||
public void setEnabled(boolean enabled) { ... }
|
||||
|
||||
public InetAddress getRemoteAddress() { ... }
|
||||
|
||||
public void setRemoteAddress(InetAddress remoteAddress) { ... }
|
||||
|
||||
public Security getSecurity() { ... }
|
||||
|
||||
public static class Security {
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
|
||||
|
||||
public String getUsername() { ... }
|
||||
|
||||
public void setUsername(String username) { ... }
|
||||
|
||||
public String getPassword() { ... }
|
||||
|
||||
public void setPassword(String password) { ... }
|
||||
|
||||
public List<String> getRoles() { ... }
|
||||
|
||||
public void setRoles(List<String> roles) { ... }
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
相当于支持配置以下属性:
|
||||
|
||||
- `acme.enabled`
|
||||
- `acme.remote-address`
|
||||
- `acme.security.username`
|
||||
- `acme.security.password`
|
||||
- `acme.security.roles`
|
||||
|
||||
然后,你需要使用 `@EnableConfigurationProperties` 注解将属性类注入配置类中。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AcmeProperties.class)
|
||||
public class MyConfiguration {
|
||||
}
|
||||
```
|
||||
|
||||
## 属性松散绑定规则
|
||||
|
||||
Spring Boot 属性名绑定比较松散。
|
||||
|
||||
以下属性 key 都是等价的:
|
||||
|
||||
| Property | Note |
|
||||
| ----------------------------------- | -------- |
|
||||
| `acme.my-project.person.first-name` | `-` 分隔 |
|
||||
| `acme.myProject.person.firstName` | 驼峰命名 |
|
||||
| `acme.my_project.person.first_name` | `_` 分隔 |
|
||||
| `ACME_MYPROJECT_PERSON_FIRSTNAME` | 大写字母 |
|
||||
|
||||
## 属性转换
|
||||
|
||||
如果需要类型转换,你可以提供一个 `ConversionService` bean (一个名叫 `conversionService` 的 bean) 或自定义属性配置 (一个 `CustomEditorConfigurer` bean) 或自定义的 `Converters` (被 `@ConfigurationPropertiesBinding` 注解修饰的 bena)。
|
||||
|
||||
### 时间单位转换
|
||||
|
||||
Spring 使用 `java.time.Duration` 类代表时间大小,以下场景适用:
|
||||
|
||||
- 除非指定 `@DurationUnit` ,否则一个 long 代表的时间为毫秒。
|
||||
- ISO-8601 标准格式( [`java.time.Duration`](https://docs.oracle.com/javase/8/docs/api//java/time/Duration.html#parse-java.lang.CharSequence-) 的实现就是参照此标准)
|
||||
- 你也可以使用以下支持的单位:
|
||||
- `ns` - 纳秒
|
||||
- `us` - 微秒
|
||||
- `ms` - 毫秒
|
||||
- `s` - 秒
|
||||
- `m` - 分
|
||||
- `h` - 时
|
||||
- `d` - 天
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@ConfigurationProperties("app.system")
|
||||
public class AppSystemProperties {
|
||||
|
||||
@DurationUnit(ChronoUnit.SECONDS)
|
||||
private Duration sessionTimeout = Duration.ofSeconds(30);
|
||||
|
||||
private Duration readTimeout = Duration.ofMillis(1000);
|
||||
|
||||
public Duration getSessionTimeout() {
|
||||
return this.sessionTimeout;
|
||||
}
|
||||
|
||||
public void setSessionTimeout(Duration sessionTimeout) {
|
||||
this.sessionTimeout = sessionTimeout;
|
||||
}
|
||||
|
||||
public Duration getReadTimeout() {
|
||||
return this.readTimeout;
|
||||
}
|
||||
|
||||
public void setReadTimeout(Duration readTimeout) {
|
||||
this.readTimeout = readTimeout;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 数据大小转换
|
||||
|
||||
Spring 使用 `DataSize` 类代表数据大小,以下场景适用:
|
||||
|
||||
- long 值(默认视为 byte)
|
||||
- 你也可以使用以下支持的单位:
|
||||
- `B`
|
||||
- `KB`
|
||||
- `MB`
|
||||
- `GB`
|
||||
- `TB`
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@ConfigurationProperties("app.io")
|
||||
public class AppIoProperties {
|
||||
|
||||
@DataSizeUnit(DataUnit.MEGABYTES)
|
||||
private DataSize bufferSize = DataSize.ofMegabytes(2);
|
||||
|
||||
private DataSize sizeThreshold = DataSize.ofBytes(512);
|
||||
|
||||
public DataSize getBufferSize() {
|
||||
return this.bufferSize;
|
||||
}
|
||||
|
||||
public void setBufferSize(DataSize bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public DataSize getSizeThreshold() {
|
||||
return this.sizeThreshold;
|
||||
}
|
||||
|
||||
public void setSizeThreshold(DataSize sizeThreshold) {
|
||||
this.sizeThreshold = sizeThreshold;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 校验属性
|
||||
|
||||
```java
|
||||
@ConfigurationProperties(prefix="acme")
|
||||
@Validated
|
||||
public class AcmeProperties {
|
||||
|
||||
@NotNull
|
||||
private InetAddress remoteAddress;
|
||||
|
||||
@Valid
|
||||
private final Security security = new Security();
|
||||
|
||||
// ... getters and setters
|
||||
|
||||
public static class Security {
|
||||
|
||||
@NotEmpty
|
||||
public String username;
|
||||
|
||||
// ... getters and setters
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
你也可以自定义一个名为 `configurationPropertiesValidator` 的校验器 Bean。获取这个 `@Bean` 的方法必须声明为 `static`。
|
||||
|
||||
## 示例源码
|
||||
|
||||
> 示例源码:[spring-boot-property](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-property)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring Boot 官方文档之 boot-features-external-config](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config)
|
|
@ -0,0 +1,199 @@
|
|||
---
|
||||
title: SpringBoot 之 Profile
|
||||
date: 2019-11-18 14:55:01
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/cb598e/
|
||||
---
|
||||
|
||||
# SpringBoot 之 Profile
|
||||
|
||||
> 一个应用为了在不同的环境下工作,常常会有不同的配置,代码逻辑处理。Spring Boot 对此提供了简便的支持。
|
||||
>
|
||||
> 关键词: `@Profile`、`spring.profiles.active`
|
||||
|
||||
## 区分环境的配置
|
||||
|
||||
### properties 配置
|
||||
|
||||
假设,一个应用的工作环境有:dev、test、prod
|
||||
|
||||
那么,我们可以添加 4 个配置文件:
|
||||
|
||||
- `applcation.properties` - 公共配置
|
||||
- `application-dev.properties` - 开发环境配置
|
||||
- `application-test.properties` - 测试环境配置
|
||||
- `application-prod.properties` - 生产环境配置
|
||||
|
||||
在 `applcation.properties` 文件中可以通过以下配置来激活 profile:
|
||||
|
||||
```properties
|
||||
spring.profiles.active = test
|
||||
```
|
||||
|
||||
### yml 配置
|
||||
|
||||
与 properties 文件类似,我们也可以添加 4 个配置文件:
|
||||
|
||||
- `applcation.yml` - 公共配置
|
||||
- `application-dev.yml` - 开发环境配置
|
||||
- `application-test.yml` - 测试环境配置
|
||||
- `application-prod.yml` - 生产环境配置
|
||||
|
||||
在 `applcation.yml` 文件中可以通过以下配置来激活 profile:
|
||||
|
||||
```yml
|
||||
spring:
|
||||
profiles:
|
||||
active: prod
|
||||
```
|
||||
|
||||
此外,yml 文件也可以在一个文件中完成所有 profile 的配置:
|
||||
|
||||
```yml
|
||||
# 激活 prod
|
||||
spring:
|
||||
profiles:
|
||||
active: prod
|
||||
# 也可以同时激活多个 profile
|
||||
# spring.profiles.active: prod,proddb,prodlog
|
||||
---
|
||||
# dev 配置
|
||||
spring:
|
||||
profiles: dev
|
||||
|
||||
# 略去配置
|
||||
|
||||
---
|
||||
spring:
|
||||
profiles: test
|
||||
|
||||
# 略去配置
|
||||
|
||||
---
|
||||
spring.profiles: prod
|
||||
spring.profiles.include:
|
||||
- proddb
|
||||
- prodlog
|
||||
|
||||
---
|
||||
spring:
|
||||
profiles: proddb
|
||||
|
||||
# 略去配置
|
||||
|
||||
---
|
||||
spring:
|
||||
profiles: prodlog
|
||||
# 略去配置
|
||||
```
|
||||
|
||||
注意:不同 profile 之间通过 `---` 分割
|
||||
|
||||
## 区分环境的代码
|
||||
|
||||
使用 `@Profile` 注解可以指定类或方法在特定的 Profile 环境生效。
|
||||
|
||||
### 修饰类
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@Profile("production")
|
||||
public class JndiDataConfig {
|
||||
|
||||
@Bean(destroyMethod="")
|
||||
public DataSource dataSource() throws Exception {
|
||||
Context ctx = new InitialContext();
|
||||
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 修饰注解
|
||||
|
||||
```java
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Profile("production")
|
||||
public @interface Production {
|
||||
}
|
||||
```
|
||||
|
||||
### 修饰方法
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean("dataSource")
|
||||
@Profile("development")
|
||||
public DataSource standaloneDataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setType(EmbeddedDatabaseType.HSQL)
|
||||
.addScript("classpath:com/bank/config/sql/schema.sql")
|
||||
.addScript("classpath:com/bank/config/sql/test-data.sql")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean("dataSource")
|
||||
@Profile("production")
|
||||
public DataSource jndiDataSource() throws Exception {
|
||||
Context ctx = new InitialContext();
|
||||
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 激活 profile
|
||||
|
||||
### 插件激活 profile
|
||||
|
||||
```
|
||||
spring-boot:run -Drun.profiles=prod
|
||||
```
|
||||
|
||||
### main 方法激活 profile
|
||||
|
||||
```
|
||||
--spring.profiles.active=prod
|
||||
```
|
||||
|
||||
### jar 激活 profile
|
||||
|
||||
```
|
||||
java -jar -Dspring.profiles.active=prod *.jar
|
||||
```
|
||||
|
||||
### 在 Java 代码中激活 profile
|
||||
|
||||
直接指定环境变量来激活 profile:
|
||||
|
||||
```java
|
||||
System.setProperty("spring.profiles.active", "test");
|
||||
```
|
||||
|
||||
在 Spring 容器中激活 profile:
|
||||
|
||||
```java
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.getEnvironment().setActiveProfiles("development");
|
||||
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
|
||||
ctx.refresh();
|
||||
```
|
||||
|
||||
## 示例源码
|
||||
|
||||
> 示例源码:[spring-boot-profile](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-profile)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring 官方文档之 Bean Definition Profiles](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-definition-profiles)
|
||||
- [Spring Boot 官方文档之 boot-features-profiles](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-profiles)
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: Spring 核心
|
||||
date: 2020-02-26 23:47:47
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring核心
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/5e7c20/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring 核心
|
||||
|
||||
> 章节主要针对:Spring 框架的核心技术。如;IOC 依赖注入、AOP、数据绑定等。
|
||||
|
||||
## 📖 内容
|
||||
|
||||
- [Spring 依赖注入(IoC)](01.Spring依赖注入.md)
|
||||
- [Spring Bean 生命周期](02.Spring生命周期.md)
|
||||
- [Spring AOP](03.SpringAop.md)
|
||||
- [Spring 资源管理](04.Spring资源管理.md)
|
||||
- [SpringBoot 教程之快速入门](21.SpringBoot之快速入门.md)
|
||||
- [SpringBoot 之属性加载](22.SpringBoot之属性加载.md)
|
||||
- [SpringBoot 之 Profile](23.SpringBoot之Profile.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
- [跟我学 Spring3](http://jinnianshilongnian.iteye.com/blog/1482071)
|
||||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -0,0 +1,199 @@
|
|||
---
|
||||
title: SpringBoot 之 JDBC
|
||||
date: 2019-02-18 14:33:55
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- JDBC
|
||||
permalink: /pages/cf19fd/
|
||||
---
|
||||
|
||||
# SpringBoot 之 JDBC
|
||||
|
||||
## 简介
|
||||
|
||||
Spring Data 包含对 JDBC 的存储库支持,并将自动为 `CrudRepository` 上的方法生成 SQL。对于更高级的查询,提供了 `@Query` 注解。
|
||||
|
||||
当 classpath 上存在必要的依赖项时,Spring Boot 将自动配置 Spring Data 的 JDBC 存储库。它们可以通过 `spring-boot-starter-data-jdbc` 的单一依赖项添加到项目中。如有必要,可以通过将 `@EnableJdbcRepositories` 批注或 `JdbcConfiguration` 子类添加到应用程序来控制 Spring Data JDBC 的配置。
|
||||
|
||||
> 更多 Spring Data JDBC 细节,可以参考 [Spring Data JDBC 官方文档](http://spring.io/projects/spring-data-jdbc)。
|
||||
|
||||
## API
|
||||
|
||||
`spring-boot-starter-data-jdbc` 引入了 `spring-jdbc` ,其 JDBC 特性就是基于 `spring-jdbc`。
|
||||
|
||||
而 `spring-jdbc` 最核心的 API 无疑就是 `JdbcTemplate`,可以说所有的 JDBC 数据访问,几乎都是围绕着这个类去工作的。
|
||||
|
||||
Spring 对数据库的操作在 Jdbc 层面做了深层次的封装,利用依赖注入,把数据源配置装配到 `JdbcTemplate` 中,再由 `JdbcTemplate` 负责具体的数据访问。
|
||||
|
||||
`JdbcTemplate` 主要提供以下几类方法:
|
||||
|
||||
- `execute` 方法:可以用于执行任何 SQL 语句,一般用于执行 DDL 语句;
|
||||
- `update` 方法及 `batchUpdate` 方法:`update` 方法用于执行新增、修改、删除等语句;`batchUpdate` 方法用于执行批处理相关语句;
|
||||
- `query` 方法及 `queryForXXX` 方法:用于执行查询相关语句;
|
||||
- `call` 方法:用于执行存储过程、函数相关语句。
|
||||
|
||||
为了方便演示,以下增删改查操作都围绕一个名为 user 的表(该表的主键 id 是自增序列)进行,该表的数据实体如下:
|
||||
|
||||
```java
|
||||
public class User {
|
||||
private Integer id;
|
||||
private String name;
|
||||
private Integer age;
|
||||
|
||||
// 省略 getter/setter
|
||||
}
|
||||
```
|
||||
|
||||
数据实体只要是一个纯粹的 Java Bean 即可,无需任何注解修饰。
|
||||
|
||||
### execute
|
||||
|
||||
使用 execute 执行 DDL 语句,创建一个名为 test 的数据库,并在此数据库下新建一个名为 user 的表。
|
||||
|
||||
```java
|
||||
public void recreateTable() {
|
||||
jdbcTemplate.execute("DROP DATABASE IF EXISTS test");
|
||||
jdbcTemplate.execute("CREATE DATABASE test");
|
||||
jdbcTemplate.execute("USE test");
|
||||
jdbcTemplate.execute("DROP TABLE if EXISTS user");
|
||||
jdbcTemplate.execute("DROP TABLE if EXISTS user");
|
||||
// @formatter:off
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CREATE TABLE user (id int (10) unsigned NOT NULL AUTO_INCREMENT,\n")
|
||||
.append("name varchar (64) NOT NULL DEFAULT '',\n")
|
||||
.append("age tinyint (3) NOT NULL DEFAULT 0,\n")
|
||||
.append("PRIMARY KEY (ID));\n");
|
||||
// @formatter:on
|
||||
jdbcTemplate.execute(sb.toString());
|
||||
}
|
||||
```
|
||||
|
||||
### update
|
||||
|
||||
新增数据
|
||||
|
||||
```java
|
||||
public void insert(String name, Integer age) {
|
||||
jdbcTemplate.update("INSERT INTO user(name, age) VALUES(?, ?)", name, age);
|
||||
}
|
||||
```
|
||||
|
||||
删除数据
|
||||
|
||||
```java
|
||||
public void delete(String name) {
|
||||
jdbcTemplate.update("DELETE FROM user WHERE name = ?", name);
|
||||
}
|
||||
```
|
||||
|
||||
修改数据
|
||||
|
||||
```java
|
||||
public void update(User user) {
|
||||
jdbcTemplate.update("UPDATE USER SET name=?, age=? WHERE id=?", user.getName(), user.getAge(), user.getId());
|
||||
}
|
||||
```
|
||||
|
||||
批处理
|
||||
|
||||
```java
|
||||
public void batchInsert(List<User> users) {
|
||||
String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
|
||||
|
||||
List<Object[]> params = new ArrayList<>();
|
||||
|
||||
users.forEach(item -> {
|
||||
params.add(new Object[] {item.getName(), item.getAge()});
|
||||
});
|
||||
jdbcTemplate.batchUpdate(sql, params);
|
||||
}
|
||||
```
|
||||
|
||||
### query
|
||||
|
||||
查单个对象
|
||||
|
||||
```java
|
||||
public User queryByName(String name) {
|
||||
try {
|
||||
return jdbcTemplate
|
||||
.queryForObject("SELECT * FROM user WHERE name = ?", new BeanPropertyRowMapper<>(User.class), name);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
查多个对象
|
||||
|
||||
```java
|
||||
public List<User> list() {
|
||||
return jdbcTemplate.query("select * from USER", new BeanPropertyRowMapper(User.class));
|
||||
}
|
||||
```
|
||||
|
||||
获取某个记录某列或者 count、avg、sum 等函数返回唯一值
|
||||
|
||||
```java
|
||||
public Integer count() {
|
||||
try {
|
||||
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM user", Integer.class);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 实战
|
||||
|
||||
### 配置数据源
|
||||
|
||||
在 `src/main/resource` 目录下添加 `application.properties` 配置文件,内容如下:
|
||||
|
||||
```properties
|
||||
spring.datasource.url = jdbc:mysql://localhost:3306/spring_boot_tutorial?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
|
||||
spring.datasource.username = root
|
||||
spring.datasource.password = root
|
||||
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
|
||||
```
|
||||
|
||||
需要根据实际情况,替换 `url`、`username`、`password`。
|
||||
|
||||
### 注入 JdbcTemplate
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class UserDAOImpl implements UserDAO {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public UserDAOImpl(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
请参考:[源码](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-data-jdbc)
|
||||
|
||||
## 引申和引用
|
||||
|
||||
**引申**
|
||||
|
||||
- [Spring Boot 教程](https://github.com/dunwu/spring-boot-tutorial)
|
||||
|
||||
**参考**
|
||||
|
||||
- [Spring Boot 官方文档之 boot-features-data-jdbc](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-data-jdbc)
|
|
@ -0,0 +1,147 @@
|
|||
---
|
||||
title: SpringBoot 之集成 MyBatis
|
||||
date: 2019-05-09 17:09:25
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- MyBatis
|
||||
- PageHelper
|
||||
- Mapper
|
||||
permalink: /pages/88219e/
|
||||
---
|
||||
|
||||
# SpringBoot 之集成 MyBatis, 分页插件 PageHelper, 通用 Mapper
|
||||
|
||||
- [Spring Boot 1.5.1.RELEASE](https://github.com/spring-projects/spring-boot)
|
||||
- [mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter)
|
||||
- [mapper-spring-boot-starter](https://github.com/abel533/mapper-boot-starter)
|
||||
- [pagehelper-spring-boot-starter](https://github.com/pagehelper/pagehelper-spring-boot)
|
||||
|
||||
## 新书《MyBatis 从入门到精通》
|
||||
|
||||
![img](https://github.com/mybatis-book/book/raw/master/book.png)
|
||||
|
||||
预售地址:[京东](https://item.jd.com/12103309.html),[当当](http://product.dangdang.com/25098208.html),[亚马逊](https://www.amazon.cn/MyBatis从入门到精通-刘增辉/dp/B072RC11DM/ref=sr_1_18?ie=UTF8&qid=1498007125&sr=8-18&keywords=mybatis)
|
||||
|
||||
CSDN 博客:http://blog.csdn.net/isea533/article/details/73555400
|
||||
|
||||
GitHub 项目:https://github.com/mybatis-book/book
|
||||
|
||||
## 项目依赖
|
||||
|
||||
```xml
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
<!--mapper-->
|
||||
<dependency>
|
||||
<groupId>tk.mybatis</groupId>
|
||||
<artifactId>mapper-spring-boot-starter</artifactId>
|
||||
<version>1.2.4</version>
|
||||
</dependency>
|
||||
<!--pagehelper-->
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Spring DevTools 配置
|
||||
|
||||
感谢[emf1002](https://github.com/emf1002)提供的解决方案。
|
||||
|
||||
在使用 DevTools 时,通用 Mapper 经常会出现 class x.x.A cannot be cast to x.x.A。
|
||||
|
||||
同一个类如果使用了不同的类加载器,就会产生这样的错误,所以解决方案就是让通用 Mapper 和实体类使用相同的类加载器即可。
|
||||
|
||||
DevTools 默认会对 IDE 中引入的所有项目使用 restart 类加载器,对于引入的 jar 包使用 base 类加载器,因此只要保证通用 Mapper 的 jar 包使用 restart
|
||||
类加载器即可。
|
||||
|
||||
在 `src/main/resources` 中创建 META-INF 目录,在此目录下添加 spring-devtools.properties 配置,内容如下:
|
||||
|
||||
```properties
|
||||
restart.include.mapper=/mapper-[\\w-\\.]+jar
|
||||
restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar
|
||||
```
|
||||
|
||||
使用这个配置后,就会使用 restart 类加载加载 include 进去的 jar 包。
|
||||
|
||||
## 集成 MyBatis Generator
|
||||
|
||||
通过 Maven 插件集成的,所以运行插件使用下面的命令:
|
||||
|
||||
> mvn mybatis-generator:generate
|
||||
|
||||
Mybatis Geneator 详解:
|
||||
|
||||
> http://blog.csdn.net/isea533/article/details/42102297
|
||||
|
||||
## application.properties 配置
|
||||
|
||||
```properties
|
||||
#mybatis
|
||||
mybatis.type-aliases-package=tk.mybatis.springboot.model
|
||||
mybatis.mapper-locations=classpath:mapper/*.xml
|
||||
|
||||
#mapper
|
||||
#mappers 多个接口时逗号隔开
|
||||
mapper.mappers=tk.mybatis.springboot.util.MyMapper
|
||||
mapper.not-empty=false
|
||||
mapper.identity=MYSQL
|
||||
|
||||
#pagehelper
|
||||
pagehelper.helperDialect=mysql
|
||||
pagehelper.reasonable=true
|
||||
pagehelper.supportMethodsArguments=true
|
||||
pagehelper.params=count=countSql
|
||||
```
|
||||
|
||||
## application.yml 配置
|
||||
|
||||
完整配置可以参考 [src/main/resources/application-old.yml](https://github.com/abel533/MyBatis-Spring-Boot/blob/master/src/main/resources/application-old.yml) ,和 MyBatis 相关的部分配置如下:
|
||||
|
||||
```yaml
|
||||
mybatis:
|
||||
type-aliases-package: tk.mybatis.springboot.model
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
|
||||
mapper:
|
||||
mappers:
|
||||
- tk.mybatis.springboot.util.MyMapper
|
||||
not-empty: false
|
||||
identity: MYSQL
|
||||
|
||||
pagehelper:
|
||||
helperDialect: mysql
|
||||
reasonable: true
|
||||
supportMethodsArguments: true
|
||||
params: count=countSql
|
||||
```
|
||||
|
||||
注意 mapper 配置,因为参数名固定,所以接收参数使用的对象,按照 Spring Boot 配置规则,大写字母都变了带横线的小写字母。针对如 IDENTITY(对应 i-d-e-n-t-i-t-y)提供了全小写的 identity 配置,如果 IDE 能自动提示,看自动提示即可。
|
||||
|
||||
## SSM 集成的基础项目
|
||||
|
||||
> https://github.com/abel533/Mybatis-Spring
|
||||
|
||||
## MyBatis 工具 http://www.mybatis.tk
|
||||
|
||||
- 推荐使用 Mybatis 通用 Mapper3 https://github.com/abel533/Mapper
|
||||
- 推荐使用 Mybatis 分页插件 PageHelper https://github.com/pagehelper/Mybatis-PageHelper
|
||||
|
||||
## 作者信息
|
||||
|
||||
- 作者博客:http://blog.csdn.net/isea533
|
||||
- 作者邮箱:abel533@gmail.com
|
||||
- 如需加群,请通过 http://mybatis.tk 首页按钮加群。
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: SpringBoot 之集成 MongoDB
|
||||
date: 2018-12-15 17:29:36
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- MongoDB
|
||||
permalink: /pages/db2a41/
|
||||
---
|
||||
|
||||
# SpringBoot 之集成 MongoDB
|
||||
|
||||
## 配置 mongodb
|
||||
|
||||
### 创建 admin
|
||||
|
||||
```bash
|
||||
use admin
|
||||
db.createUser({
|
||||
"user": "admin",
|
||||
"pwd": "123456",
|
||||
"roles": [{ "role": "userAdminAnyDatabase", "db": "admin" }]
|
||||
})
|
||||
```
|
||||
|
||||
### 创建 root
|
||||
|
||||
```bash
|
||||
db.createUser({
|
||||
"user": "root",
|
||||
"pwd": "123456",
|
||||
"roles": [{ "role": "root", "db": "admin" }]
|
||||
})
|
||||
```
|
||||
|
||||
### 创建用户自己的数据库的角色
|
||||
|
||||
```bash
|
||||
use test
|
||||
db.createUser({
|
||||
"user": "test",
|
||||
"pwd": "123456",
|
||||
"roles": [{ "role": "dbOwner", "db": "test" }]
|
||||
})
|
||||
```
|
||||
|
||||
### 查看用户
|
||||
|
||||
```bash
|
||||
db.system.users.find()
|
||||
show users
|
||||
```
|
||||
|
||||
### 删除用户
|
||||
|
||||
删除用户必须由账号管理员来删,所以,切换到 admin 角色
|
||||
|
||||
```bash
|
||||
use admin
|
||||
db.auth("admin","123456")
|
||||
|
||||
# 删除单个用户
|
||||
db.system.users.remove({user:"XXXXXX"})
|
||||
# 删除所有用户
|
||||
db.system.users.remove({})
|
||||
```
|
||||
|
||||
## Spring Boot 配置
|
||||
|
||||
```properties
|
||||
spring.data.mongodb.host = localhost
|
||||
spring.data.mongodb.port = 27017
|
||||
spring.data.mongodb.database = test
|
||||
spring.data.mongodb.username = root
|
||||
spring.data.mongodb.password = root
|
||||
```
|
||||
|
||||
## 引用和引申
|
||||
|
||||
https://spring.io/guides/gs/accessing-data-mongodb/
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
title: SpringBoot 之集成 Elasticsearch
|
||||
date: 2018-12-25 14:06:36
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- Elasticsearch
|
||||
permalink: /pages/fac14c/
|
||||
---
|
||||
|
||||
# SpringBoot 之集成 Elasticsearch
|
||||
|
||||
## 简介
|
||||
|
||||
[Elasticsearch](https://www.elastic.co/products/elasticsearch) 是一个开源的、分布式的搜索和分析引擎。
|
||||
|
||||
### 通过 REST 客户端连接 Elasticsearch
|
||||
|
||||
如果在 classpath 路径下存在 `org.elasticsearch.client:elasticsearch-rest-client` jar 包,Spring Boot 会自动配置并注册一个 `RestClient` Bean,它的默认访问路径为:`localhost:9200`。
|
||||
|
||||
你可以使用如下方式进行定制:
|
||||
|
||||
```properties
|
||||
spring.elasticsearch.rest.uris=http://search.example.com:9200
|
||||
spring.elasticsearch.rest.username=user
|
||||
spring.elasticsearch.rest.password=secret
|
||||
```
|
||||
|
||||
您还可以注册实现任意数量的 `RestClientBuilderCustomizer` bean,以进行更高级的定制。要完全控制注册,请定义 `RestClient` bean。
|
||||
|
||||
如果 classpath 路径有 `org.elasticsearch.client:elasticsearch-rest-high-level-client` jar 包,Spring Boot 将自动配置一个 `RestHighLevelClient`,它包装任何现有的 `RestClient` bean,重用其 HTTP 配置。
|
||||
|
||||
### 通过 Jest 连接 Elasticsearch
|
||||
|
||||
如果 classpath 上有 Jest,你可以注入一个自动配置的 `JestClient`,默认情况下是 `localhost:9200`。您可以进一步调整客户端的配置方式,如以下示例所示:
|
||||
|
||||
```properties
|
||||
spring.elasticsearch.jest.uris=http://search.example.com:9200
|
||||
spring.elasticsearch.jest.read-timeout=10000
|
||||
spring.elasticsearch.jest.username=user
|
||||
spring.elasticsearch.jest.password=secret
|
||||
```
|
||||
|
||||
您还可以注册实现任意数量的 `HttpClientConfigBuilderCustomizer` bean,以进行更高级的定制。以下示例调整为其他 HTTP 设置:
|
||||
|
||||
```java
|
||||
static class HttpSettingsCustomizer implements HttpClientConfigBuilderCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(HttpClientConfig.Builder builder) {
|
||||
builder.maxTotalConnection(100).defaultMaxTotalConnectionPerRoute(5);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
要完全控制注册,请定义 `JestClient` bean。
|
||||
|
||||
### 通过 Spring Data 访问 Elasticsearch
|
||||
|
||||
要连接到 Elasticsearch,您必须提供一个或多个集群节点的地址。可以通过将 `spring.data.elasticsearch.cluster-nodes` 属性设置为以逗号分隔的 `host:port` 列表来指定地址。使用此配置,可以像任何其他 Spring bean 一样注入 `ElasticsearchTemplate` 或 `TransportClient`,如以下示例所示:
|
||||
|
||||
```java
|
||||
spring.data.elasticsearch.cluster-nodes=localhost:9300
|
||||
@Component
|
||||
public class MyBean {
|
||||
|
||||
private final ElasticsearchTemplate template;
|
||||
|
||||
public MyBean(ElasticsearchTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
如果你添加了自定义的 `ElasticsearchTemplate` 或 `TransportClient` `@Bean` ,就会替换默认的配置。
|
||||
|
||||
### Elasticsearch Repositories
|
||||
|
||||
Spring Data 包含对 Elasticsearch 的 repository 支持。基本原则是根据方法名称自动为您构建查询。
|
||||
|
||||
事实上,Spring Data JPA 和 Spring Data Elasticsearch 共享相同的通用基础架构。
|
||||
|
||||
## 源码
|
||||
|
||||
完整示例:[源码](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-data-elasticsearch)
|
||||
|
||||
使用方法:
|
||||
|
||||
```bash
|
||||
mvn clean package
|
||||
cd target
|
||||
java -jar spring-boot-data-elasticsearch.jar
|
||||
```
|
||||
|
||||
## 引申和引用
|
||||
|
||||
**引申**
|
||||
|
||||
- [Spring Boot 教程](https://github.com/dunwu/spring-boot-tutorial)
|
||||
|
||||
**参考**
|
||||
|
||||
- [Spring Boot 官方文档之 boot-features-elasticsearch](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-elasticsearch)
|
||||
- [Spring Data Elasticsearch Github](https://github.com/spring-projects/spring-data-elasticsearch)
|
||||
- [Spring Data Elasticsearch 官方文档](https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/)
|
|
@ -1,21 +1,38 @@
|
|||
---
|
||||
title: Spring 数据
|
||||
date: 2022-09-18 11:05:36
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- 数据
|
||||
permalink: /pages/b912d1/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring 数据
|
||||
|
||||
> 章节主要针对:Spring 在数据库领域的应用。如:JDBC、ORM、事务等。
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### [Spring 的数据访问策略](Spring数据访问策略.md)
|
||||
|
||||
### [Spring 中使用 JDBC 访问数据](Spring中使用JDBC访问数据.md)
|
||||
|
||||
### [Spring 事务管理](Spring事务管理.md)
|
||||
- [SpringBoot 之 JDBC](21.SpringBoot之JDBC.md)
|
||||
- [SpringBoot 之 Mybatis](22.SpringBoot之Mybatis.md)
|
||||
- [SpringBoot 之 MongoDB](23.SpringBoot之MongoDB.md)
|
||||
- [SpringBoot 之 Elasticsearch](24.SpringBoot之Elasticsearch.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
|
@ -25,4 +42,4 @@
|
|||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [spring-tutorial 首页](https://dunwu.github.io/spring-tutorial/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -1,3 +1,15 @@
|
|||
---
|
||||
title: Spring中使用JDBC访问数据
|
||||
date: 2017-10-20 09:27:55
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- null
|
||||
permalink: /pages/476edf/
|
||||
---
|
||||
# Spring 中使用 JDBC 访问数据
|
||||
|
||||
## 准备
|
|
@ -1,3 +1,15 @@
|
|||
---
|
||||
title: Spring事务管理
|
||||
date: 2019-04-22 17:36:37
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- null
|
||||
permalink: /pages/a8c9c0/
|
||||
---
|
||||
# 事务管理
|
||||
|
||||
## 理解事务
|
|
@ -1,3 +1,15 @@
|
|||
---
|
||||
title: Spring数据访问策略
|
||||
date: 2019-11-21 09:21:26
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- null
|
||||
permalink: /pages/c7cab3/
|
||||
---
|
||||
# Spring 的数据访问策略
|
||||
|
||||
Spring 的目标之一,就是允许开发人员在开发过程中能够遵循面向对象(OO)原则中的“针对接口编程”。Spring 对数据访问的支持也不例外。
|
|
@ -1,3 +1,15 @@
|
|||
---
|
||||
title: spring-tx
|
||||
date: 2020-08-05 09:28:40
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring数据
|
||||
tags:
|
||||
- null
|
||||
permalink: /pages/63965f/
|
||||
---
|
||||
# Spring 事务管理
|
||||
|
||||
Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 Spring 的声明式事务功能更是提供了极其方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在方法上标记 @Transactional 注解,即可一键开启方法的事务性配置。
|
|
@ -1,3 +1,19 @@
|
|||
---
|
||||
title: spring-mvc
|
||||
date: 2017-11-08 16:53:27
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringWeb
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Web
|
||||
permalink: /pages/65351b/
|
||||
---
|
||||
|
||||
# SpringMVC 简介
|
||||
|
||||
## SpringMVC 工作流程描述
|
|
@ -0,0 +1,508 @@
|
|||
---
|
||||
title: SpringBoot 之应用 EasyUI
|
||||
date: 2019-01-08 17:19:34
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringWeb
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- Web
|
||||
permalink: /pages/ad0516/
|
||||
---
|
||||
|
||||
# SpringBoot 之应用 EasyUI
|
||||
|
||||
> EasyUI 是一个简单的用户界面组件的集合。由于 EasyUI 已经封装好大部分 UI 基本功能,能帮用户减少大量的 js 和 css 代码。所以,EasyUI 非常适合用于开发简单的系统或原型系统。
|
||||
>
|
||||
> 本文示例使用技术点:
|
||||
>
|
||||
> - Spring Boot:主要使用了 spring-boot-starter-web、spring-boot-starter-data-jpa
|
||||
> - EasyUI:按需加载,并没有引入所有的 EasyUI 特性
|
||||
> - 数据库:为了测试方便,使用 H2
|
||||
|
||||
![img](http://www.jeasyui.cn/images/easyui.png)
|
||||
|
||||
## 简介
|
||||
|
||||
### 什么是 EasyUI?
|
||||
|
||||
- easyui 是基于 jQuery、Angular.、Vue 和 React 的用户界面组件的集合。
|
||||
- easyui 提供了构建现代交互式 javascript 应用程序的基本功能。
|
||||
- 使用 easyui,您不需要编写许多 javascript 代码,通常通过编写一些 HTML 标记来定义用户界面。
|
||||
- 完整的 HTML5 网页框架。
|
||||
- 使用 easyui 开发你的产品时可以大量节省你的时间和规模。
|
||||
- easyui 使用非常简单但功能非常强大。
|
||||
|
||||
## Spring Boot 整合 EasyUI
|
||||
|
||||
### 配置
|
||||
|
||||
application.properties 修改:
|
||||
|
||||
```properties
|
||||
spring.mvc.view.prefix = /views/
|
||||
spring.mvc.view.suffix = .html
|
||||
```
|
||||
|
||||
### 引入 easyui
|
||||
|
||||
EasyUI 下载地址:http://www.jeasyui.cn/download.html
|
||||
|
||||
在 `src/main/resources/static` 目录下引入 easyui。
|
||||
|
||||
然后在 html 中引用:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="../lib/easyui/themes/bootstrap/easyui.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="../lib/easyui/themes/icon.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="../lib/easyui/themes/color.css"
|
||||
/>
|
||||
<script type="text/javascript" src="../lib/easyui/jquery.min.js"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="../lib/easyui/jquery.easyui.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="../lib/easyui/locale/easyui-lang-zh_CN.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 省略 -->
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
引入 easyui 后,需要使用哪种组件,可以查看相关文档或 API,十分简单,此处不一一赘述。
|
||||
|
||||
## 实战
|
||||
|
||||
### 引入 maven 依赖
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
### 使用 JPA
|
||||
|
||||
为了使用 JPA 技术访问数据,我们需要定义 Entity 和 Repository
|
||||
|
||||
定义一个 Entity:
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String phone;
|
||||
private String email;
|
||||
|
||||
protected User() {}
|
||||
|
||||
public User(String firstName, String lastName, String phone, String email) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.phone = phone;
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
// 略 getter/setter
|
||||
}
|
||||
```
|
||||
|
||||
定义一个 Repository:
|
||||
|
||||
```
|
||||
public interface UserRepository extends CrudRepository<User, Long> {
|
||||
|
||||
List<User> findByLastName(String lastName);
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 Web
|
||||
|
||||
首页 Controller,将 web 请求定向到指定页面(下面的例子定向到 index.html)
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@RequestMapping(value = {"", "/", "index"})
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
此外,需要定义一个 Controller,提供后台的 API 接口
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserRepository customerRepository;
|
||||
|
||||
@RequestMapping(value = "/user", method = RequestMethod.GET)
|
||||
public String user() {
|
||||
return "user";
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/user/list")
|
||||
public ResponseDTO<User> list() {
|
||||
Iterable<User> all = customerRepository.findAll();
|
||||
List<User> list = IteratorUtils.toList(all.iterator());
|
||||
return new ResponseDTO<>(true, list.size(), list);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/user/add")
|
||||
public ResponseDTO<User> add(User user) {
|
||||
User result = customerRepository.save(user);
|
||||
List<User> list = new ArrayList<>();
|
||||
list.add(result);
|
||||
return new ResponseDTO<>(true, 1, list);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/user/save")
|
||||
public ResponseDTO<User> save(@RequestParam("id") Long id, User user) {
|
||||
user.setId(id);
|
||||
customerRepository.save(user);
|
||||
List<User> list = new ArrayList<>();
|
||||
list.add(user);
|
||||
return new ResponseDTO<>(true, 1, list);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/user/delete")
|
||||
public ResponseDTO delete(@RequestParam("id") Long id) {
|
||||
customerRepository.deleteById(id);
|
||||
return new ResponseDTO<>(true, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 EasyUI
|
||||
|
||||
接下来,我们要使用前面定义的后台接口,仅需要在 EasyUI API 中指定 `url` 即可。
|
||||
|
||||
请留意下面示例中的 url 字段,和实际接口是一一对应的。
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Complex Layout - jQuery EasyUI Demo</title>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="../lib/easyui/themes/bootstrap/easyui.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="../lib/easyui/themes/icon.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="../lib/easyui/themes/color.css"
|
||||
/>
|
||||
<script type="text/javascript" src="../lib/easyui/jquery.min.js"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="../lib/easyui/jquery.easyui.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="../lib/easyui/locale/easyui-lang-zh_CN.js"
|
||||
></script>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: microsoft yahei;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%">
|
||||
<h2>基本的 CRUD 应用</h2>
|
||||
<p>数据来源于后台系统</p>
|
||||
|
||||
<table
|
||||
id="dg"
|
||||
title="Custom List"
|
||||
class="easyui-datagrid"
|
||||
url="/user/list"
|
||||
toolbar="#toolbar"
|
||||
pagination="true"
|
||||
rownumbers="true"
|
||||
fitColumns="true"
|
||||
singleSelect="true"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th field="id" width="50">ID</th>
|
||||
<th field="firstName" width="50">First Name</th>
|
||||
<th field="lastName" width="50">Last Name</th>
|
||||
<th field="phone" width="50">Phone</th>
|
||||
<th field="email" width="50">Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div id="toolbar">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="easyui-linkbutton"
|
||||
iconCls="icon-add"
|
||||
plain="true"
|
||||
onclick="newUser()"
|
||||
>添加</a
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="easyui-linkbutton"
|
||||
iconCls="icon-edit"
|
||||
plain="true"
|
||||
onclick="editUser()"
|
||||
>修改</a
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="easyui-linkbutton"
|
||||
iconCls="icon-remove"
|
||||
plain="true"
|
||||
onclick="destroyUser()"
|
||||
>删除</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="dlg"
|
||||
class="easyui-dialog"
|
||||
style="width:400px"
|
||||
data-options="closed:true,modal:true,border:'thin',buttons:'#dlg-buttons'"
|
||||
>
|
||||
<form
|
||||
id="fm"
|
||||
method="post"
|
||||
novalidate
|
||||
style="margin:0;padding:20px 50px"
|
||||
>
|
||||
<h3>User Information</h3>
|
||||
<div style="margin-bottom:10px">
|
||||
<input
|
||||
name="firstName"
|
||||
class="easyui-textbox"
|
||||
required="true"
|
||||
label="First Name:"
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom:10px">
|
||||
<input
|
||||
name="lastName"
|
||||
class="easyui-textbox"
|
||||
required="true"
|
||||
label="Last Name:"
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom:10px">
|
||||
<input
|
||||
name="phone"
|
||||
class="easyui-textbox"
|
||||
required="true"
|
||||
label="Phone:"
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom:10px">
|
||||
<input
|
||||
name="email"
|
||||
class="easyui-textbox"
|
||||
required="true"
|
||||
validType="email"
|
||||
label="Email:"
|
||||
style="width:100%"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="dlg-buttons">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="easyui-linkbutton c6"
|
||||
iconCls="icon-ok"
|
||||
onclick="saveUser()"
|
||||
style="width:90px"
|
||||
>Save</a
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="easyui-linkbutton"
|
||||
iconCls="icon-cancel"
|
||||
onclick="javascript:$('#dlg').dialog('close')"
|
||||
style="width:90px"
|
||||
>Cancel</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var url
|
||||
|
||||
function newUser() {
|
||||
$('#dlg')
|
||||
.dialog('open')
|
||||
.dialog('center')
|
||||
.dialog('setTitle', 'New User')
|
||||
$('#fm').form('clear')
|
||||
url = '/user/add'
|
||||
}
|
||||
|
||||
function editUser() {
|
||||
var row = $('#dg').datagrid('getSelected')
|
||||
if (row) {
|
||||
$('#dlg')
|
||||
.dialog('open')
|
||||
.dialog('center')
|
||||
.dialog('setTitle', 'Edit User')
|
||||
$('#fm').form('load', row)
|
||||
url = '/user/save'
|
||||
}
|
||||
}
|
||||
|
||||
function saveUser() {
|
||||
$('#fm').form('submit', {
|
||||
url: url,
|
||||
onSubmit: function() {
|
||||
return $(this).form('validate')
|
||||
},
|
||||
success: function(result) {
|
||||
var result = eval('(' + result + ')')
|
||||
if (result.errorMsg) {
|
||||
$.messager.show({
|
||||
title: 'Error',
|
||||
msg: result.errorMsg
|
||||
})
|
||||
} else {
|
||||
$('#dlg').dialog('close') // close the dialog
|
||||
$('#dg').datagrid('reload') // reload the user data
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function destroyUser() {
|
||||
var row = $('#dg').datagrid('getSelected')
|
||||
if (row) {
|
||||
$.messager.confirm(
|
||||
'Confirm',
|
||||
'Are you sure you want to destroy this user?',
|
||||
function(r) {
|
||||
if (r) {
|
||||
$.post(
|
||||
'/user/delete',
|
||||
{ id: row.id },
|
||||
function(result) {
|
||||
if (result.success) {
|
||||
$('#dg').datagrid('reload') // reload the user data
|
||||
} else {
|
||||
$.messager.show({
|
||||
// show error message
|
||||
title: 'Error',
|
||||
msg: result.errorMsg
|
||||
})
|
||||
}
|
||||
},
|
||||
'json'
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
请参考 [源码](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-web-ui/spring-boot-web-ui-easyui)
|
||||
|
||||
运行方式:
|
||||
|
||||
```
|
||||
mvn clean package -DskipTests=true
|
||||
java -jar target/
|
||||
```
|
||||
|
||||
在浏览器中访问:http://localhost:8080/
|
||||
|
||||
## 引用和引申
|
||||
|
||||
- [EasyUI 官网](http://www.jeasyui.com/)
|
||||
- [EasyUI 中文网](http://www.jeasyui.cn/)
|
|
@ -1,17 +1,36 @@
|
|||
# Spring 集成
|
||||
---
|
||||
title: Spring Web
|
||||
date: 2020-02-26 23:48:06
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringWeb
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- Web
|
||||
permalink: /pages/e2586a/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring Web
|
||||
|
||||
> 章节主要针对:Spring 在 web 领域的应用。如:Spring MVC、WebSocket 等。
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### [Spring MVC](spring-mvc.md)
|
||||
- [Spring WebMvc](01.SpringWebMvc.md)
|
||||
- [SpringBoot 之应用 EasyUI](21.SpringBoot之应用EasyUI.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
|
@ -21,4 +40,4 @@
|
|||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [spring-tutorial 首页](https://dunwu.github.io/spring-tutorial/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
title: spring-boot-async
|
||||
date: 2019-11-18 14:55:01
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringIO
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- 异步
|
||||
permalink: /pages/92add2/
|
||||
---
|
||||
|
||||
# SpringBoot 教程之处理异步请求
|
||||
|
||||
## `@EnableAsync` 注解
|
||||
|
||||
要使用 `@Async`,首先需要使用 `@EnableAsync` 注解开启 Spring Boot 中的异步特性。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AppConfig {
|
||||
}
|
||||
```
|
||||
|
||||
更详细的配置说明,可以参考:[`AsyncConfigurer`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/AsyncConfigurer.html)
|
||||
|
||||
## `@Async` 注解
|
||||
|
||||
### 支持的用法
|
||||
|
||||
(1)**无入参无返回值方法**
|
||||
|
||||
您可以用 `@Async` 注解修饰方法,这表明这个方法是异步方式调用。换句话说,程序在调用此方法时会立即返回,而方法的实际执行发生在已提交给 Spring `TaskExecutor` 的任务中。在最简单的情况下,您可以将注解应用于返回 void 的方法,如以下示例所示:
|
||||
|
||||
```java
|
||||
@Async
|
||||
void doSomething() {
|
||||
// this will be executed asynchronously
|
||||
}
|
||||
```
|
||||
|
||||
(2)**有入参无返回值方法**
|
||||
|
||||
与使用 `@Scheduled` 注释注释的方法不同,这些方法可以指定参数,因为它们在运行时由调用者以“正常”方式调用,而不是由容器管理的调度任务调用。例如,以下代码是 `@Async` 注解的合法应用:
|
||||
|
||||
```java
|
||||
@Async
|
||||
void doSomething(String s) {
|
||||
// this will be executed asynchronously
|
||||
}
|
||||
```
|
||||
|
||||
(3)**有入参有返回值方法**
|
||||
|
||||
甚至可以异步调用返回值的方法。但是,这些方法需要具有 `Future` 类型的返回值。这仍然提供了异步执行的好处,以便调用者可以在调用 `Future` 上的 `get()` 之前执行其他任务。以下示例显示如何在返回值的方法上使用`@Async`:
|
||||
|
||||
```java
|
||||
@Async
|
||||
Future<String> returnSomething(int i) {
|
||||
// this will be executed asynchronously
|
||||
}
|
||||
```
|
||||
|
||||
### 不支持的用法
|
||||
|
||||
`@Async` 不能与生命周期回调一起使用,例如 `@PostConstruct`。
|
||||
|
||||
要异步初始化 Spring bean,必须使用单独的初始化 Spring bean,然后在目标上调用 `@Async` 带注释的方法,如以下示例所示:
|
||||
|
||||
```java
|
||||
public class SampleBeanImpl implements SampleBean {
|
||||
|
||||
@Async
|
||||
void doSomething() {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SampleBeanInitializer {
|
||||
|
||||
private final SampleBean bean;
|
||||
|
||||
public SampleBeanInitializer(SampleBean bean) {
|
||||
this.bean = bean;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initialize() {
|
||||
bean.doSomething();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 明确指定执行器
|
||||
|
||||
默认情况下,在方法上指定 `@Async` 时,使用的执行器是在启用异步支持时配置的执行器,即如果使用 XML 或 `AsyncConfigurer` 实现(如果有),则为 `annotation-driven` 元素。但是,如果需要指示在执行给定方法时应使用默认值以外的执行器,则可以使用 `@Async` 注解的 value 属性。以下示例显示了如何执行此操作:
|
||||
|
||||
```java
|
||||
@Async("otherExecutor")
|
||||
void doSomething(String s) {
|
||||
// this will be executed asynchronously by "otherExecutor"
|
||||
}
|
||||
```
|
||||
|
||||
在这种情况下,“otherExecutor”可以是 Spring 容器中任何 Executor bean 的名称,也可以是与任何 Executor 关联的限定符的名称(例如,使用 `<qualifier>` 元素或 Spring 的 `@Qualifier` 注释指定) )。
|
||||
|
||||
## 管理 `@Async` 的异常
|
||||
|
||||
当 `@Async` 方法的返回值类型为 `Future` 型时,很容易管理在方法执行期间抛出的异常,因为在调用 `get` 结果时会抛出此异常。但是,对于返回值类型为 void 型的方法,异常不会被捕获且无法传输。您可以提供 `AsyncUncaughtExceptionHandler` 来处理此类异常。以下示例显示了如何执行此操作:
|
||||
|
||||
```java
|
||||
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
// handle exception
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
默认情况下,仅记录异常。您可以使用 `AsyncConfigurer` 或 `<task:annotation-driven />` XML 元素定义自定义 `AsyncUncaughtExceptionHandler`。
|
||||
|
||||
## 示例源码
|
||||
|
||||
> 示例源码:[spring-boot-async](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-async)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring Boot 官方文档之 boot-features-external-config](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config)
|
||||
- [Spring Boot 官方文档之 scheduling-annotation-support](https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#scheduling-annotation-support)
|
|
@ -0,0 +1,271 @@
|
|||
---
|
||||
title: SpringBoot 之集成 Json
|
||||
date: 2018-12-30 22:24:16
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringIO
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- JSON
|
||||
permalink: /pages/676725/
|
||||
---
|
||||
|
||||
# SpringBoot 之集成 Json
|
||||
|
||||
## 简介
|
||||
|
||||
### Spring Boot 支持的 Json 库
|
||||
|
||||
Spring Boot 支持三种 Json 库:
|
||||
|
||||
- Gson
|
||||
- Jackson
|
||||
- JSON-B
|
||||
|
||||
**Jackson 是 Spring Boot 官方推荐的默认库。**
|
||||
|
||||
Spring Boot 提供了 Jackson 的自动配置,Jackson 是 `spring-boot-starter-json` 的一部分。当 Jackson 在类路径上时,会自动配置 ObjectMapper bean。
|
||||
|
||||
Spring Boot 提供了 Gson 的自动配置。当 Gson 在 classpath 上时,会自动配置 Gson bean。提供了几个 `spring.gson.*` 配置属性来自定义配置。为了获得更多控制,可以使用一个或多个 `GsonBuilderCustomizer` bean。
|
||||
|
||||
Spring Boot 提供了 JSON-B 的自动配置。当 JSON-B API 在 classpath 上时,将自动配置 Jsonb bean。首选的 JSON-B 实现是 Apache Johnzon,它提供了依赖关系管理。
|
||||
|
||||
### Spring Web 中的序列化、反序列化
|
||||
|
||||
以下注解都是 `spring-web` 中提供的支持。
|
||||
|
||||
#### `@ResponseBody`
|
||||
|
||||
`@Responsebody` 注解用于将 Controller 的方法返回的对象,通过适当的 `HttpMessageConverter` 转换为指定格式后,写入到 HTTP Response 对象的 body 数据区。一般在异步获取数据时使用。通常是在使用 `@RequestMapping` 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP 响应正文中。
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@ResponseBody
|
||||
@RequestMapping(name = "/getInfo", method = RequestMethod.GET)
|
||||
public InfoDTO getInfo() {
|
||||
return new InfoDTO();
|
||||
}
|
||||
```
|
||||
|
||||
#### `@RequestBody`
|
||||
|
||||
@RequestBody 注解用于读取 HTTP Request 请求的 body 部分数据,使用系统默认配置的 `HttpMessageConverter` 进行解析,然后把相应的数据绑定到要返回的对象上;再把 `HttpMessageConverter` 返回的对象数据绑定到 controller 中方法的参数上。
|
||||
|
||||
request 的 body 部分的数据编码格式由 header 部分的 `Content-Type` 指定。
|
||||
|
||||
示例:
|
||||
|
||||
```java
|
||||
@RequestMapping(name = "/postInfo", method = RequestMethod.POST)
|
||||
public void postInfo(@RequestBody InfoDTO infoDTO) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### `@RestController`
|
||||
|
||||
Spring 4 以前:
|
||||
|
||||
如果需要返回到指定页面,则需要用 `@Controller` 配合视图解析器 `InternalResourceViewResolver` 。
|
||||
|
||||
如果需要返回 JSON,XML 或自定义 mediaType 内容到页面,则需要在对应的方法上加上 `@ResponseBody` 注解。
|
||||
|
||||
Spring 4 以后,新增了 `@RestController` 注解:
|
||||
|
||||
它相当于 `@Controller` + `@RequestBody` 。
|
||||
|
||||
如果使用 `@RestController` 注解 Controller,则 Controller 中的方法无法返回 jsp 页面,或者 html,配置的视图解析器 `InternalResourceViewResolver` 将不起作用,直接返回内容。
|
||||
|
||||
## 指定类的 Json 序列化、反序列化
|
||||
|
||||
如果使用 Jackson 序列化和反序列化 JSON 数据,您可能需要编写自己的 `JsonSerializer` 和 `JsonDeserializer` 类。自定义序列化程序通常通过模块向 Jackson 注册,但 Spring Boot 提供了另一种 `@JsonComponent` 注释,可以更容易地直接注册 Spring Beans。
|
||||
|
||||
您可以直接在 `JsonSerializer` 或 `JsonDeserializer` 实现上使用 `@JsonComponent` 注释。您还可以在包含序列化程序/反序列化程序作为内部类的类上使用它,如以下示例所示:
|
||||
|
||||
```java
|
||||
import java.io.*;
|
||||
import com.fasterxml.jackson.core.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import org.springframework.boot.jackson.*;
|
||||
|
||||
@JsonComponent
|
||||
public class Example {
|
||||
|
||||
public static class Serializer extends JsonSerializer<SomeObject> {
|
||||
// ...
|
||||
}
|
||||
|
||||
public static class Deserializer extends JsonDeserializer<SomeObject> {
|
||||
// ...
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`ApplicationContext` 中的所有 `@JsonComponent` bean 都会自动注册到 Jackson。因为 `@JsonComponent` 是使用 `@Component` 进行元注释的,所以通常的组件扫描规则适用。
|
||||
|
||||
Spring Boot 还提供了 [`JsonObjectSerializer`](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonObjectSerializer.java) 和 [`JsonObjectDeserializer`](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jackson/JsonObjectDeserializer.java) 基类,它们在序列化对象时提供了标准 Jackson 版本的有用替代方法。有关详细信息,请参阅 Javadoc 中的 [`JsonObjectSerializer`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonObjectSerializer.html) 和 [`JsonObjectDeserializer`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonObjectDeserializer.html)。
|
||||
|
||||
## @JsonTest
|
||||
|
||||
使用 `@JsonTest` 可以很方便的在 Spring Boot 中测试序列化、反序列化。
|
||||
|
||||
使用 `@JsonTest` 相当于使用以下自动配置:
|
||||
|
||||
```
|
||||
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration org.springframework.boot.test.autoconfigure.json.JsonTestersAutoConfiguration
|
||||
```
|
||||
|
||||
`@JsonTest` 使用示例:
|
||||
|
||||
想试试完整示例,可以参考:[源码](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-web-fastjson)
|
||||
|
||||
```java
|
||||
@JsonTest
|
||||
@RunWith(SpringRunner.class)
|
||||
public class SimpleJsonTest {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private JacksonTester<InfoDTO> json;
|
||||
|
||||
@Test
|
||||
public void testSerialize() throws Exception {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
InfoDTO infoDTO = new InfoDTO("JSON测试应用", "1.0.0", sdf.parse("2019-01-01 12:00:00"));
|
||||
JsonContent<InfoDTO> jsonContent = json.write(infoDTO);
|
||||
log.info("json content: {}", jsonContent.getJson());
|
||||
// 或者使用基于JSON path的校验
|
||||
assertThat(jsonContent).hasJsonPathStringValue("@.appName");
|
||||
assertThat(jsonContent).extractingJsonPathStringValue("@.appName").isEqualTo("JSON测试应用");
|
||||
assertThat(jsonContent).hasJsonPathStringValue("@.version");
|
||||
assertThat(jsonContent).extractingJsonPathStringValue("@.version").isEqualTo("1.0.0");
|
||||
assertThat(jsonContent).hasJsonPathStringValue("@.date");
|
||||
assertThat(jsonContent).extractingJsonPathStringValue("@.date").isEqualTo("2019-01-01 12:00:00");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws Exception {
|
||||
String content = "{\"appName\":\"JSON测试应用\",\"version\":\"1.0.0\",\"date\":\"2019-01-01\"}";
|
||||
InfoDTO actual = json.parseObject(content);
|
||||
assertThat(actual.getAppName()).isEqualTo("JSON测试应用");
|
||||
assertThat(actual.getVersion()).isEqualTo("1.0.0");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Spring Boot 中的 json 配置
|
||||
|
||||
### Jackson 配置
|
||||
|
||||
当 Spring Boot 的 json 库为 jackson 时,可以使用以下配置属性(对应 [`JacksonProperties`](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java) 类):
|
||||
|
||||
```properties
|
||||
spring.jackson.date-format= # Date format string or a fully-qualified date format class name. For instance, `yyyy-MM-dd HH:mm:ss`.
|
||||
spring.jackson.default-property-inclusion= # Controls the inclusion of properties during serialization. Configured with one of the values in Jackson's JsonInclude.Include enumeration.
|
||||
spring.jackson.deserialization.*= # Jackson on/off features that affect the way Java objects are deserialized.
|
||||
spring.jackson.generator.*= # Jackson on/off features for generators.
|
||||
spring.jackson.joda-date-time-format= # Joda date time format string. If not configured, "date-format" is used as a fallback if it is configured with a format string.
|
||||
spring.jackson.locale= # Locale used for formatting.
|
||||
spring.jackson.mapper.*= # Jackson general purpose on/off features.
|
||||
spring.jackson.parser.*= # Jackson on/off features for parsers.
|
||||
spring.jackson.property-naming-strategy= # One of the constants on Jackson's PropertyNamingStrategy. Can also be a fully-qualified class name of a PropertyNamingStrategy subclass.
|
||||
spring.jackson.serialization.*= # Jackson on/off features that affect the way Java objects are serialized.
|
||||
spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".
|
||||
spring.jackson.visibility.*= # Jackson visibility thresholds that can be used to limit which methods (and fields) are auto-detected.
|
||||
```
|
||||
|
||||
### GSON 配置
|
||||
|
||||
当 Spring Boot 的 json 库为 gson 时,可以使用以下配置属性(对应 [`GsonProperties`](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/gson/GsonProperties.java) 类):
|
||||
|
||||
```properties
|
||||
spring.gson.date-format= # Format to use when serializing Date objects.
|
||||
spring.gson.disable-html-escaping= # Whether to disable the escaping of HTML characters such as '<', '>', etc.
|
||||
spring.gson.disable-inner-class-serialization= # Whether to exclude inner classes during serialization.
|
||||
spring.gson.enable-complex-map-key-serialization= # Whether to enable serialization of complex map keys (i.e. non-primitives).
|
||||
spring.gson.exclude-fields-without-expose-annotation= # Whether to exclude all fields from consideration for serialization or deserialization that do not have the "Expose" annotation.
|
||||
spring.gson.field-naming-policy= # Naming policy that should be applied to an object's field during serialization and deserialization.
|
||||
spring.gson.generate-non-executable-json= # Whether to generate non executable JSON by prefixing the output with some special text.
|
||||
spring.gson.lenient= # Whether to be lenient about parsing JSON that doesn't conform to RFC 4627.
|
||||
spring.gson.long-serialization-policy= # Serialization policy for Long and long types.
|
||||
spring.gson.pretty-printing= # Whether to output serialized JSON that fits in a page for pretty printing.
|
||||
spring.gson.serialize-nulls= # Whether to serialize null fields.
|
||||
```
|
||||
|
||||
## Spring Boot 中使用 Fastjson
|
||||
|
||||
国内很多的 Java 程序员更喜欢使用阿里的 fastjson 作为 json lib。那么,如何在 Spring Boot 中将其替换默认的 jackson 库呢?
|
||||
|
||||
你需要做如下处理:
|
||||
|
||||
(1)引入 fastjson jar 包:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.54</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
(2)实现 WebMvcConfigurer 接口,自定义 `configureMessageConverters` 接口。如下所示:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
/**
|
||||
* 自定义消息转换器
|
||||
* @param converters
|
||||
*/
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
// 清除默认 Json 转换器
|
||||
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
|
||||
|
||||
// 配置 FastJson
|
||||
FastJsonConfig config = new FastJsonConfig();
|
||||
config.setSerializerFeatures(SerializerFeature.QuoteFieldNames, SerializerFeature.WriteEnumUsingToString,
|
||||
SerializerFeature.WriteMapNullValue, SerializerFeature.WriteDateUseDateFormat,
|
||||
SerializerFeature.DisableCircularReferenceDetect);
|
||||
|
||||
// 添加 FastJsonHttpMessageConverter
|
||||
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
|
||||
fastJsonHttpMessageConverter.setFastJsonConfig(config);
|
||||
List<MediaType> fastMediaTypes = new ArrayList<>();
|
||||
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
|
||||
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
|
||||
converters.add(fastJsonHttpMessageConverter);
|
||||
|
||||
// 添加 StringHttpMessageConverter,解决中文乱码问题
|
||||
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
|
||||
converters.add(stringHttpMessageConverter);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 示例源码
|
||||
|
||||
完整示例:[源码](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-web-fastjson)
|
||||
|
||||
## 引申和引用
|
||||
|
||||
**引申**
|
||||
|
||||
- [Spring Boot 教程](https://github.com/dunwu/spring-boot-tutorial)
|
||||
|
||||
**引用**
|
||||
|
||||
- [Spring Boot 官方文档之 boot-features-json](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-json)
|
|
@ -0,0 +1,277 @@
|
|||
---
|
||||
title: SpringBoot 之发送邮件
|
||||
date: 2019-11-20 15:20:44
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringIO
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- 邮件
|
||||
permalink: /pages/2586f1/
|
||||
---
|
||||
|
||||
# SpringBoot 之发送邮件
|
||||
|
||||
## 简介
|
||||
|
||||
Spring Boot 收发邮件最简便方式是通过 `spring-boot-starter-mail`。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
spring-boot-starter-mail 本质上是使用 JavaMail(javax.mail)。如果想对 JavaMail 有进一步了解,可以参考: [JavaMail 使用指南](https://dunwu.github.io/java-tutorial/#/javalib/javamail)
|
||||
|
||||
## API
|
||||
|
||||
Spring Framework 提供了一个使用 `JavaMailSender` 接口发送电子邮件的简单抽象,这是发送邮件的核心 API。
|
||||
|
||||
`JavaMailSender` 接口提供的 API 如下:
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20190110111102.png)
|
||||
|
||||
## 配置
|
||||
|
||||
Spring Boot 为 `JavaMailSender` 提供了自动配置以及启动器模块。
|
||||
|
||||
如果 `spring.mail.host` 和相关库(由 spring-boot-starter-mail 定义)可用,则 Spring Boot 会创建默认 `JavaMailSender`(如果不存在)。可以通过 `spring.mail` 命名空间中的配置项进一步自定义发件人。
|
||||
特别是,某些默认超时值是无限的,您可能希望更改它以避免线程被无响应的邮件服务器阻塞,如以下示例所示:
|
||||
|
||||
```properties
|
||||
spring.mail.properties.mail.smtp.connectiontimeout=5000
|
||||
spring.mail.properties.mail.smtp.timeout=3000
|
||||
spring.mail.properties.mail.smtp.writetimeout=5000
|
||||
```
|
||||
|
||||
也可以使用 JNDI 中的现有会话配置 `JavaMailSender`:
|
||||
|
||||
```
|
||||
spring.mail.jndi-name=mail/Session
|
||||
```
|
||||
|
||||
以下为 Spring Boot 关于 Mail 的配置:
|
||||
|
||||
有关更多详细信息,请参阅 [`MailProperties`](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mail/MailProperties.java)。
|
||||
|
||||
```properties
|
||||
# Email (MailProperties)
|
||||
spring.mail.default-encoding=UTF-8 # Default MimeMessage encoding.
|
||||
spring.mail.host= # SMTP server host. For instance, `smtp.example.com`.
|
||||
spring.mail.jndi-name= # Session JNDI name. When set, takes precedence over other Session settings.
|
||||
spring.mail.password= # Login password of the SMTP server.
|
||||
spring.mail.port= # SMTP server port.
|
||||
spring.mail.properties.*= # Additional JavaMail Session properties.
|
||||
spring.mail.protocol=smtp # Protocol used by the SMTP server.
|
||||
spring.mail.test-connection=false # Whether to test that the mail server is available on startup.
|
||||
spring.mail.username= # Login user of the SMTP server.
|
||||
```
|
||||
|
||||
## 实战
|
||||
|
||||
### 引入依赖
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.dozermapper</groupId>
|
||||
<artifactId>dozer-spring-boot-starter</artifactId>
|
||||
<version>6.4.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
### 配置邮件属性
|
||||
|
||||
在 `src/main/resources` 目录下添加 `application-163.properties` 配置文件,内容如下:
|
||||
|
||||
```properties
|
||||
spring.mail.host = smtp.163.com
|
||||
spring.mail.username = xxxxxx
|
||||
spring.mail.password = xxxxxx
|
||||
spring.mail.properties.mail.smtp.auth = true
|
||||
spring.mail.properties.mail.smtp.starttls.enable = true
|
||||
spring.mail.properties.mail.smtp.starttls.required = true
|
||||
spring.mail.default-encoding = UTF-8
|
||||
|
||||
mail.domain = 163.com
|
||||
mail.from = ${spring.mail.username}@${mail.domain}
|
||||
```
|
||||
|
||||
注:需替换有效的 `spring.mail.username`、`spring.mail.password`。
|
||||
|
||||
`application-163.properties` 配置文件表示使用 163 邮箱时的配置,为了使之生效,需要通过 `spring.profiles.active = 163` 来激活它。
|
||||
|
||||
在 `src/main/resources` 目录下添加 `application.properties` 配置文件,内容如下:
|
||||
|
||||
```properties
|
||||
spring.profiles.active = 163
|
||||
```
|
||||
|
||||
### Java 代码
|
||||
|
||||
首先,需要读取部分配置属性,方法如下:
|
||||
|
||||
```java
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Validated
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mail")
|
||||
public class MailProperties {
|
||||
private String domain;
|
||||
private String from;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(String from) {
|
||||
this.from = from;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接着,定义一个邮件参数实体类(使用 lombok 简化了 getter、setter):
|
||||
|
||||
```java
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class MailDTO {
|
||||
private String from;
|
||||
private String replyTo;
|
||||
private String[] to;
|
||||
private String[] cc;
|
||||
private String[] bcc;
|
||||
private Date sentDate;
|
||||
private String subject;
|
||||
private String text;
|
||||
private String[] filenames;
|
||||
}
|
||||
```
|
||||
|
||||
接着,实现发送邮件的功能接口:
|
||||
|
||||
```java
|
||||
import com.github.dozermapper.core.Mapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import java.io.IOException;
|
||||
|
||||
@Service
|
||||
public class MailService {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private MailProperties mailProperties;
|
||||
|
||||
@Autowired
|
||||
private JavaMailSender javaMailSender;
|
||||
|
||||
@Autowired
|
||||
private Mapper mapper;
|
||||
|
||||
public void sendSimpleMailMessage(MailDTO mailDTO) {
|
||||
SimpleMailMessage simpleMailMessage = mapper.map(mailDTO, SimpleMailMessage.class);
|
||||
if (StringUtils.isEmpty(mailDTO.getFrom())) {
|
||||
mailDTO.setFrom(mailProperties.getFrom());
|
||||
}
|
||||
javaMailSender.send(simpleMailMessage);
|
||||
}
|
||||
|
||||
public void sendMimeMessage(MailDTO mailDTO) {
|
||||
|
||||
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
|
||||
MimeMessageHelper messageHelper;
|
||||
try {
|
||||
messageHelper = new MimeMessageHelper(mimeMessage, true);
|
||||
|
||||
if (StringUtils.isEmpty(mailDTO.getFrom())) {
|
||||
messageHelper.setFrom(mailProperties.getFrom());
|
||||
}
|
||||
messageHelper.setTo(mailDTO.getTo());
|
||||
messageHelper.setSubject(mailDTO.getSubject());
|
||||
|
||||
mimeMessage = messageHelper.getMimeMessage();
|
||||
MimeBodyPart mimeBodyPart = new MimeBodyPart();
|
||||
mimeBodyPart.setContent(mailDTO.getText(), "text/html;charset=UTF-8");
|
||||
|
||||
// 描述数据关系
|
||||
MimeMultipart mm = new MimeMultipart();
|
||||
mm.setSubType("related");
|
||||
mm.addBodyPart(mimeBodyPart);
|
||||
|
||||
// 添加邮件附件
|
||||
for (String filename : mailDTO.getFilenames()) {
|
||||
MimeBodyPart attachPart = new MimeBodyPart();
|
||||
try {
|
||||
attachPart.attachFile(filename);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mm.addBodyPart(attachPart);
|
||||
}
|
||||
mimeMessage.setContent(mm);
|
||||
mimeMessage.saveChanges();
|
||||
|
||||
} catch (MessagingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
javaMailSender.send(mimeMessage);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 示例源码
|
||||
|
||||
> 示例源码:[spring-boot-mail](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-mail)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring Boot 官方文档之 Sending Email](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-email)
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: Spring IO
|
||||
date: 2022-09-18 11:34:00
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringIO
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- IO
|
||||
hidden: true
|
||||
permalink: /pages/56581b/
|
||||
---
|
||||
|
||||
# Spring IO
|
||||
|
||||
## 📖 内容
|
||||
|
||||
- [SpringBoot 之异步请求](01.SpringBoot之异步请求.md)
|
||||
- [SpringBoot 之 Json](02.SpringBoot之Json.md)
|
||||
- [SpringBoot 之邮件](03.SpringBoot之邮件.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
- [跟我学 Spring3](http://jinnianshilongnian.iteye.com/blog/1482071)
|
||||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -1,21 +1,24 @@
|
|||
---
|
||||
title: Spring集成缓存
|
||||
date: 2017-11-08 16:53:27
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring集成
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- 集成
|
||||
- 缓存
|
||||
permalink: /pages/a311cb/
|
||||
---
|
||||
|
||||
# Spring 集成缓存中间件
|
||||
|
||||
> Spring 中提供了缓存功能的抽象,允许你在底层灵活的替换缓存实现,而对上层暴露相同的缓存接口。
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [缓存接口](#缓存接口)
|
||||
- [开启注解](#开启注解)
|
||||
- [缓存注解使用](#缓存注解使用)
|
||||
- [缓存存储](#缓存存储)
|
||||
- [使用 ConcurrentHashMap 作为缓存](#使用-concurrenthashmap-作为缓存)
|
||||
- [使用 Ehcache 作为缓存](#使用-ehcache-作为缓存)
|
||||
- [使用 Caffeine 作为缓存](#使用-caffeine-作为缓存)
|
||||
- [示例代码](#示例代码)
|
||||
- [参考资料](#参考资料)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## 缓存接口
|
||||
|
||||
Spring 的缓存 API 以注解方式提供。
|
|
@ -1,16 +1,34 @@
|
|||
---
|
||||
title: Spring 集成调度器
|
||||
date: 2017-11-08 16:53:27
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring集成
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- 集成
|
||||
- 调度器
|
||||
permalink: /pages/a187f0/
|
||||
---
|
||||
|
||||
# Spring 集成调度器
|
||||
|
||||
## 概述
|
||||
|
||||
如果想在Spring中使用任务调度功能,除了集成调度框架Quartz这种方式,也可以使用Spring自己的调度任务框架。
|
||||
使用Spring的调度框架,优点是:支持注解`@Scheduler`,可以省去大量的配置。
|
||||
如果想在 Spring 中使用任务调度功能,除了集成调度框架 Quartz 这种方式,也可以使用 Spring 自己的调度任务框架。
|
||||
使用 Spring 的调度框架,优点是:支持注解`@Scheduler`,可以省去大量的配置。
|
||||
|
||||
## 实时触发调度任务
|
||||
|
||||
#### TaskScheduler接口
|
||||
### TaskScheduler 接口
|
||||
|
||||
Spring3 引入了`TaskScheduler`接口,这个接口定义了调度任务的抽象方法。
|
||||
TaskScheduler 接口的声明:
|
||||
|
||||
Spring3引入了`TaskScheduler`接口,这个接口定义了调度任务的抽象方法。
|
||||
TaskScheduler接口的声明:
|
||||
```java
|
||||
public interface TaskScheduler {
|
||||
|
||||
|
@ -28,34 +46,40 @@ public interface TaskScheduler {
|
|||
|
||||
}
|
||||
```
|
||||
从以上方法可以看出TaskScheduler有两类重要参数:
|
||||
- 一个是要调度的方法,即一个实现了Runnable接口的线程类的run()方法;
|
||||
|
||||
从以上方法可以看出 TaskScheduler 有两类重要参数:
|
||||
|
||||
- 一个是要调度的方法,即一个实现了 Runnable 接口的线程类的 run()方法;
|
||||
- 另一个就是触发条件。
|
||||
|
||||
**TaskScheduler接口的实现类**
|
||||
**TaskScheduler 接口的实现类**
|
||||
它有三个实现类:`DefaultManagedTaskScheduler`、`ThreadPoolTaskScheduler`、`TimerManagerTaskScheduler`。
|
||||
**DefaultManagedTaskScheduler**:基于JNDI的调度器。
|
||||
**DefaultManagedTaskScheduler**:基于 JNDI 的调度器。
|
||||
**TimerManagerTaskScheduler**:托管`commonj.timers.TimerManager`实例的调度器。
|
||||
**ThreadPoolTaskScheduler**:提供线程池管理的调度器,它也实现了`TaskExecutor`接口,从而使的单一的实例可以尽可能快地异步执行。
|
||||
|
||||
#### Trigger接口
|
||||
Trigger接口抽象了触发条件的方法。
|
||||
Trigger接口的声明:
|
||||
#### Trigger 接口
|
||||
|
||||
Trigger 接口抽象了触发条件的方法。
|
||||
Trigger 接口的声明:
|
||||
|
||||
```
|
||||
public interface Trigger {
|
||||
Date nextExecutionTime(TriggerContext triggerContext);
|
||||
}
|
||||
```
|
||||
|
||||
**Trigger接口的实现类**
|
||||
**CronTrigger**:实现了cron规则的触发器类(和Quartz的cron规则相同)。
|
||||
**Trigger 接口的实现类**
|
||||
**CronTrigger**:实现了 cron 规则的触发器类(和 Quartz 的 cron 规则相同)。
|
||||
**PeriodicTrigger**:实现了一个周期性规则的触发器类(例如:定义触发起始时间、间隔时间等)。
|
||||
|
||||
#### 完整范例
|
||||
|
||||
实现一个调度任务的功能有以下几个关键点:
|
||||
**(1) 定义调度器**
|
||||
在spring-bean.xml中进行配置
|
||||
使用`task:scheduler`标签定义一个大小为10的线程池调度器,spring会实例化一个`ThreadPoolTaskScheduler`。
|
||||
**(1) 定义调度器**
|
||||
在 spring-bean.xml 中进行配置
|
||||
使用`task:scheduler`标签定义一个大小为 10 的线程池调度器,spring 会实例化一个`ThreadPoolTaskScheduler`。
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
|
@ -72,7 +96,9 @@ public interface Trigger {
|
|||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
</beans>
|
||||
```
|
||||
***注:不要忘记引入xsd:***
|
||||
|
||||
**_注:不要忘记引入 xsd:_**
|
||||
|
||||
```xml
|
||||
http://www.springframework.org/schema/task
|
||||
http://www.springframework.org/schema/task/spring-task-3.1.xsd
|
||||
|
@ -80,6 +106,7 @@ http://www.springframework.org/schema/task/spring-task-3.1.xsd
|
|||
|
||||
**(2) 定义调度任务**
|
||||
定义实现`Runnable`接口的线程类。
|
||||
|
||||
```
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -96,7 +123,8 @@ public class DemoTask implements Runnable {
|
|||
|
||||
**(3) 装配调度器,并执行调度任务**
|
||||
在一个`Controller`类中用`@Autowired`注解装配`TaskScheduler`。
|
||||
然后调动TaskScheduler对象的schedule方法启动调度器,就可以执行调度任务了。
|
||||
然后调动 TaskScheduler 对象的 schedule 方法启动调度器,就可以执行调度任务了。
|
||||
|
||||
```java
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
|
@ -107,17 +135,19 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
|||
|
||||
@Controller
|
||||
@RequestMapping("/scheduler")
|
||||
public class SchedulerController {
|
||||
public class SchedulerController {
|
||||
@Autowired
|
||||
TaskScheduler scheduler;
|
||||
|
||||
|
||||
@RequestMapping(value = "/start", method = RequestMethod.POST)
|
||||
public void start() {
|
||||
scheduler.schedule(new DemoTask(), new CronTrigger("0/5 * * * * *"));
|
||||
}
|
||||
}
|
||||
```
|
||||
访问/scheduler/start接口,启动调度器,可以看到如下日志内容:
|
||||
|
||||
访问/scheduler/start 接口,启动调度器,可以看到如下日志内容:
|
||||
|
||||
```
|
||||
13:53:15.010 myScheduler-1 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
|
||||
13:53:20.003 myScheduler-1 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
|
||||
|
@ -125,18 +155,20 @@ public class SchedulerController {
|
|||
13:53:30.005 myScheduler-1 o.zp.notes.spring.scheduler.DemoTask.run - call DemoTask.run
|
||||
```
|
||||
|
||||
### @Scheduler的使用方法
|
||||
### @Scheduler 的使用方法
|
||||
|
||||
Spring的调度器一个很大的亮点在于`@Scheduler`注解,这可以省去很多繁琐的配置。
|
||||
Spring 的调度器一个很大的亮点在于`@Scheduler`注解,这可以省去很多繁琐的配置。
|
||||
|
||||
#### 启动注解
|
||||
使用@Scheduler注解先要使用`<task:annotation-driven>`启动注解开关。
|
||||
***例:***
|
||||
|
||||
使用@Scheduler 注解先要使用`<task:annotation-driven>`启动注解开关。
|
||||
**_例:_**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
|
@ -151,8 +183,10 @@ Spring的调度器一个很大的亮点在于`@Scheduler`注解,这可以省
|
|||
</beans>
|
||||
```
|
||||
|
||||
#### @Scheduler定义触发条件
|
||||
例:使用`fixedDelay`指定触发条件为每5000毫秒执行一次。注意:必须在上一次调度成功后的5000秒才能执行。
|
||||
#### @Scheduler 定义触发条件
|
||||
|
||||
例:使用`fixedDelay`指定触发条件为每 5000 毫秒执行一次。注意:必须在上一次调度成功后的 5000 秒才能执行。
|
||||
|
||||
```java
|
||||
@Scheduled(fixedDelay=5000)
|
||||
public void doSomething() {
|
||||
|
@ -160,7 +194,7 @@ public void doSomething() {
|
|||
}
|
||||
```
|
||||
|
||||
例:使用`fixedRate`指定触发条件为每5000毫秒执行一次。注意:无论上一次调度是否成功,5000秒后必然执行。
|
||||
例:使用`fixedRate`指定触发条件为每 5000 毫秒执行一次。注意:无论上一次调度是否成功,5000 秒后必然执行。
|
||||
|
||||
```java
|
||||
@Scheduled(fixedRate=5000)
|
||||
|
@ -169,7 +203,8 @@ public void doSomething() {
|
|||
}
|
||||
```
|
||||
|
||||
例:使用`initialDelay`指定方法在初始化1000毫秒后才开始调度。
|
||||
例:使用`initialDelay`指定方法在初始化 1000 毫秒后才开始调度。
|
||||
|
||||
```java
|
||||
@Scheduled(initialDelay=1000, fixedRate=5000)
|
||||
public void doSomething() {
|
||||
|
@ -177,7 +212,8 @@ public void doSomething() {
|
|||
}
|
||||
```
|
||||
|
||||
例:使用`cron`表达式指定触发条件为每5000毫秒执行一次。cron规则和Quartz中的cron规则一致。
|
||||
例:使用`cron`表达式指定触发条件为每 5000 毫秒执行一次。cron 规则和 Quartz 中的 cron 规则一致。
|
||||
|
||||
```java
|
||||
@Scheduled(cron="*/5 * * * * MON-FRI")
|
||||
public void doSomething() {
|
||||
|
@ -186,13 +222,15 @@ public void doSomething() {
|
|||
```
|
||||
|
||||
#### 完整范例
|
||||
|
||||
**(1) 启动注解开关,并定义调度器和执行器**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
|
||||
|
@ -208,8 +246,9 @@ public void doSomething() {
|
|||
</beans>
|
||||
```
|
||||
|
||||
**(2) 使用@Scheduler注解来修饰一个要调度的方法**
|
||||
下面的例子展示了@Scheduler注解定义触发条件的不同方式。
|
||||
**(2) 使用@Scheduler 注解来修饰一个要调度的方法**
|
||||
下面的例子展示了@Scheduler 注解定义触发条件的不同方式。
|
||||
|
||||
```java
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -275,9 +314,9 @@ public class ScheduledMgr {
|
|||
}
|
||||
```
|
||||
|
||||
我刻意设置触发方式的间隔都是5s,且方法中均有Thread.sleep(6000);语句。从而确保方法在下一次调度触发时间点前无法完成执行,来看一看各种方式的表现吧。
|
||||
启动spring项目后,spring会扫描`@Component`注解,然后初始化ScheduledMgr。
|
||||
接着,spring会扫描`@Scheduler`注解,初始化调度器。调度器在触发条件匹配的情况下开始工作,输出日志。
|
||||
我刻意设置触发方式的间隔都是 5s,且方法中均有 Thread.sleep(6000);语句。从而确保方法在下一次调度触发时间点前无法完成执行,来看一看各种方式的表现吧。
|
||||
启动 spring 项目后,spring 会扫描`@Component`注解,然后初始化 ScheduledMgr。
|
||||
接着,spring 会扫描`@Scheduler`注解,初始化调度器。调度器在触发条件匹配的情况下开始工作,输出日志。
|
||||
截取部分打印日志来进行分析。
|
||||
|
||||
```
|
||||
|
@ -297,12 +336,14 @@ public class ScheduledMgr {
|
|||
10:59:14.524 myScheduler-4 o.z.n.s.scheduler.ScheduledTasks.testFixedDelay - Current time: 2016-08-31 10:59:14
|
||||
10:59:15.987 myScheduler-6 o.z.n.s.scheduler.ScheduledTasks.testCron - Current time: 2016-08-31 10:59:15
|
||||
```
|
||||
构造方法打印一次,时间点在10:58:46。
|
||||
testFixedRate打印四次,每次间隔6秒。说明,fixedRate不等待上一次调度执行完成,在间隔时间达到时立即执行。
|
||||
testFixedDelay打印三次,每次间隔大于6秒,且时间不固定。说明,fixedDelay等待上一次调度执行成功后,开始计算间隔时间,再执行。
|
||||
testInitialDelay第一次调度时间和构造方法调度时间相隔7秒。说明,initialDelay在初始化后等待指定的延迟时间才开始调度。
|
||||
testCron打印三次,时间间隔并非5秒或6秒,显然,cron等待上一次调度执行成功后,开始计算间隔时间,再执行。
|
||||
此外,可以从日志中看出,打印日志的线程最多只有10个,说明2.1中的调度器线程池配置生效。
|
||||
|
||||
构造方法打印一次,时间点在 10:58:46。
|
||||
testFixedRate 打印四次,每次间隔 6 秒。说明,fixedRate 不等待上一次调度执行完成,在间隔时间达到时立即执行。
|
||||
testFixedDelay 打印三次,每次间隔大于 6 秒,且时间不固定。说明,fixedDelay 等待上一次调度执行成功后,开始计算间隔时间,再执行。
|
||||
testInitialDelay 第一次调度时间和构造方法调度时间相隔 7 秒。说明,initialDelay 在初始化后等待指定的延迟时间才开始调度。
|
||||
testCron 打印三次,时间间隔并非 5 秒或 6 秒,显然,cron 等待上一次调度执行成功后,开始计算间隔时间,再执行。
|
||||
此外,可以从日志中看出,打印日志的线程最多只有 10 个,说明 2.1 中的调度器线程池配置生效。
|
||||
|
||||
## 参考
|
||||
[Spring Framework官方文档](http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/)
|
||||
|
||||
[Spring Framework 官方文档](http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/)
|
|
@ -1,3 +1,20 @@
|
|||
---
|
||||
title: Spring集成Dubbo
|
||||
date: 2017-10-27 17:30:41
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring集成
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- 集成
|
||||
- Dubbo
|
||||
permalink: /pages/274fd7/
|
||||
---
|
||||
|
||||
# Spring 集成 Dubbo
|
||||
|
||||
## ZooKeeper
|
||||
|
@ -86,7 +103,7 @@ dubbo.registry.address=zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181
|
|||
|
||||
```
|
||||
|
||||
> 1. Zookeeper是 Apache Hadoop 的子项目,强度相对较好,建议生产环境使用该注册中心
|
||||
> 1. Zookeeper 是 Apache Hadoop 的子项目,强度相对较好,建议生产环境使用该注册中心
|
||||
> 2. 其中 data 目录需改成你真实输出目录
|
||||
> 3. 其中 data 目录和 server 地址需改成你真实部署机器的信息
|
||||
> 4. 上面 `zoo.cfg` 中的 `dataDir`
|
||||
|
@ -204,7 +221,7 @@ consumer.xml:
|
|||
|
||||
如果注册中心使用 ZooKeeper,可以将 dubbo:registry 改为 zookeeper://127.0.0.1:2181
|
||||
|
||||
### 加载Spring配置,并调用远程服务
|
||||
### 加载 Spring 配置,并调用远程服务
|
||||
|
||||
Consumer.java [3](https://dubbo.gitbooks.io/dubbo-user-book/quick-start.html#fn_3):
|
||||
|
||||
|
@ -240,4 +257,3 @@ public class Consumer {
|
|||
**ZooKeeper**
|
||||
|
||||
[官网](http://zookeeper.apache.org/) | [官方文档](http://zookeeper.apache.org/doc/trunk/)
|
||||
|
|
@ -1,21 +1,37 @@
|
|||
---
|
||||
title: Spring 集成
|
||||
date: 2020-02-26 23:47:47
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring集成
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- 集成
|
||||
permalink: /pages/d6025b/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring 集成
|
||||
|
||||
> 章节主要针对:Spring 与第三方框架、库集成。如:Cache、Scheduling、JMS、JMX 等。
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### [Spring 集成 Dubbo](Spring集成Dubbo.md)
|
||||
|
||||
### [Spring 集成缓存中间件](Spring集成缓存中间件.md)
|
||||
|
||||
### [Spring 集成定时任务中间件](Spring集成定时任务中间件.md)
|
||||
- [Spring 集成缓存中间件](01.Spring集成缓存.md)
|
||||
- [Spring 集成定时任务中间件](02.Spring集成调度器.md)
|
||||
- [Spring 集成 Dubbo](03.Spring集成Dubbo.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
|
@ -25,4 +41,4 @@
|
|||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [spring-tutorial 首页](https://dunwu.github.io/spring-tutorial/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: SpringBoot 之安全快速入门
|
||||
date: 2021-05-13 18:21:56
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring安全
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
- 安全
|
||||
permalink: /pages/568352/
|
||||
---
|
||||
|
||||
# SpringBoot 之安全快速入门
|
||||
|
||||
## QuickStart
|
||||
|
||||
(1)添加依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
(2)添加配置
|
||||
|
||||
```properties
|
||||
spring.security.user.name = root
|
||||
spring.security.user.password = root
|
||||
spring.security.user.roles = USER
|
||||
```
|
||||
|
||||
(3)启动应用后,访问任意路径,都会出现以下页面,提示你先执行登录操作。输入配置的用户名、密码(root/root)即可访问应用页面。
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/image-20191118150326556.png)
|
|
@ -1,4 +1,17 @@
|
|||
# spring 4 升级踩雷指南
|
||||
---
|
||||
title: Spring 4 升级踩雷指南
|
||||
date: 2017-12-15 15:10:32
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring其他
|
||||
tags:
|
||||
- null
|
||||
permalink: /pages/752c6a/
|
||||
---
|
||||
|
||||
# Spring 4 升级踩雷指南
|
||||
|
||||
## 前言
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
---
|
||||
title: SpringBoot 之 banner 定制
|
||||
date: 2018-12-21 23:22:44
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring其他
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/bac2ce/
|
||||
---
|
||||
|
||||
# SpringBoot 之 banner 定制
|
||||
|
||||
## 简介
|
||||
|
||||
Spring Boot 启动时默认会显示以下 LOGO:
|
||||
|
||||
```
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v2.1.1.RELEASE)
|
||||
```
|
||||
|
||||
实际上,Spring Boot 支持自定义 logo 的功能。
|
||||
|
||||
让我们来看看如何实现的。
|
||||
|
||||
只要你在 `resources` 目录下放置名为 `banner.txt`、`banner.gif` 、`banner.jpg` 或 `banner.png` 的文件,Spring Boot 会自动加载,将其作为启动时打印的 logo。
|
||||
|
||||
- 对于文本文件,Spring Boot 会将其直接输出。
|
||||
- 对于图像文件( `banner.gif` 、`banner.jpg` 或 `banner.png` ),Spring Boot 会将图像转为 ASCII 字符,然后输出。
|
||||
|
||||
## 变量
|
||||
|
||||
banner.txt 文件中还可以使用变量来设置字体、颜色、版本号。
|
||||
|
||||
| 变量 | 描述 |
|
||||
| :------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `${application.version}` | `MANIFEST.MF` 中定义的版本。如:`1.0` |
|
||||
| `${application.formatted-version}` | `MANIFEST.MF` 中定义的版本,并添加一个 `v` 前缀。如:`v1.0` |
|
||||
| `${spring-boot.version}` | Spring Boot 版本。如:`2.1.1.RELEASE`. |
|
||||
| `${spring-boot.formatted-version}` | Spring Boot 版本,并添加一个 `v` 前缀。如:`v2.1.1.RELEASE` |
|
||||
| `${Ansi.NAME}` (or `${AnsiColor.NAME}`, `${AnsiBackground.NAME}`, `${AnsiStyle.NAME}`) | ANSI 颜色、字体。更多细节,参考:[`AnsiPropertySource`](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiPropertySource.java)。 |
|
||||
| `${application.title}` | `MANIFEST.MF` 中定义的应用名。 |
|
||||
|
||||
示例:
|
||||
|
||||
在 Spring Boot 项目中的 `resources` 目录下添加一个名为 banner.txt 的文件,内容如下:
|
||||
|
||||
```
|
||||
${AnsiColor.BRIGHT_YELLOW}${AnsiStyle.BOLD}
|
||||
________ ___ ___ ________ ___ __ ___ ___
|
||||
|\ ___ \|\ \|\ \|\ ___ \|\ \ |\ \|\ \|\ \
|
||||
\ \ \_|\ \ \ \\\ \ \ \\ \ \ \ \ \ \ \ \ \\\ \
|
||||
\ \ \ \\ \ \ \\\ \ \ \\ \ \ \ \ __\ \ \ \ \\\ \
|
||||
\ \ \_\\ \ \ \\\ \ \ \\ \ \ \ \|\__\_\ \ \ \\\ \
|
||||
\ \_______\ \_______\ \__\\ \__\ \____________\ \_______\
|
||||
\|_______|\|_______|\|__| \|__|\|____________|\|_______|
|
||||
${AnsiBackground.WHITE}${AnsiColor.RED}${AnsiStyle.UNDERLINE}
|
||||
:: Spring Boot :: (v${spring-boot.version})
|
||||
:: Spring Boot Tutorial :: (v1.0.0)
|
||||
```
|
||||
|
||||
> 注:`${}` 设置字体颜色的变量之间不能换行或空格分隔,否则会导致除最后一个变量外,都不生效。
|
||||
|
||||
启动应用后,控制台将打印如下 logo:
|
||||
|
||||
![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20181221231330.png)
|
||||
推荐两个生成字符画的网站,可以将生成的字符串放入这个`banner.txt` 文件:
|
||||
|
||||
- <http://www.network-science.de/ascii/>
|
||||
- <http://patorjk.com/software/taag/>
|
||||
|
||||
## 配置
|
||||
|
||||
`application.properties` 中与 Banner 相关的配置:
|
||||
|
||||
```properties
|
||||
# banner 模式。有三种模式:console/log/off
|
||||
# console 打印到控制台(通过 System.out)
|
||||
# log - 打印到日志中
|
||||
# off - 关闭打印
|
||||
spring.main.banner-mode = off
|
||||
# banner 文件编码
|
||||
spring.banner.charset = UTF-8
|
||||
# banner 文本文件路径
|
||||
spring.banner.location = classpath:banner.txt
|
||||
# banner 图像文件路径(可以选择 png,jpg,gif 文件)
|
||||
spring.banner.image.location = classpath:banner.gif
|
||||
used).
|
||||
# 图像 banner 的宽度(字符数)
|
||||
spring.banner.image.width = 76
|
||||
# 图像 banner 的高度(字符数)
|
||||
spring.banner.image.height =
|
||||
# 图像 banner 的左边界(字符数)
|
||||
spring.banner.image.margin = 2
|
||||
# 是否将图像转为黑色控制台主题
|
||||
spring.banner.image.invert = false
|
||||
```
|
||||
|
||||
当然,你也可以在 YAML 文件中配置,例如:
|
||||
|
||||
```yml
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
```
|
||||
|
||||
## 编程
|
||||
|
||||
默认,Spring Boot 会注册一个 `SpringBootBanner` 的单例 Bean,用来负责打印 Banner。
|
||||
|
||||
如果想完全个人定制 Banner,可以这么做:先实现 `org.springframework.boot.Banner#printBanner` 接口来自己定制 Banner。在将这个 Banner 通过 `SpringApplication.setBanner(…)` 方法注入 Spring Boot。
|
||||
|
||||
## 示例源码
|
||||
|
||||
> 示例源码:[spring-boot-banner](https://github.com/dunwu/spring-boot-tutorial/tree/master/codes/spring-boot-banner)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring Boot 官方文档之 Customizing the Banner](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-banner)
|
|
@ -0,0 +1,370 @@
|
|||
---
|
||||
title: SpringBoot Actuator 快速入门
|
||||
date: 2022-06-14 20:51:22
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring其他
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/c013cc/
|
||||
---
|
||||
|
||||
# SpringBoot Actuator 快速入门
|
||||
|
||||
[`spring-boot-actuator`](https://github.com/spring-projects/spring-boot/tree/v2.7.0/spring-boot-project/spring-boot-actuator) 模块提供了 Spring Boot 的所有生产就绪功能。启用这些功能的推荐方法是添加 `spring-boot-starter-actuator` 依赖。
|
||||
|
||||
如果是 Maven 项目,添加以下依赖:
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
如果是 Gradle 项目,添加以下声明:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
}
|
||||
```
|
||||
|
||||
## 端点(Endpoint)
|
||||
|
||||
Actuator Endpoint 使 Spring Boot 用户可以监控应用,并和应用进行交互。Spring Boot 内置了许多 端点,并允许用户自定义端点。例如,`health` 端点提供基本的应用健康信息。
|
||||
|
||||
用户可以启用或禁用每个单独的端点并通过 HTTP 或 JMX 暴露它们(使它们可远程访问)。当端点被启用和公开时,它被认为是可用的。内置端点仅在可用时才会自动配置。大多数应用程序选择通过 HTTP 公开。例如,默认情况下,`health` 端点映射到 `/actuator/health`。
|
||||
|
||||
### 启用端点
|
||||
|
||||
默认情况下,除了 `shutdown` 之外的所有端点都已启用。要配置端点的启用,请使用 `management.endpoint.<id>.enabled` 属性。以下示例启用 `shutdown` 端点:
|
||||
|
||||
```properties
|
||||
management.endpoint.shutdown.enabled=true
|
||||
```
|
||||
|
||||
如果您希望端点是明确指定才启用,请将 `management.endpoints.enabled-by-default` 属性设置为 false 并根据需要明确指定启用的端点,以下为示例:
|
||||
|
||||
```properties
|
||||
management.endpoints.enabled-by-default=false
|
||||
management.endpoint.info.enabled=true
|
||||
```
|
||||
|
||||
### 暴露端点
|
||||
|
||||
由于端点可能包含敏感信息,您应该仔细考虑何时暴露它们。下表显示了内置端点的默认曝光:
|
||||
|
||||
| ID | JMX | Web |
|
||||
| :----------------- | :-- | :-- |
|
||||
| `auditevents` | Yes | No |
|
||||
| `beans` | Yes | No |
|
||||
| `caches` | Yes | No |
|
||||
| `conditions` | Yes | No |
|
||||
| `configprops` | Yes | No |
|
||||
| `env` | Yes | No |
|
||||
| `flyway` | Yes | No |
|
||||
| `health` | Yes | Yes |
|
||||
| `heapdump` | N/A | No |
|
||||
| `httptrace` | Yes | No |
|
||||
| `info` | Yes | No |
|
||||
| `integrationgraph` | Yes | No |
|
||||
| `jolokia` | N/A | No |
|
||||
| `logfile` | N/A | No |
|
||||
| `loggers` | Yes | No |
|
||||
| `liquibase` | Yes | No |
|
||||
| `metrics` | Yes | No |
|
||||
| `mappings` | Yes | No |
|
||||
| `prometheus` | N/A | No |
|
||||
| `quartz` | Yes | No |
|
||||
| `scheduledtasks` | Yes | No |
|
||||
| `sessions` | Yes | No |
|
||||
| `shutdown` | Yes | No |
|
||||
| `startup` | Yes | No |
|
||||
| `threaddump` | Yes | No |
|
||||
|
||||
要更改暴露的端点,请使用以下特定于技术的包含和排除属性:
|
||||
|
||||
| Property | Default |
|
||||
| :------------------------------------------ | :------- |
|
||||
| `management.endpoints.jmx.exposure.exclude` | |
|
||||
| `management.endpoints.jmx.exposure.include` | `*` |
|
||||
| `management.endpoints.web.exposure.exclude` | |
|
||||
| `management.endpoints.web.exposure.include` | `health` |
|
||||
|
||||
`include` 属性列出了暴露的端点的 ID。 `exclude` 属性列出了不应暴露的端点的 ID。 `exclude` 属性优先于 `include` 属性。您可以使用端点 ID 列表配置包含和排除属性。
|
||||
|
||||
例如,仅暴露 `health` 和 info 端点,其他端点都不通过 JMX 暴露,可以按如下配置:
|
||||
|
||||
```properties
|
||||
management.endpoints.jmx.exposure.include=health,info
|
||||
```
|
||||
|
||||
注意:`*` 可用于选择所有端点。
|
||||
|
||||
### 安全
|
||||
|
||||
出于安全考虑,只有 `/health` 端点会通过 HTTP 方式暴露。用户可以通过 `management.endpoints.web.exposure.include` 决定哪些端点可以通过 HTTP 方式暴露。
|
||||
|
||||
如果 Spring Security 在类路径上并且不存在其他 `WebSecurityConfigurerAdapter` 或 `SecurityFilterChain` bean,则除 `/health` 之外的所有 actuator 都由 Spring Boot 自动启用安全控制。如果用户自定义了 `WebSecurityConfigurerAdapter` 或 `SecurityFilterChain` bean,Spring Boot 不再启用安全控制,由用户自行控制访问规则。
|
||||
|
||||
如果您希望为 HTTP 端点定义安全控制(例如,只允许具有特定角色的用户访问它们),Spring Boot 提供了一些方便的 `RequestMatcher` 对象,您可以将它们与 Spring Security 结合使用。
|
||||
|
||||
下面是一个典型的 Spring Security 配置示例:
|
||||
|
||||
```java
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class MySecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint())
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
|
||||
http.httpBasic();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
前面的示例使用 EndpointRequest.toAnyEndpoint() 将请求匹配到任何端点,然后确保所有端点都具有 ENDPOINT_ADMIN 角色。 EndpointRequest 上还提供了其他几种匹配器方法。
|
||||
|
||||
如果希望无需身份验证即可访问所有执行器端点。可以通过更改 management.endpoints.web.exposure.include 属性来做到这一点,如下所示:
|
||||
|
||||
```properties
|
||||
management.endpoints.web.exposure.include=*
|
||||
```
|
||||
|
||||
此外,如果存在 Spring Security,您将需要添加自定义安全配置,以允许未经身份验证的访问端点,如以下示例所示:
|
||||
|
||||
```java
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class MySecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint())
|
||||
.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
由于 Spring Boot 依赖于 Spring Security 的默认设置,因此 CSRF 保护默认开启。这意味着在使用默认安全配置时,需要 POST(关闭和记录器端点)、PUT 或 DELETE 的执行器端点会收到 403(禁止)错误。
|
||||
|
||||
> 建议仅在创建非浏览器客户端使用的服务时完全禁用 CSRF 保护。
|
||||
|
||||
### 配置端点
|
||||
|
||||
端点会自动缓存对不带任何参数的读操作的响应数据。要配置端点缓存响应的时间量,请使用其 `cache.time-to-live` 属性。以下示例将 bean 端点缓存的生存时间设置为 10 秒:
|
||||
|
||||
```properties
|
||||
management.endpoint.beans.cache.time-to-live=10s
|
||||
```
|
||||
|
||||
### Actuator Web 端点的超媒体
|
||||
|
||||
Spring Boot Actuator 中内置了一个“发现页面”端点,其中包含了所有端点的链接。默认情况下,“发现页面”在 `/actuator` 上可用。
|
||||
|
||||
要禁用“发现页面”,请将以下属性添加到您的应用程序属性中:
|
||||
|
||||
```properties
|
||||
management.endpoints.web.discovery.enabled=false
|
||||
```
|
||||
|
||||
配置自定义管理上下文路径后,“发现页面”会自动从 `/actuator` 移动到应用管理上下文的根目录。例如,如果管理上下文路径是 `/management`,则发现页面可从 `/management` 获得。当管理上下文路径设置为 / 时,发现页面被禁用以防止与其他映射发生冲突的可能性。
|
||||
|
||||
### 跨域支持
|
||||
|
||||
CORS 是一种 W3C 规范,可让用户以灵活的方式指定授权哪种跨域请求。如果使用 Spring MVC 或 Spring WebFlux,则可以配置 Actuator 的 Web 端点以支持此类场景。
|
||||
|
||||
CORS 支持默认是禁用的,只有在设置 `management.endpoints.web.cors.allowed-origins` 属性后才会启用。以下配置允许来自 example.com 域的 GET 和 POST 调用:
|
||||
|
||||
```properties
|
||||
management.endpoints.web.cors.allowed-origins=https://example.com
|
||||
management.endpoints.web.cors.allowed-methods=GET,POST
|
||||
```
|
||||
|
||||
### 自定义端点
|
||||
|
||||
如果添加带有 `@Endpoint` 注释的 `@Bean`,则任何带有 `@ReadOperation`、`@WriteOperation` 或 `@DeleteOperation` 注释的方法都会自动通过 JMX 公开,并且在 Web 应用程序中,也可以通过 HTTP 公开。可以使用 Jersey、Spring MVC 或 Spring WebFlux 通过 HTTP 公开端点。如果 Jersey 和 Spring MVC 都可用,则使用 Spring MVC。
|
||||
|
||||
以下示例公开了一个返回自定义对象的读取操作:
|
||||
|
||||
```java
|
||||
@ReadOperation
|
||||
public CustomData getData() {
|
||||
return new CustomData("test", 5);
|
||||
}
|
||||
```
|
||||
|
||||
您还可以使用 `@JmxEndpoint` 或 `@WebEndpoint` 编写特定技术的端点。这些端点仅限于各自的技术。例如,`@WebEndpoint` 仅通过 HTTP 而不是通过 JMX 公开。
|
||||
|
||||
您可以使用 `@EndpointWebExtension` 和 `@EndpointJmxExtension` 编写特定技术的扩展。这些注释让您可以提供特定技术的操作来扩充现有端点。
|
||||
|
||||
最后,如果您需要访问 Web 框架的功能,您可以实现 servlet 或 Spring `@Controller` 和 `@RestController` 端点,但代价是它们无法通过 JMX 或使用不同的 Web 框架获得。
|
||||
|
||||
## 通过 HTTP 进行监控和管理
|
||||
|
||||
### 自定义管理端点路径
|
||||
|
||||
如果是 Web 应用,Spring Boot Actuator 会自动将所有启用的端点通过 HTTP 方式暴露。默认约定是使用前缀为 `/actuator` 的端点的 id 作为 URL 路径。例如,健康被暴露为 `/actuator/health`。
|
||||
|
||||
有时,自定义管理端点的前缀很有用。例如,您的应用程序可能已经将 `/actuator` 用于其他目的。您可以使用 `management.endpoints.web.base-path` 属性更改管理端点的前缀,如以下示例所示:
|
||||
|
||||
```properties
|
||||
management.endpoints.web.base-path=/manage
|
||||
```
|
||||
|
||||
该示例将端点从 `/actuator/{id}` 更改为 `/manage/{id}`(例如,`/manage/info`)。
|
||||
|
||||
### 自定义管理服务器端口
|
||||
|
||||
```properties
|
||||
management.server.port=8081
|
||||
```
|
||||
|
||||
### 配置 SSL
|
||||
|
||||
当配置为使用自定义端口时,还可以使用各种 `management.server.ssl.*` 属性为管理服务器配置自己的 SSL。例如,这样做可以让管理服务器在主应用程序使用 HTTPS 时通过 HTTP 可用,如以下属性设置所示:
|
||||
|
||||
```properties
|
||||
server.port=8443
|
||||
server.ssl.enabled=true
|
||||
server.ssl.key-store=classpath:store.jks
|
||||
server.ssl.key-password=secret
|
||||
management.server.port=8080
|
||||
management.server.ssl.enabled=false
|
||||
```
|
||||
|
||||
或者,主服务器和管理服务器都可以使用 SSL,但使用不同的密钥存储,如下所示:
|
||||
|
||||
```properties
|
||||
server.port=8443
|
||||
server.ssl.enabled=true
|
||||
server.ssl.key-store=classpath:main.jks
|
||||
server.ssl.key-password=secret
|
||||
management.server.port=8080
|
||||
management.server.ssl.enabled=true
|
||||
management.server.ssl.key-store=classpath:management.jks
|
||||
management.server.ssl.key-password=secret
|
||||
```
|
||||
|
||||
### 自定义管理服务器地址
|
||||
|
||||
```properties
|
||||
management.server.port=8081
|
||||
management.server.address=127.0.0.1
|
||||
```
|
||||
|
||||
### 禁用 HTTP 端点
|
||||
|
||||
如果您不想通过 HTTP 方式暴露端点,可以将管理端口设置为 -1,如以下示例所示:
|
||||
|
||||
```properties
|
||||
management.server.port=-1
|
||||
```
|
||||
|
||||
也可以通过使用 management.endpoints.web.exposure.exclude 属性来实现这一点,如以下示例所示:
|
||||
|
||||
```properties
|
||||
management.endpoints.web.exposure.exclude=*
|
||||
```
|
||||
|
||||
## 通过 JMX 进行监控和管理
|
||||
|
||||
Java 管理扩展 (JMX) 提供了一种标准机制来监视和管理应用程序。默认情况下,此功能未启用。您可以通过将 `spring.jmx.enabled` 配置属性设置为 true 来打开它。 Spring Boot 将最合适的 `MBeanServer` 暴露为 ID 为 `mbeanServer` 的 bean。使用 Spring JMX 注释(`@ManagedResource`、`@ManagedAttribute` 或 `@ManagedOperation`)注释的任何 bean 都会暴露给它。
|
||||
|
||||
如果您的平台提供标准 `MBeanServer`,则 Spring Boot 会使用该标准并在必要时默认使用 VM `MBeanServer`。如果一切都失败了,则创建一个新的 `MBeanServer`。
|
||||
|
||||
有关更多详细信息,请参阅 [`JmxAutoConfiguration`](https://github.com/spring-projects/spring-boot/tree/v2.7.0/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.java) 类。
|
||||
|
||||
默认情况下,Spring Boot 还将管理端点公开为 `org.springframework.boot` 域下的 JMX MBean。要完全控制 JMX 域中的端点注册,请考虑注册您自己的 `EndpointObjectNameFactory` 实现。
|
||||
|
||||
### 定制化 MBean Names
|
||||
|
||||
MBean 的名称通常由端点的 id 生成。例如,健康端点公开为 `org.springframework.boot:type=Endpoint,name=Health`。
|
||||
|
||||
如果您的应用程序包含多个 Spring `ApplicationContext`,您可能会发现名称冲突。要解决此问题,您可以将 `spring.jmx.unique-names` 属性设置为 true,以便 MBean 名称始终是唯一的。
|
||||
|
||||
如果需要定制,跨域按如下配置:
|
||||
|
||||
```properties
|
||||
spring.jmx.unique-names=true
|
||||
management.endpoints.jmx.domain=com.example.myapp
|
||||
```
|
||||
|
||||
### 禁用 JMX 端点
|
||||
|
||||
想禁用 JMX 端点,可以按如下配置:
|
||||
|
||||
```
|
||||
management.endpoints.jmx.exposure.exclude=*
|
||||
```
|
||||
|
||||
### 将 Jolokia 用于基于 HTTP 的 JMX
|
||||
|
||||
Jolokia 是一个 JMX-HTTP 的桥接工具,它提供了另一种访问 JMX bean 的方法。要使用 Jolokia,需要先添加依赖:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.jolokia</groupId>
|
||||
<artifactId>jolokia-core</artifactId>
|
||||
</dependency
|
||||
```
|
||||
|
||||
然后,您可以通过将 `jolokia` 或 `*` 添加到 `Management.Endpoints.web.exposure.include` 属性来暴露 Jolokia 端点。然后,您可以在管理 HTTP 服务器上使用 `/actuator/jolokia` 访问它。
|
||||
|
||||
## 日志
|
||||
|
||||
Spring Boot Actuator 支持查看和配置应用日志级别。
|
||||
|
||||
日志级别的可选值如下:
|
||||
|
||||
- `TRACE`
|
||||
- `DEBUG`
|
||||
- `INFO`
|
||||
- `WARN`
|
||||
- `ERROR`
|
||||
- `FATAL`
|
||||
- `OFF`
|
||||
- `null`
|
||||
|
||||
`null` 表示没有显式配置。
|
||||
|
||||
## 指标
|
||||
|
||||
## 审计
|
||||
|
||||
Spring Boot Actuator 支持简单的审计功能。如果应用中启用了 Spring Security,Spring Boot Actuator 就会发布安全事件(如:“身份验证成功”、“失败”和“访问被拒绝”异常)。
|
||||
|
||||
可以通过在应用的配置中提供 `AuditEventRepository` 类型的 bean 来启用审计。为方便起见,Spring Boot 提供了一个 `InMemoryAuditEventRepository`。 `InMemoryAuditEventRepository` 的功能有限,建议仅将其用于开发环境。
|
||||
|
||||
如果要自定义安全事件,可以提供 `AbstractAuthenticationAuditListener` 和 `AbstractAuthorizationAuditListener` 实现。
|
||||
|
||||
此外,还可以将审计服务用于业务活动。为此,要么将 `AuditEventRepository` bean 注入组件并直接使用它,要么使用 Spring `ApplicationEventPublisher` 发布 `AuditApplicationEvent`(通过实现 `ApplicationEventPublisherAware`)。
|
||||
|
||||
## HTTP 追踪
|
||||
|
||||
用户可以通过在应用中提供 `HttpTraceRepository` 类型的 bean 来启用 HTTP 跟踪。Spring Boot 提供了内置的 `InMemoryHttpTraceRepository`,它可以存储最近 100 次(默认)请求-响应的追踪数据。与其他 HTTP 追踪解决方案相比,`InMemoryHttpTraceRepository` 比较受限,建议仅用于开发环境。对于生产环境,建议使用 Zipkin 或 Spring Cloud Sleuth。
|
||||
|
||||
或者,可以自定义 `HttpTraceRepository`。
|
||||
|
||||
## 处理监控
|
||||
|
||||
在 spring-boot 模块中,您可以找到两个类来创建对进程监控有用的文件:
|
||||
|
||||
- `ApplicationPidFileWriter` 创建一个包含应用程序 PID 的文件(默认情况下,在应用程序目录中,文件名为 `application.pid`)。
|
||||
- `WebServerPortFileWriter` 创建一个文件(或多个文件),其中包含正在运行的 Web 服务器的端口(默认情况下,在应用程序目录中,文件名为 `application.port`)。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Spring Boot 官方文档之 Production-ready Features](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator)
|
|
@ -1,15 +1,34 @@
|
|||
---
|
||||
title: Spring 其他
|
||||
date: 2020-02-26 23:48:06
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- Spring其他
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/6bb8c1/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring 其他
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### [Spring4 升级踩雷指南](spring4-upgrade.md)
|
||||
- [Spring4 升级](01.Spring4升级.md)
|
||||
- [SpringBoot 之 banner](21.SpringBoot之banner.md)
|
||||
- [SpringBoot 之 Actuator](22.SpringBoot之Actuator.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
|
@ -19,4 +38,4 @@
|
|||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [spring-tutorial 首页](https://dunwu.github.io/spring-tutorial/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
title: Spring & Spring Boot
|
||||
date: 2022-06-14 09:37:30
|
||||
categories:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
tags:
|
||||
- Java
|
||||
- 框架
|
||||
- Spring
|
||||
- SpringBoot
|
||||
permalink: /pages/a1a3d3/
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# Spring & Spring Boot
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### 综合
|
||||
|
||||
- [Spring 概述](00.Spring综合/01.Spring概述.md)
|
||||
- [SpringBoot 知识图谱](00.Spring综合/21.SpringBoot知识图谱.md)
|
||||
- [SpringBoot 基本原理](00.Spring综合/22.SpringBoot基本原理.md)
|
||||
- [Spring 常见面试题](00.Spring综合/99.Spring常见面试题.md)
|
||||
|
||||
### 核心
|
||||
|
||||
- [Spring 依赖注入(IoC)](01.Spring核心/01.Spring依赖注入.md)
|
||||
- [Spring Bean 生命周期](01.Spring核心/02.Spring生命周期.md)
|
||||
- [Spring AOP](01.Spring核心/03.SpringAop.md)
|
||||
- [Spring 资源管理](01.Spring核心/04.Spring资源管理.md)
|
||||
- [SpringBoot 教程之快速入门](01.Spring核心/21.SpringBoot之快速入门.md)
|
||||
- [SpringBoot 之属性加载](01.Spring核心/22.SpringBoot之属性加载.md)
|
||||
- [SpringBoot 之 Profile](01.Spring核心/23.SpringBoot之Profile.md)
|
||||
|
||||
### 数据
|
||||
|
||||
- [SpringBoot 之 JDBC](02.Spring数据/21.SpringBoot之JDBC.md)
|
||||
- [SpringBoot 之 Mybatis](02.Spring数据/22.SpringBoot之Mybatis.md)
|
||||
- [SpringBoot 之 MongoDB](02.Spring数据/23.SpringBoot之MongoDB.md)
|
||||
- [SpringBoot 之 Elasticsearch](02.Spring数据/24.SpringBoot之Elasticsearch.md)
|
||||
|
||||
### Web
|
||||
|
||||
- [Spring WebMvc](03.SpringWeb/01.SpringWebMvc.md)
|
||||
- [SpringBoot 之应用 EasyUI](03.SpringWeb/21.SpringBoot之应用EasyUI.md)
|
||||
|
||||
### IO
|
||||
|
||||
- [SpringBoot 之异步请求](04.SpringIO/01.SpringBoot之异步请求.md)
|
||||
- [SpringBoot 之 Json](04.SpringIO/02.SpringBoot之Json.md)
|
||||
- [SpringBoot 之邮件](04.SpringIO/03.SpringBoot之邮件.md)
|
||||
|
||||
### 集成
|
||||
|
||||
- [Spring 集成缓存中间件](05.Spring集成/01.Spring集成缓存.md)
|
||||
- [Spring 集成定时任务中间件](05.Spring集成/02.Spring集成调度器.md)
|
||||
- [Spring 集成 Dubbo](05.Spring集成/03.Spring集成Dubbo.md)
|
||||
|
||||
### 其他
|
||||
|
||||
- [Spring4 升级](99.Spring其他/01.Spring4升级.md)
|
||||
- [SpringBoot 之 banner](99.Spring其他/21.SpringBoot之banner.md)
|
||||
- [SpringBoot 之 Actuator](99.Spring其他/22.SpringBoot之Actuator.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《 Spring 实战(第 5 版)》](https://book.douban.com/subject/34949443/)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
- [跟我学 Spring3](http://jinnianshilongnian.iteye.com/blog/1482071)
|
||||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
archivesPage: true
|
||||
title: 归档
|
||||
permalink: /archives/
|
||||
article: false
|
||||
---
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
categoriesPage: true
|
||||
title: 分类
|
||||
permalink: /categories/
|
||||
article: false
|
||||
---
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
tagsPage: true
|
||||
title: 标签
|
||||
permalink: /tags/
|
||||
article: false
|
||||
---
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
home: true
|
||||
heroImage: https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo-200.png
|
||||
heroImage: img/bg.gif
|
||||
heroText: SPRING-TUTORIAL
|
||||
tagline: ☕ spring-tutorial 是一个以简单范例来展示 spring 在 web 开发中的各种应用的教程。
|
||||
actionLink: /
|
||||
bannerBg: none
|
||||
postList: none
|
||||
footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu
|
||||
---
|
||||
|
||||
|
@ -19,43 +20,50 @@ footer: CC-BY-SA-4.0 Licensed | Copyright © 2018-Now Dunwu
|
|||
|
||||
### 综合
|
||||
|
||||
- [Spring 概述](summary/Spring概述.md)
|
||||
- [Spring 常见面试题](summary/Spring常见面试题.md)
|
||||
- [Spring 概述](01.Java/13.框架/01.Spring/00.Spring综合/01.Spring概述.md)
|
||||
- [SpringBoot 知识图谱](01.Java/13.框架/01.Spring/00.Spring综合/21.SpringBoot知识图谱.md)
|
||||
- [SpringBoot 基本原理](01.Java/13.框架/01.Spring/00.Spring综合/22.SpringBoot基本原理.md)
|
||||
- [Spring 常见面试题](01.Java/13.框架/01.Spring/00.Spring综合/99.Spring常见面试题.md)
|
||||
|
||||
### 核心
|
||||
|
||||
> [核心](core/README.md) 章节主要针对:Spring 框架的核心技术。如;IOC 依赖注入、AOP、数据绑定等。
|
||||
|
||||
- [Spring 依赖注入(IoC)](core/Spring依赖注入.md)
|
||||
- [Spring 生命周期](core/Spring生命周期.md)
|
||||
- [Spring AOP](core/spring-aop.md)
|
||||
- [Spring 资源管理](core/Spring资源管理.md)
|
||||
- [Spring 依赖注入(IoC)](01.Java/13.框架/01.Spring/01.Spring核心/01.Spring依赖注入.md)
|
||||
- [Spring Bean 生命周期](01.Java/13.框架/01.Spring/01.Spring核心/02.Spring生命周期.md)
|
||||
- [Spring AOP](01.Java/13.框架/01.Spring/01.Spring核心/03.SpringAop.md)
|
||||
- [Spring 资源管理](01.Java/13.框架/01.Spring/01.Spring核心/04.Spring资源管理.md)
|
||||
- [SpringBoot 教程之快速入门](01.Java/13.框架/01.Spring/01.Spring核心/21.SpringBoot之快速入门.md)
|
||||
- [SpringBoot 之属性加载](01.Java/13.框架/01.Spring/01.Spring核心/22.SpringBoot之属性加载.md)
|
||||
- [SpringBoot 之 Profile](01.Java/13.框架/01.Spring/01.Spring核心/23.SpringBoot之Profile.md)
|
||||
|
||||
### 数据
|
||||
|
||||
> [数据](data/README.md) 章节主要针对:Spring 在数据库领域的应用。如:JDBC、ORM、事务等。
|
||||
|
||||
- [Spring 的数据访问策略](data/Spring数据访问策略.md)
|
||||
- [Spring 中使用 JDBC 访问数据](data/Spring中使用JDBC访问数据.md)
|
||||
- [Spring 事务管理](data/Spring事务管理.md)
|
||||
- [SpringBoot 之 JDBC](01.Java/13.框架/01.Spring/02.Spring数据/21.SpringBoot之JDBC.md)
|
||||
- [SpringBoot 之 Mybatis](01.Java/13.框架/01.Spring/02.Spring数据/22.SpringBoot之Mybatis.md)
|
||||
- [SpringBoot 之 MongoDB](01.Java/13.框架/01.Spring/02.Spring数据/23.SpringBoot之MongoDB.md)
|
||||
- [SpringBoot 之 Elasticsearch](01.Java/13.框架/01.Spring/02.Spring数据/24.SpringBoot之Elasticsearch.md)
|
||||
|
||||
### Web
|
||||
|
||||
> [Web](web/README.md) 章节主要针对:Spring 在 web 领域的应用。如:Spring MVC、WebSocket 等。
|
||||
- [Spring WebMvc](01.Java/13.框架/01.Spring/03.SpringWeb/01.SpringWebMvc.md)
|
||||
- [SpringBoot 之应用 EasyUI](01.Java/13.框架/01.Spring/03.SpringWeb/21.SpringBoot之应用EasyUI.md)
|
||||
|
||||
- [Spring MVC](web/spring-mvc.md)
|
||||
### IO
|
||||
|
||||
- [SpringBoot 之异步请求](01.Java/13.框架/01.Spring/04.SpringIO/01.SpringBoot之异步请求.md)
|
||||
- [SpringBoot 之 Json](01.Java/13.框架/01.Spring/04.SpringIO/02.SpringBoot之Json.md)
|
||||
- [SpringBoot 之邮件](01.Java/13.框架/01.Spring/04.SpringIO/03.SpringBoot之邮件.md)
|
||||
|
||||
### 集成
|
||||
|
||||
> [集成](integration/README.md) 章节主要针对:Spring 与第三方框架、库集成。如:Cache、Scheduling、JMS、JMX 等。
|
||||
|
||||
- [Spring 集成 Dubbo](integration/Spring集成Dubbo.md)
|
||||
- [Spring 集成缓存中间件](integration/Spring集成缓存中间件.md)
|
||||
- [Spring 集成定时任务中间件](integration/Spring集成定时任务中间件.md)
|
||||
- [Spring 集成缓存中间件](01.Java/13.框架/01.Spring/05.Spring集成/01.Spring集成缓存.md)
|
||||
- [Spring 集成定时任务中间件](01.Java/13.框架/01.Spring/05.Spring集成/02.Spring集成调度器.md)
|
||||
- [Spring 集成 Dubbo](01.Java/13.框架/01.Spring/05.Spring集成/03.Spring集成Dubbo.md)
|
||||
|
||||
### 其他
|
||||
|
||||
- [spring 4 升级踩雷指南](others/spring4-upgrade.md)
|
||||
- [Spring4 升级](01.Java/13.框架/01.Spring/99.Spring其他/01.Spring4升级.md)
|
||||
- [SpringBoot 之 banner](01.Java/13.框架/01.Spring/99.Spring其他/21.SpringBoot之banner.md)
|
||||
- [SpringBoot 之 Actuator](01.Java/13.框架/01.Spring/99.Spring其他/22.SpringBoot之Actuator.md)
|
||||
|
||||
## 💻 示例
|
||||
|
||||
|
@ -100,9 +108,9 @@ $ mvn jetty:run -Dmaven.test.skip=true
|
|||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- [Spring Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《 Spring 实战(第 5 版)》](https://book.douban.com/subject/34949443/)
|
||||
- [《 Spring 实战(第 5 版)》](https://book.douban.com/subject/34949443/)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
|
@ -110,20 +118,4 @@ $ mvn jetty:run -Dmaven.test.skip=true
|
|||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [SPRING-TUTORIAL 首页](https://github.com/dunwu/spring-tutorial) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
||||
|
||||
> 你可能会感兴趣:
|
||||
|
||||
- [Java 教程](https://github.com/dunwu/java-tutorial) 📚
|
||||
- [JavaCore 教程](https://dunwu.github.io/javacore/) 📚
|
||||
- [JavaTech 教程](https://dunwu.github.io/javatech/) 📚
|
||||
- [Spring 教程](https://dunwu.github.io/spring-tutorial/) 📚
|
||||
- [Spring Boot 教程](https://dunwu.github.io/spring-boot-tutorial/) 📚
|
||||
- [数据库教程](https://dunwu.github.io/db-tutorial/) 📚
|
||||
- [数据结构和算法教程](https://dunwu.github.io/algorithm-tutorial/) 📚
|
||||
- [Linux 教程](https://dunwu.github.io/linux-tutorial/) 📚
|
||||
- [Nginx 教程](https://github.com/dunwu/nginx-tutorial/) 📚
|
||||
|
||||
## License
|
||||
|
||||
本博客所有文章除特别声明外,均采用 [![License: CC BY-NC-SA 4.0](https://licensebuttons.net/l/by-nc-sa/4.0/80x15.png)](https://creativecommons.org/licenses/by-nc-sa/4.0/) 许可协议。
|
||||
◾ 💧 [钝悟的 IT 知识图谱](https://dunwu.github.io/waterdrop/) ◾ 🎯 [钝悟的博客](https://dunwu.github.io/blog/) ◾
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
# Spring 核心
|
||||
|
||||
> 章节主要针对:Spring 框架的核心技术。如;IOC 依赖注入、AOP、数据绑定等。
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### [Spring 依赖注入(IoC)](Spring依赖注入.md)
|
||||
|
||||
### [Spring Bean 生命周期](Spring生命周期.md)
|
||||
|
||||
### Spring 循环依赖
|
||||
|
||||
### [Spring AOP](spring-aop.md)
|
||||
|
||||
### [Spring 资源管理](Spring资源管理.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
- [跟我学 Spring3](http://jinnianshilongnian.iteye.com/blog/1482071)
|
||||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [spring-tutorial 首页](https://dunwu.github.io/spring-tutorial/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -1,26 +0,0 @@
|
|||
# Spring Aop 应用
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
引入 jar 包
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
- @Pointcut
|
||||
- 前置通知(@Before):在目标方法被调用之前调用通知功能;
|
||||
- 后置通知(@After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
|
||||
- 返回通知(@AfterReturning):在目标方法成功执行之后调用通知;
|
||||
- 异常通知(@AfterThrowing):在目标方法抛出异常后调用通知。
|
||||
- @Around
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [《 Spring 实战(第 4 版)》](https://item.jd.com/11899370.html)
|
|
@ -1,115 +0,0 @@
|
|||
# Spring 依赖查找
|
||||
|
||||
## 单一类型依赖查找
|
||||
|
||||
单一类型依赖查找接口- BeanFactory
|
||||
|
||||
- 根据 Bean 名称查找
|
||||
- getBean(String)
|
||||
- Spring 2.5 覆盖默认参数:getBean(String,Object...)
|
||||
- 根据 Bean 类型查找
|
||||
- Bean 实时查找
|
||||
- Spring 3.0 getBean(Class)
|
||||
- Spring 4.1 覆盖默认参数:getBean(Class,Object...)
|
||||
- Spring 5.1 Bean 延迟查找
|
||||
- getBeanProvider(Class)
|
||||
- getBeanProvider(ResolvableType)
|
||||
- 根据 Bean 名称 + 类型查找:getBean(String,Class)
|
||||
|
||||
## 集合类型依赖查找
|
||||
|
||||
集合类型依赖查找接口- ListableBeanFactory
|
||||
|
||||
- 根据 Bean 类型查找
|
||||
- 获取同类型 Bean 名称列表
|
||||
- getBeanNamesForType(Class)
|
||||
- Spring 4.2 getBeanNamesForType(ResolvableType)
|
||||
- 获取同类型 Bean 实例列表
|
||||
- getBeansOfType(Class) 以及重载方法
|
||||
- 通过注解类型查找
|
||||
|
||||
- Spring 3.0 获取标注类型 Bean 名称列表
|
||||
|
||||
- getBeanNamesForAnnotation(Class<? extends Annotation>)
|
||||
|
||||
- Spring 3.0 获取标注类型 Bean 实例列表
|
||||
|
||||
- getBeansWithAnnotation(Class<? extends Annotation>)
|
||||
|
||||
- Spring 3.0 获取指定名称+ 标注类型 Bean 实例
|
||||
|
||||
- findAnnotationOnBean(String,Class<? extends Annotation>)
|
||||
|
||||
## 层次性依赖查找
|
||||
|
||||
层次性依赖查找接口- HierarchicalBeanFactory
|
||||
|
||||
- 双亲 BeanFactory:getParentBeanFactory()
|
||||
- 层次性查找
|
||||
- 根据 Bean 名称查找
|
||||
- 基于 containsLocalBean 方法实现
|
||||
- 根据 Bean 类型查找实例列表
|
||||
- 单一类型:BeanFactoryUtils#beanOfType
|
||||
- 集合类型:BeanFactoryUtils#beansOfTypeIncludingAncestors
|
||||
- 根据 Java 注解查找名称列表
|
||||
- BeanFactoryUtils#beanNamesForTypeIncludingAncestors
|
||||
|
||||
## 延迟依赖查找
|
||||
|
||||
Bean 延迟依赖查找接口
|
||||
|
||||
- org.springframework.beans.factory.ObjectFactory
|
||||
- org.springframework.beans.factory.ObjectProvider(Spring 5 对 Java 8 特性扩展)
|
||||
- 函数式接口
|
||||
- getIfAvailable(Supplier)
|
||||
- ifAvailable(Consumer)
|
||||
- Stream 扩展- stream()
|
||||
|
||||
## 安全依赖查找
|
||||
|
||||
| 依赖查找类型 | 代表实现 | 是否安全 |
|
||||
| ------------ | ---------------------------------- | -------- |
|
||||
| 单一类型查找 | BeanFactory#getBean | 否 |
|
||||
| | ObjectFactory#getObject | 否 |
|
||||
| | ObjectProvider#getIfAvailable | 是 |
|
||||
| | | |
|
||||
| 集合类型查找 | ListableBeanFactory#getBeansOfType | 是 |
|
||||
| | ObjectProvider#stream | 是 |
|
||||
|
||||
注意:层次性依赖查找的安全性取决于其扩展的单一或集合类型的 BeanFactory 接口
|
||||
|
||||
## 内建可查找的依赖
|
||||
|
||||
AbstractApplicationContext 内建可查找的依赖
|
||||
|
||||
| Bean | 名称 Bean | 实例使用场景 |
|
||||
| --------------------------- | -------------------------------- | ----------------------- |
|
||||
| environment | Environment 对象 | 外部化配置以及 Profiles |
|
||||
| systemProperties | java.util.Properties 对象 | Java 系统属性 |
|
||||
| systemEnvironment | java.util.Map 对象 | 操作系统环境变量 |
|
||||
| messageSource | MessageSource 对象 | 国际化文案 |
|
||||
| lifecycleProcessor | LifecycleProcessor 对象 | Lifecycle Bean 处理器 |
|
||||
| applicationEventMulticaster | ApplicationEventMulticaster 对象 | Spring 事件广播器 |
|
||||
|
||||
注解驱动 Spring 应用上下文内建可查找的依赖(部分)
|
||||
|
||||
| Bean 名称 | Bean 实例 | 使用场景 |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------------------- | ----------------------------------------------------- |
|
||||
| org.springframework.context.annotation.internalConfigurationAnnotationProcessor | ConfigurationClassPostProcessor 对象 | 处理 Spring 配置类 |
|
||||
| org.springframework.context.annotation.internalAutowiredAnnotationProcessor | AutowiredAnnotationBeanPostProcessor 对象 | 处理@Autowired 以及@Value 注解 |
|
||||
| org.springframework.context.annotation.internalCommonAnnotationProcessor | CommonAnnotationBeanPostProcessor 对象 | (条件激活)处理 JSR-250 注解,如@PostConstruct 等 |
|
||||
| org.springframework.context.event.internalEventListenerProcessor | EventListenerMethodProcessor 对象 | 处理标注@EventListener 的 Spring 事件监听方法 |
|
||||
| org.springframework.context.event.internalEventListenerFactory | DefaultEventListenerFactory 对象 | @EventListener 事件监听方法适配为 ApplicationListener |
|
||||
| org.springframework.context.annotation.internalPersistenceAnnotationProcessor | PersistenceAnnotationBeanPostProcessor 对象 | (条件激活)处理 JPA 注解场景 |
|
||||
|
||||
## 依赖查找中的经典异常
|
||||
|
||||
BeansException 子类型
|
||||
|
||||
| 异常类型 | 触发条件(举例) | 场景举例 |
|
||||
| ------------------------------- | ------------------------------------------ | ------------------------------------------ |
|
||||
| NoSuchBeanDefinitionException | 当查找 Bean 不存在于 IoC 容器时 | BeanFactory#getBeanObjectFactory#getObject |
|
||||
| NoUniqueBeanDefinitionException | 类型依赖查找时,IoC 容器存在多个 Bean 实例 | BeanFactory#getBean(Class) |
|
||||
| BeanInstantiationException | 当 Bean 所对应的类型非具体类时 | BeanFactory#getBean |
|
||||
| BeanCreationException | 当 Bean 初始化过程中 | Bean 初始化方法执行异常时 |
|
||||
| BeanDefinitionStoreException | 当 BeanDefinition 配置元信息非法时 | XML 配置资源无法打开时 |
|
|
@ -1 +0,0 @@
|
|||
# Spring 循环依赖
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"name": "spring-tutorial",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf dist && rimraf .temp",
|
||||
"build": "npm run clean && vuepress build ./ --temp .temp",
|
||||
"start": "vuepress dev ./ --temp .temp",
|
||||
"lint": "markdownlint -r markdownlint-rule-emphasis-style -c ./.markdownlint.json **/*.md -i node_modules",
|
||||
"lint:fix": "markdownlint -f -r markdownlint-rule-emphasis-style -c ./.markdownlint.json **/*.md -i node_modules",
|
||||
"show-help": "vuepress --help",
|
||||
"view-info": "vuepress view-info ./ --temp .temp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vuepress/plugin-active-header-links": "^1.8.2",
|
||||
"@vuepress/plugin-back-to-top": "^1.8.2",
|
||||
"@vuepress/plugin-medium-zoom": "^1.8.2",
|
||||
"@vuepress/plugin-pwa": "^1.8.2",
|
||||
"@vuepress/theme-vue": "^1.8.2",
|
||||
"markdownlint-cli": "^0.25.0",
|
||||
"markdownlint-rule-emphasis-style": "^1.0.1",
|
||||
"rimraf": "^3.0.1",
|
||||
"vue-toasted": "^1.1.25",
|
||||
"vuepress": "^1.8.2",
|
||||
"vuepress-plugin-flowchart": "^1.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.29.1"
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
# Spring 综述
|
||||
|
||||
## 📖 内容
|
||||
|
||||
### [Spring 概述](Spring概述.md)
|
||||
|
||||
### [Spring 常见面试题](Spring常见面试题.md)
|
||||
|
||||
## 📚 资料
|
||||
|
||||
- **官方**
|
||||
- [Spring 官网](https://spring.io/)
|
||||
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
|
||||
- [spring-framework Github](https://github.com/spring-projects/spring-framework)
|
||||
- **书籍**
|
||||
- [《Spring In Action》](https://item.jd.com/12622829.html)
|
||||
- **教程**
|
||||
- [《小马哥讲 Spring 核心编程思想》](https://time.geekbang.org/course/intro/265)
|
||||
- [geekbang-lessons](https://github.com/geektime-geekbang/geekbang-lessons)
|
||||
- [跟我学 Spring3](http://jinnianshilongnian.iteye.com/blog/1482071)
|
||||
|
||||
## 🚪 传送
|
||||
|
||||
◾ 🏠 [spring-tutorial 首页](https://dunwu.github.io/spring-tutorial/) ◾ 🎯 [我的博客](https://github.com/dunwu/blog) ◾
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "spring-tutorial",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf docs/.temp",
|
||||
"start": "vuepress dev docs",
|
||||
"build": "vuepress build docs",
|
||||
"deploy": "bash scripts/deploy.sh",
|
||||
"updateTheme": "yarn remove vuepress-theme-vdoing && rm -rf node_modules && yarn && yarn add vuepress-theme-vdoing -D",
|
||||
"editFm": "node utils/editFrontmatter.js",
|
||||
"lint": "markdownlint -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules",
|
||||
"lint:fix": "markdownlint -f -r markdownlint-rule-emphasis-style -c docs/.markdownlint.json **/*.md -i node_modules",
|
||||
"show-help": "vuepress --help",
|
||||
"view-info": "vuepress view-info ./ --temp docs/.temp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dayjs": "^1.9.7",
|
||||
"inquirer": "^7.1.0",
|
||||
"json2yaml": "^1.1.0",
|
||||
"vuepress": "1.9.2",
|
||||
"vuepress-plugin-baidu-tongji": "^1.0.1",
|
||||
"vuepress-plugin-comment": "^0.7.3",
|
||||
"vuepress-plugin-demo-block": "^0.7.2",
|
||||
"vuepress-plugin-fulltext-search": "^2.2.1",
|
||||
"vuepress-plugin-one-click-copy": "^1.0.2",
|
||||
"vuepress-plugin-thirdparty-search": "^1.0.2",
|
||||
"vuepress-plugin-zooming": "^1.1.7",
|
||||
"vuepress-plugin-flowchart": "^1.4.2",
|
||||
"vuepress-theme-vdoing": "^1.10.3",
|
||||
"yamljs": "^0.3.0",
|
||||
"markdownlint-cli": "^0.25.0",
|
||||
"markdownlint-rule-emphasis-style": "^1.0.1",
|
||||
"rimraf": "^3.0.1",
|
||||
"vue-toasted": "^1.1.25"
|
||||
}
|
||||
}
|
|
@ -2,43 +2,45 @@
|
|||
|
||||
# ------------------------------------------------------------------------------
|
||||
# gh-pages 部署脚本
|
||||
# @author <a href="mailto:forbreak@163.com">Zhang Peng</a>
|
||||
# @author Zhang Peng
|
||||
# @since 2020/2/10
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# 装载其它库
|
||||
ROOT_DIR=$(cd `dirname $0`/..; pwd)
|
||||
ROOT_DIR=$(
|
||||
cd $(dirname $0)/..
|
||||
pwd
|
||||
)
|
||||
|
||||
# 确保脚本抛出遇到的错误
|
||||
set -e
|
||||
|
||||
cd "${ROOT_DIR}/docs"
|
||||
|
||||
# 生成静态文件
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# 进入生成的文件夹
|
||||
cd dist
|
||||
cd ${ROOT_DIR}/docs/.temp
|
||||
|
||||
# 如果是发布到自定义域名
|
||||
# echo 'www.example.com' > CNAME
|
||||
|
||||
git init
|
||||
git checkout -b gh-pages && git add .
|
||||
git commit -m 'deploy'
|
||||
|
||||
# 如果发布到 https://<USERNAME>.github.io/<REPO>
|
||||
if [[ ${GITHUB_TOKEN} ]] && [[ ${GITEE_TOKEN} ]]; then
|
||||
echo "使用 token 公钥部署 gh-pages"
|
||||
# ${GITHUB_TOKEN} 是 Github 私人令牌;${GITEE_TOKEN} 是 Gitee 私人令牌
|
||||
# ${GITHUB_TOKEN} 和 ${GITEE_TOKEN} 都是环境变量;travis-ci 构建时会传入变量
|
||||
git push --force --quiet "https://dunwu:${GITHUB_TOKEN}@github.com/dunwu/spring-tutorial.git" gh-pages
|
||||
git push --force --quiet "https://turnon:${GITEE_TOKEN}@gitee.com/turnon/spring-tutorial.git" gh-pages
|
||||
if [[ ${GITHUB_TOKEN} && ${GITEE_TOKEN} ]]; then
|
||||
msg='自动部署'
|
||||
GITHUB_URL=https://dunwu:${GITHUB_TOKEN}@github.com/dunwu/spring-tutorial.git
|
||||
GITEE_URL=https://turnon:${GITEE_TOKEN}@gitee.com/turnon/spring-tutorial.git
|
||||
git config --global user.name "dunwu"
|
||||
git config --global user.email "forbreak@163.com"
|
||||
else
|
||||
echo "使用 ssh 公钥部署 gh-pages"
|
||||
git push -f git@github.com:dunwu/spring-tutorial.git gh-pages
|
||||
git push -f git@gitee.com:turnon/spring-tutorial.git gh-pages
|
||||
msg='手动部署'
|
||||
GITHUB_URL=git@github.com:dunwu/spring-tutorial.git
|
||||
GITEE_URL=git@gitee.com:turnon/spring-tutorial.git
|
||||
fi
|
||||
git init
|
||||
git add -A
|
||||
git commit -m "${msg}"
|
||||
# 推送到github gh-pages分支
|
||||
git push -f "${GITHUB_URL}" master:gh-pages
|
||||
git push -f "${GITEE_URL}" master:gh-pages
|
||||
|
||||
cd "${ROOT_DIR}"
|
||||
cd -
|
||||
rm -rf ${ROOT_DIR}/docs/.temp
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# 批量添加和修改、删除front matter配置文件
|
||||
|
||||
# 需要批量处理的路径,docs文件夹内的文件夹 (数组,映射路径:path[0]/path[1]/path[2] ... )
|
||||
path:
|
||||
- docs # 第一个成员必须是docs
|
||||
|
||||
# 要删除的字段 (数组)
|
||||
delete:
|
||||
# - tags
|
||||
|
||||
# 要添加、修改front matter的数据 (front matter中没有的数据则添加,已有的数据则覆盖)
|
||||
data:
|
||||
# author:
|
||||
# name: xugaoyi
|
||||
# link: https://github.com/xugaoyi
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* 批量添加和修改front matter ,需要配置 ./config.yml 文件。
|
||||
*/
|
||||
const fs = require('fs') // 文件模块
|
||||
const path = require('path') // 路径模块
|
||||
const matter = require('gray-matter') // front matter解析器 https://github.com/jonschlinkert/gray-matter
|
||||
const jsonToYaml = require('json2yaml')
|
||||
const yamlToJs = require('yamljs')
|
||||
const inquirer = require('inquirer') // 命令行操作
|
||||
const chalk = require('chalk') // 命令行打印美化
|
||||
const readFileList = require('./modules/readFileList')
|
||||
const { type, repairDate } = require('./modules/fn')
|
||||
const log = console.log
|
||||
|
||||
const configPath = path.join(__dirname, 'config.yml') // 配置文件的路径
|
||||
|
||||
main()
|
||||
|
||||
/**
|
||||
* 主体函数
|
||||
*/
|
||||
async function main() {
|
||||
const promptList = [
|
||||
{
|
||||
type: 'confirm',
|
||||
message: chalk.yellow('批量操作frontmatter有修改数据的风险,确定要继续吗?'),
|
||||
name: 'edit'
|
||||
}
|
||||
]
|
||||
let edit = true
|
||||
|
||||
await inquirer.prompt(promptList).then((answers) => {
|
||||
edit = answers.edit
|
||||
})
|
||||
|
||||
if (!edit) {
|
||||
// 退出操作
|
||||
return
|
||||
}
|
||||
|
||||
const config = yamlToJs.load(configPath) // 解析配置文件的数据转为js对象
|
||||
|
||||
if (type(config.path) !== 'array') {
|
||||
log(chalk.red('路径配置有误,path字段应该是一个数组'))
|
||||
return
|
||||
}
|
||||
|
||||
if (config.path[0] !== 'docs') {
|
||||
log(chalk.red("路径配置有误,path数组的第一个成员必须是'docs'"))
|
||||
return
|
||||
}
|
||||
|
||||
const filePath = path.join(__dirname, '..', ...config.path) // 要批量修改的文件路径
|
||||
const files = readFileList(filePath) // 读取所有md文件数据
|
||||
|
||||
files.forEach((file) => {
|
||||
let dataStr = fs.readFileSync(file.filePath, 'utf8') // 读取每个md文件的内容
|
||||
const fileMatterObj = matter(dataStr) // 解析md文件的front Matter。 fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{<frontmatter对象>}, ...}
|
||||
let matterData = fileMatterObj.data // 得到md文件的front Matter
|
||||
|
||||
let mark = false
|
||||
// 删除操作
|
||||
if (config.delete) {
|
||||
if (type(config.delete) !== 'array') {
|
||||
log(chalk.yellow('未能完成删除操作,delete字段的值应该是一个数组!'))
|
||||
} else {
|
||||
config.delete.forEach((item) => {
|
||||
if (matterData[item]) {
|
||||
delete matterData[item]
|
||||
mark = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 添加、修改操作
|
||||
if (type(config.data) === 'object') {
|
||||
Object.assign(matterData, config.data) // 将配置数据合并到front Matter对象
|
||||
mark = true
|
||||
}
|
||||
|
||||
// 有操作时才继续
|
||||
if (mark) {
|
||||
if (matterData.date && type(matterData.date) === 'date') {
|
||||
matterData.date = repairDate(matterData.date) // 修复时间格式
|
||||
}
|
||||
const newData =
|
||||
jsonToYaml
|
||||
.stringify(matterData)
|
||||
.replace(/\n\s{2}/g, '\n')
|
||||
.replace(/"/g, '') +
|
||||
'---\r\n' +
|
||||
fileMatterObj.content
|
||||
fs.writeFileSync(file.filePath, newData) // 写入
|
||||
log(chalk.green(`update frontmatter:${file.filePath} `))
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 类型判断
|
||||
exports.type = function (o) {
|
||||
var s = Object.prototype.toString.call(o)
|
||||
return s.match(/\[object (.*?)\]/)[1].toLowerCase()
|
||||
}
|
||||
|
||||
// 修复date时区格式的问题
|
||||
exports.repairDate = function (date) {
|
||||
date = new Date(date)
|
||||
return `${date.getUTCFullYear()}-${zero(date.getUTCMonth() + 1)}-${zero(date.getUTCDate())} ${zero(
|
||||
date.getUTCHours()
|
||||
)}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}`
|
||||
}
|
||||
|
||||
// 日期的格式
|
||||
exports.dateFormat = function (date) {
|
||||
return `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero(
|
||||
date.getMinutes()
|
||||
)}:${zero(date.getSeconds())}`
|
||||
}
|
||||
|
||||
// 小于10补0
|
||||
function zero(d) {
|
||||
return d.toString().padStart(2, '0')
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* 读取所有md文件数据
|
||||
*/
|
||||
const fs = require('fs') // 文件模块
|
||||
const path = require('path') // 路径模块
|
||||
const docsRoot = path.join(__dirname, '..', '..', 'docs') // docs文件路径
|
||||
|
||||
function readFileList(dir = docsRoot, filesList = []) {
|
||||
const files = fs.readdirSync(dir)
|
||||
files.forEach((item, index) => {
|
||||
let filePath = path.join(dir, item)
|
||||
const stat = fs.statSync(filePath)
|
||||
if (stat.isDirectory() && item !== '.vuepress') {
|
||||
readFileList(path.join(dir, item), filesList) //递归读取文件
|
||||
} else {
|
||||
if (path.basename(dir) !== 'docs') {
|
||||
// 过滤docs目录级下的文件
|
||||
|
||||
const filename = path.basename(filePath)
|
||||
const fileNameArr = filename.split('.')
|
||||
const firstDotIndex = filename.indexOf('.')
|
||||
const lastDotIndex = filename.lastIndexOf('.')
|
||||
|
||||
let name = null,
|
||||
type = null
|
||||
if (fileNameArr.length === 2) {
|
||||
// 没有序号的文件
|
||||
name = fileNameArr[0]
|
||||
type = fileNameArr[1]
|
||||
} else if (fileNameArr.length >= 3) {
|
||||
// 有序号的文件(或文件名中间有'.')
|
||||
name = filename.substring(firstDotIndex + 1, lastDotIndex)
|
||||
type = filename.substring(lastDotIndex + 1)
|
||||
}
|
||||
|
||||
if (type === 'md') {
|
||||
// 过滤非md文件
|
||||
filesList.push({
|
||||
name,
|
||||
filePath
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return filesList
|
||||
}
|
||||
|
||||
module.exports = readFileList
|
Loading…
Reference in New Issue