JoyLau's Blog

JoyLau 的技术学习与思考

Sonatype

说明

  • 个人感觉第一次发布的步骤非常复杂,我在第一次操作的时候来来回回发布了7,8个版本,最后都是校验失败,导致构件不能关闭(因为我遇到了个大坑)
  • 第一次发布成功之后后面的更新和添加新的构件都相对来说要容易一些(groupid不变的情况下)

开始

账户注册

创建并提交工单

创建工单

  • Project和issue Type的填写如上图所示,不能填写错了
  • 创建完成之后就等待网站工作人员的审核就可以了,不知道为什么,我等待的时间非常短,2分钟都不到,工作人员就回复我了,可能是我的运气比较好吧,但是上个星期买房摇号我却没摇到伤心欲绝
  • 当issue 的 state 变为 RESOLVED时就可继续操作了,同时下面的活动区会给你发消息
    Comment

gpg生成密钥对

  • 下载安装:https://www.gpg4win.org/download.html
    安装时注意的是,只安装主体组件和加密解密窗口的组件就可以了,其他的不需要~~~~
  • 查看是否安装成功:gpg --version
    version
  • 生成密钥对:gpg --gen-key
    gpg --gen-key
    gpg --gen-key
  • 之后往下,会让你输入用户名和邮箱,还有一个Passphase,相当于密钥库密码,不要忘记。
  • 查看公钥:gpg --list-keys
  • 将公钥发布到 PGP 密钥服务器
    1
    2
    3
    4
    5
    gpg --keyserver hkp://pool.sks-keyservers.net --send-keys C990D076
    //可能由于网络问题,有点慢,多重试几次

    //查看发布是否成功
    gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys C990D076

配置setting.xml文件和pom.xml文件

  • setting.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    <servers>
    <server>
    //记住id,需要和pom文件里的id一致
    <id>oss</id>
    <username>username</username>
    <password>password</password>
    </server>
    </servers>
  • pom.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    <!--
    ~ The MIT License (MIT)
    ~
    ~ Copyright (c) 2017 2587038142@qq.com
    ~
    ~ Permission is hereby granted, free of charge, to any person obtaining a copy
    ~ of this software and associated documentation files (the "Software"), to deal
    ~ in the Software without restriction, including without limitation the rights
    ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    ~ copies of the Software, and to permit persons to whom the Software is
    ~ furnished to do so, subject to the following conditions:
    ~
    ~ The above copyright notice and this permission notice shall be included in
    ~ all copies or substantial portions of the Software.
    ~
    ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    ~ THE SOFTWARE.
    -->

    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.joylau.code</groupId>
    <artifactId>joylau-echarts</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>joylau-echarts</name>
    <description>Configure the most attribute for ECharts3.0+ by Gson</description>
    <url>http://code.joylau.cn</url>

    <parent>
    <groupId>org.sonatype.oss</groupId>
    <artifactId>oss-parent</artifactId>
    <version>7</version>
    </parent>

    <licenses>
    <license>
    <name>The Apache Software License, Version 2.0</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
    </license>
    </licenses>

    <developers>
    <developer>
    <name>JoyLau</name>
    <email>2587038142@qq.com</email>
    <url>http://joylau.cn</url>
    </developer>
    </developers>

    <scm>
    <connection>scm:git:git@github.com:JoyLau/joylau-echarts.git</connection>
    <developerConnection>scm:git:git@github.com:JoyLau/joylau-echarts.git</developerConnection>
    <url>git@github.com:JoyLau/joylau-echarts</url>
    </scm>
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.5</version>
    <scope>compile</scope>
    <optional>true</optional>
    </dependency>
    </dependencies>

    <build>
    <testResources>
    <testResource>
    <directory>src/test/resources</directory>
    </testResource>
    <testResource>
    <directory>src/test/java</directory>
    </testResource>
    </testResources>
    </build>

    <profiles>
    <profile>
    <id>release</id>
    <build>
    <plugins>
    <!--Compiler-->
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.7</source>
    <target>1.7</target>
    </configuration>
    </plugin>
    <!-- Source -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.2.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>jar-no-fork</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <!-- Javadoc -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.9.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>jar</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <!-- GPG -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.6</version>
    <executions>
    <execution>
    <phase>verify</phase>
    <goals>
    <goal>sign</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    <distributionManagement>
    <snapshotRepository>
    <id>oss</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
    </snapshotRepository>
    <repository>
    <id>oss</id>
    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
    </distributionManagement>
    </profile>
    </profiles>
    </project>

上传构件到 OSS 中

1
2
3
mvn clean deploy -P release
<--! jdk1.8后再生成javadoc时语法较为严格,这时去除javadoc即可 !-->
mvn clean deploy -P release -Dmaven.javadoc.skip=true

在上传之前会自动弹出一个对话框,需要输入上面提到的 Passphase,它就是刚才设置的 GPG 密钥库的密码。
随后会看到大量的 upload 信息,因为在国内网络的缘故,时间有点久,耐心等待吧。

在 OSS 中发布构件

  • 在 OSS 中,使用自己的 Sonatype 账号登录后,可在 Staging Repositories 中查看刚才已上传的构件,这些构件目前是放在 Staging 仓库中,可进行模糊查询,快速定位到自己的构件。
  • 此时,该构件的状态为 Open,需要勾选它,然后点击 Close 按钮。系统会自动验证该构件是否满足指定要求,当验证完毕后,状态会变为 Closed。
    1
  • 最后,点击 Release 按钮来发布该构件,这一步没有截图,将就看吧知道就行:
    2

等待构件审批通过

这个,又只能等待了,当然他们晚上上班,还是第二天看。当审批通过后,将会收到邮件通知。

从中央仓库中搜索构件

这时,就可以在maven的中央仓库中搜索到自己发布的构件了,以后可以直接在pom.xml中使用了!

中央仓库搜索网站:http://search.maven.org/

第一次成功发布之后,以后就不用这么麻烦了,可以直接使用Group Id发布任何的构件,当然前提是Group Id没有变。

以后的发布流程:

a)构件完成后直接使用maven在命令行上传构建;

b)在https://oss.sonatype.org/ close并release构件;

c)等待同步好(大约2小时多)之后,就可以使用了

遇坑记录

  • 安装GPG时候,没有安装弹框组件,导致gpg密码框弹不出来
  • 一开始所有的命令行都在git下操作,每次部署的时候都是提示没有私钥错误,后来发现git生成的gpg密钥对在user更目录下,切换到CMD操作,是生成在AppData下。经查看有私钥,问题解决

引用

有一部分程序员中的老司机,他们善于找各种借口,少干活,少背锅,多拿钱。但是,更多的程序员坦诚、直白、意气用事。
那些年吹过的牛逼都实现了吗?还是随风而去?

这个功能简单,一天就能搞完
Images
程序员拿到一个新功能,心里暗暗发笑,这剧情我见过啊。于是脱口而出,这功能简单,一天就能做完,明天上线肯定没问题。
结果,眼看着到自己设定的截止日期了,还有一部分代码没有写完,怎么办?
很简单啊,又不是生死状,又不要命。解决办法很简单,加班~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

这段代码肯定没bug,我都测试过了
Images
功能开发完了,拿去测试吧,拿去玩耍吧,上线吧,部署吧,发给客户吧,肯定没问题的。
结果,很多时候还没发布。要么测试发现bug,要么产品发现bug,要么老板发现bug。
你的第一反应就是:是特么你们不会用老子开发的功能吧?你乐呵呵的看着bug复现,怎么办呢?
很简单啊,紧急修复bug,重新发布。时间来不及了?加班啊~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

我用的是最现在最流行的技术,某某大公司也用这个
Images
在技术讨论会上,你侃侃而谈,我精心设计的前后端分离的框架,我使用了现在最流行的界面库,我们用的技术某某独角兽公司都在使用,肯定是最好的。
结果呢,使用的技术太新。Github上很少有相关的开源项目,Stack Overflow上很少有这方面的问答。你被一个问题搞的昏天暗地,只能默默的看官方文档,而且是英文的(这是好事儿)。
啊?项目着急上线怎么办呢?加班啊~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

重构代码,很快就能完成
Images

  • 何为Code refactoring

    Code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior.

之前为了快速迭代,忽略了代码的结构和质量。正好最近这两天没有什么新功能开发,我要重构一下现有的代码,绝对没问题。
结果呢,两天的空窗期没搞定。明天就要开发新的功能了,怎么办呢?加班啊~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

向外行介绍程序员工作的复杂程度
在工作中经常能听到这样的话「不就加个按钮么?怎么要做两天时天?」。那么,作为程序员如何解释自己的工作复杂度呢?
如果你的老板是技术出身,那你很庆幸,他能理解你实现一个小小功能,修改一个小小功能所付出的辛苦劳动。
如果你的老板不懂技术,也许你就要无穷无尽的加班了。给你的忠告就是:做正确的事儿,等着被开除。这是一位谷歌工程师说的话。
如果你的产品经理懂技术,那么你既是幸运的也是不幸的。
幸运的是,他可以理解程序员工作的复杂度。但是“不幸”的是,你再也不能为了偷懒找借口。
当产品经理提出一个方案时,你再也不敢坚定地说“技术不可行”。因为你害怕产品经理自己写好了代码给你,那是多么尴尬的境地。

  • 下面是 Channing Walton 的用泡茶的例子来解释,非常形象。
      - 请他们描述泡出一杯茶需要哪些步骤,他们会这么说:
      - 烧水
      - 把茶叶放到茶壶里
      - 水烧开后倒入茶壶
      - 等待5分钟
      - 把茶倒进杯子
      - 加牛奶
      - 喝
      - 现在,有趣的开始了。你要开始问这样的问题:
      - 烧水?
      - 水哪来的?
      - 热水壶在哪里?
      - 你怎么把水倒进热水壶?
      - 你怎么知道热水壶壶里要倒多少水?
      - 如果没有水/热水壶/电怎么办呢?
      - 假如加水传感器失效怎么办?
      - 假如煮水传感器失效怎么办?
      - 茶叶放到茶壶里?
      - 茶壶在哪里,如果没有茶壶怎么办?烧水之前我们应该考虑到这些问题吗?
      - 茶叶在哪里,要用哪一种茶叶?我们是否应该先问清楚,或许如果没有对应的茶叶,我们甚至都不应该开始泡茶?
      - 关于加水和传感器也可以有类似的问题要问
      - 倒开水?
      - 你确定水已经开了么?你怎么能确保“倒水”的机器从热水壶那收到“烧水完成”的信号呢?
      - 你如何确保倒水的机器知道热水壶在哪里?
      - 如果热水壶在倒水的过程翻了怎么办呢?

程序员代码提交中的骂声
正如你工作中看到的,写代码会让你骂骂咧咧,经常爆粗口。
另外有数据统计,写 C++ 程序,会比写 PHP 或 Python 程序所遭到的骂声更多。
Andrew Vos在找一个周末项目,于是决定在 GitHub 上抓取100百万条提交信息(commit),并扫描其中的脏话。
Images   

  • 而且程序员最喜欢的一句是:
      “去TMD,咱们就这样发布。

1
2
3
4
let arr = [1, 1, 2, 2]
arr = Array.prototype.slice.call(new Set(arr))
alert(arr)
//output: 1, 2

build-better-enterprise

SpringBoot文章推荐

SpringBoot项目实战

不啰嗦,直接上代码

集成Druid

DruidConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Created by LiuFa on 2016/9/14.
* cn.lfdevelopment.www.sys.druid
* DevelopmentApp
*/
@Configuration
public class DruidConfig{

private Logger logger = LoggerFactory.getLogger(getClass());

@Bean(initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource() {
@Override
public void setUsername(String username) {
try {
username = ConfigTools.decrypt(username);
} catch (Exception e) {
e.printStackTrace();
}
super.setUsername(username);
}

@Override
public void setUrl(String jdbcUrl) {
try {
jdbcUrl = ConfigTools.decrypt(jdbcUrl);
} catch (Exception e) {
e.printStackTrace();
}
super.setUrl(jdbcUrl);
}
};
}
}

DruidStatViewConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Created by LiuFa on 2016/8/8.
* cn.lfdevelopment.www.sys.druid
* DevelopmentApp
* 这样的方式不需要添加注解:@ServletComponentScan
*/
@Configuration
public class DruidStatViewConfig {

@Value("${spring.druid.loginUsername}")
private String loginUsername;

@Value("${spring.druid.loginPassword}")
private String loginPassword;

/**
* 注册一个StatViewServlet
* 使用Druid的内置监控页面
*/
@Bean
public ServletRegistrationBean DruidStatViewServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),
"/druid/*");
//添加初始化参数:initParams
//白名单
//ip配置规则
//配置的格式
//<IP> 或者 <IP>/<SUB_NET_MASK_size> 多个ip地址用逗号隔开
//其中
//128.242.127.1/24
//24表示,前面24位是子网掩码,比对的时候,前面24位相同就匹配。
//由于匹配规则不支持IPV6,配置了allow或者deny之后,会导致IPV6无法访问。
servletRegistrationBean.addInitParameter("allow", "");

//deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝。
//如果allow没有配置或者为空,则允许所有访问
//IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
servletRegistrationBean.addInitParameter("deny", "");

//登录查看信息的账号密码.
try {
servletRegistrationBean.addInitParameter("loginUsername", ConfigTools.decrypt(loginUsername));
servletRegistrationBean.addInitParameter("loginPassword", ConfigTools.decrypt(loginPassword));
} catch (Exception e) {
e.printStackTrace();
}

//是否能够重置数据.
servletRegistrationBean.addInitParameter("resetEnable", "true");

return servletRegistrationBean;
}

/**
* 注册一个:filterRegistrationBean
* 内置监控中的Web关联监控的配置
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());

//添加过滤规则.
filterRegistrationBean.addUrlPatterns("/*");

//排除一些不必要的url
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
//缺省sessionStatMaxCount是1000个,这里设置了3000个
filterRegistrationBean.addInitParameter("sessionStatMaxCount", "3000");
//可以配置principalCookieName,使得druid知道指定的sessionName是谁
// filterRegistrationBean.addInitParameter("principalSessionName", "sessionId");
//druid 0.2.7版本开始支持profile,配置profileEnable能够监控单个url调用的sql列表。
filterRegistrationBean.addInitParameter("profileEnable", "true");
return filterRegistrationBean;
}

/**
* 注册一个:druidStatInterceptor
*/
/*@Bean
public DruidStatInterceptor druidStatInterceptor(){
return new DruidStatInterceptor();
}*/

/**
* 注册一个:beanNameAutoProxyCreator
* 内置监控中的spring关联监控的配置
* 该方法使用的是按照BeanId来拦截配置,还有2种方法,分别是
* 按类型拦截配置
* 方法名正则匹配拦截配置
*/
/*@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true);
beanNameAutoProxyCreator.setBeanNames("*Controller");
beanNameAutoProxyCreator.setInterceptorNames("druidStatInterceptor");
return beanNameAutoProxyCreator;
}*/
}

application-dev:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#druid配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=G11Jor+OrLz9MFztdkOfqRnrJKVrFCDdBbYJFmB0qGjUARxPr2tiyRzUn4xbnk/XqPgM8PMjdIJ/pO8UF4aeVg==
spring.datasource.username=bNVOqb7WKLX5Bjnw+LMv92taj25KOxDimXxILPQjw42wgv+1lHzOH8kr97xDwWdhpY67QuYCS7sWN4W46YbkFA==
spring.datasource.password=l65GeQaXVXxx2ogcQeZLAFM7VcPwgzc9202vxql4hjCbjM8dVm/sD4osdvaBdVkC+BiYdnYL2EzpaCysXAZ5Gw==


# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.initialSize=10
spring.datasource.minIdle=25
spring.datasource.maxActive=250

# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000

# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=1200000

# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=1800000

spring.datasource.validationQuery=SELECT 'x'

#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.testWhileIdle=true

#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.testOnBorrow=false

#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.testOnReturn=false

# 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。分库分表较多的数据库,建议配置为false 在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。5.5及以上版本有PSCache,建议开启。
spring.datasource.poolPreparedStatements=true

# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,'stat'用于监控,‘log4j’用于日志,'config'是指ConfigFilter
spring.datasource.filters=wall,stat,config

# 通过connectProperties属性来打开mergeSql功能;慢SQL记录,超过3秒就是慢sql
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=3000;config.decrypt=true

# 合并多个DruidDataSource的监控数据,缺省多个DruidDataSource的监控数据是各自独立的,在Druid-0.2.17版本之后,支持配置公用监控数据
spring.datasource.useGlobalDataSourceStat=true

#druid登陆用户名
spring.druid.loginUsername=lCzd9geWAuAuJtLhpaG/J+d28H8KiMFAWopxXkYpMNdUai6Xe/LsPqMQeg5MIrmvtMa+hzycdRhWs29ZUPU1IQ==

#druid登录密码
spring.druid.loginPassword=hf96/2MU+Q12fdb9oZN9ghub1OHmUBa8YuW7NJf8Pll/sawcaRVscHTpr4t5SB39+KbJn31Lqy76uEDvj+sgMw==

集成Mybatis

MyBatisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* Created by LiuFa on 2016/8/8.
* cn.lfdevelopment.www.sys.mybatis
* DevelopmentApp
* DataSource 交由Druid自动根据配置创建
*/
@Configuration
@EnableTransactionManagement
public class MyBatisConfig implements TransactionManagementConfigurer {

@Autowired
private DataSource dataSource;


@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("cn.lfdevelopment.www.app.**.pojo");
//支持属性使用驼峰的命名,mapper配置不需要写字段与属性的配置,会自动映射。
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(configuration);

//分页插件
PageHelper pageHelper = new PageHelper();
Properties properties = new Properties();
/* 3.3.0版本可用 - 分页参数合理化,默认false禁用
启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
在EXTjs里面配置与否无所谓,因为在前台传过来的分页数据已经进行合理化了 */
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("returnPageInfo", "check");
/* 3.5.0版本可用 - 为了支持startPage(Object params)方法
增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值
可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值
不理解该含义的前提下,不要随便复制该配置 -->*/

// properties.setProperty("params", "count=countSql");
pageHelper.setProperties(properties);

//添加插件
bean.setPlugins(new Interceptor[]{pageHelper});

//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath*:mapperxml/**/*Mapper.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

@Bean
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
try {
return new DataSourceTransactionManager(dataSource);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

MyBatisMapperScannerConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* MyBatis扫描接口,使用的tk.mybatis.spring.mapper.MapperScannerConfigurer,如果你不使用通用Mapper,可以改为org.xxx...
*/
@Configuration
//由于MapperScannerConfigurer执行的比较早
public class MyBatisMapperScannerConfig {

@Bean
public static MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("cn.lfdevelopment.www.app.**.mapper");
Properties properties = new Properties();
properties.setProperty("mappers", "cn.lfdevelopment.www.sys.base.BaseMapper");
properties.setProperty("notEmpty", "false");
properties.setProperty("IDENTITY", "MYSQL");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}

}

集成Redis

RedisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    
/**
* Created by LiuFa on 2016/9/5.
* cn.lfdevelopment.www.sys.redis
* DevelopmentApp
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
};
}

@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
return new RedisCacheManager(redisTemplate);
}

/**
* StringRedisTemplate
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}


/**
* redisTemplateForShiro
* @param factory
* @return
*/
@Bean
public RedisTemplate<byte[], Object> redisTemplateForShiro(RedisConnectionFactory factory) {
RedisTemplate<byte[], Object> redisTemplateForShiro = new RedisTemplate<>();
redisTemplateForShiro.setConnectionFactory(factory);
return redisTemplateForShiro;
}

}

集成Shiro

ShiroConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
* Created by LiuFa on 2016/9/13.
* cn.lfdevelopment.www.sys.shiro
* DevelopmentApp
*/
@Configuration
public class ShiroConfiguration {
/**
* FilterRegistrationBean
* @return
*/
@Autowired
private RedisTemplate redisTemplate;

@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.addInitParameter("targetFilterLifecycle","true");
filterRegistration.addUrlPatterns("/*");
filterRegistration.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
// filterRegistration.setAsyncSupported(true);
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
return filterRegistration;
}

/**
* @see org.apache.shiro.spring.web.ShiroFilterFactoryBean
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setSuccessUrl("/main");
//验证不具备权限后的转向页面
bean.setUnauthorizedUrl("/main");

Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("authc",shiroFormAuthenticationFilter());
filters.put("session",sessionFilter());
filters.put("rolesOr",rolesAuthorizationFilter());
bean.setFilters(filters);

Map<String, String> chains = new LinkedHashMap<>();
chains.put("/favicon.ico","anon");
chains.put("/","anon");
chains.put("/index","anon");
chains.put("/blog","anon");
chains.put("/blog/**","anon");
chains.put("/weixin","anon");
chains.put("/weixin/**","anon");
chains.put("/static/**", "anon");
chains.put("/getGifCode","anon");
chains.put("/404", "anon");
chains.put("/druid/**","anon");
chains.put("/logout", "logout");

chains.put("/login", "authc");
chains.put("/main","authc");
chains.put("/**", "session,user");
bean.setFilterChainDefinitionMap(chains);
return bean;
}


/**
* @see org.apache.shiro.mgt.SecurityManager
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm());
manager.setCacheManager(redisCacheManager());
manager.setSessionManager(defaultWebSessionManager());
return manager;
}

/**
* @see DefaultWebSessionManager
* @return
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(redisCacheManager());
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationInterval(600000);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}


/**
* @return
*/
@Bean
@DependsOn(value={"lifecycleBeanPostProcessor", "shrioRedisCacheManager"})
public AuthorizingRealm userRealm() {
AuthorizingRealm userRealm = new AuthorizingRealm();
userRealm.setCacheManager(redisCacheManager());
userRealm.setCachingEnabled(true);
userRealm.setAuthenticationCachingEnabled(true);
userRealm.setAuthorizationCachingEnabled(true);
return userRealm;
}


@Bean(name="shrioRedisCacheManager")
@DependsOn(value="redisTemplate")
public ShrioRedisCacheManager redisCacheManager() {
ShrioRedisCacheManager cacheManager = new ShrioRedisCacheManager(redisTemplate);
return cacheManager;
}

@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

@Bean(name = "authcFilter")
public FormAuthenticationFilter shiroFormAuthenticationFilter(){
return new AuthcFilter();
}

@Bean
public SessionFilter sessionFilter(){
return new SessionFilter();
}


@Bean(name = "rolesOrFilter")
public RolesAuthorizationFilter rolesAuthorizationFilter(){
return new RolesAuthorizationFilter() {
@Override
public boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;

if ((rolesArray == null) || (rolesArray.length == 0)) {
return true;
}
for (String aRolesArray : rolesArray) {
if (subject.hasRole(aRolesArray)) {
//用户只要拥有任何一个角色则验证通过
return true;
}
}
return false;
}
};
}
}

ShiroRedisCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* Created by LiuFa on 2016/9/13.
* cn.lfdevelopment.www.sys.shiro
* DevelopmentApp
*/
public class ShrioRedisCache<K, V> implements Cache<K, V> {
private org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private RedisTemplate<byte[], V> redisTemplate;
private String prefix = "shiro_redis:";

public ShrioRedisCache(RedisTemplate<byte[], V> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public ShrioRedisCache(RedisTemplate<byte[], V> redisTemplate, String prefix) {
this(redisTemplate);
this.prefix = prefix;
}

@Override
public V get(K key) throws CacheException {
if(log.isDebugEnabled()) {
log.debug("Key: {}", key);
}
if(key == null) {
return null;
}

byte[] bkey = getByteKey(key);
return redisTemplate.opsForValue().get(bkey);
}

@Override
public V put(K key, V value) throws CacheException {
if(log.isDebugEnabled()) {
log.debug("Key: {}, value: {}", key, value);
}

if(key == null || value == null) {
return null;
}

byte[] bkey = getByteKey(key);
redisTemplate.opsForValue().set(bkey, value);
return value;
}

@Override
public V remove(K key) throws CacheException {
if(log.isDebugEnabled()) {
log.debug("Key: {}", key);
}

if(key == null) {
return null;
}

byte[] bkey = getByteKey(key);
ValueOperations<byte[], V> vo = redisTemplate.opsForValue();
V value = vo.get(bkey);
redisTemplate.delete(bkey);
return value;
}

@Override
public void clear() throws CacheException {
redisTemplate.getConnectionFactory().getConnection().flushDb();
}

@Override
public int size() {
Long len = redisTemplate.getConnectionFactory().getConnection().dbSize();
return len.intValue();
}

@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
byte[] bkey = (prefix+"*").getBytes();
Set<byte[]> set = redisTemplate.keys(bkey);
Set<K> result = new HashSet<>();

if(CollectionUtils.isEmpty(set)) {
return Collections.emptySet();
}

for(byte[] key: set) {
result.add((K)key);
}
return result;
}

@Override
public Collection<V> values() {
Set<K> keys = keys();
List<V> values = new ArrayList<>(keys.size());
for(K k: keys) {
byte[] bkey = getByteKey(k);
values.add(redisTemplate.opsForValue().get(bkey));
}
return values;
}

private byte[] getByteKey(K key){
if(key instanceof String){
String preKey = this.prefix + key;
return preKey.getBytes();
}else{
return SerializeUtils.serialize(key);
}
}

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}
}

最后

  • 本文暂未完结,后续将持续集成更多第三方框架,或接着更新,或另起新篇
  • 详细代码内容可在GitHub上follow

SpringBootStart-Main

开始

我们开发任何一个Spring Boot项目,都会用到如下的启动类

1
2
3
4
5
6
7
@SpringBootApplication
public class JoylauApplication {

public static void main(String[] args) {
SpringApplication.run(JoylauApplication.class, args);
}
}

从上面代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以要揭开SpringBoot的神秘面纱,我们要从这两位开始就可以了。

SpringBootApplication背后的秘密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "exclude"
)
Class<?>[] exclude() default {};

@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "excludeName"
)
String[] excludeName() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}

虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

  • @Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan
    所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class JoylauApplication {
    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配置的方式是这样:
    1
    2
    3
    4
    5
    6
    7
    <?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的配置方式是这样:
    1
    2
    3
    4
    @Configuration
    public class MockConfiguration{
    //bean定义
    }

任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。

  • 注册bean定义层面
    基于XML的配置形式是这样:
    1
    2
    3
    <bean id="mockService" class="..MockServiceImpl">
    ...
    </bean>

而基于JavaConfig的配置形式是这样的:

1
2
3
4
5
6
7
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl();
}
}

任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

  • 表达依赖注入关系层面
    为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:
    1
    2
    3
    4
    5
    <bean id="mockService" class="..MockServiceImpl">
    <propery name ="dependencyService" ref="dependencyService" />
    </bean>

    <bean id="dependencyService" class="DependencyServiceImpl"></bean>
    而基于JavaConfig的配置形式是这样的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @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配置中的context:component-scan元素,@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,其自身定义关键信息如下:

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样

深入探索SpringApplication执行流程

SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:

1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

  • 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
  • 推断并设置main方法的定义类。
    2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。

3) 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。

4) 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。

5) 如果SpringApplication的showBanner属性被设置为true,则打印banner。

6) 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。

7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。

8) 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。

9) 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

10) 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。

11) 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。

12) 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。

13) 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)
去除事件通知点后,整个流程如下:
SpringBoot-start-model

参考

大部分参考了《SpringBoot揭秘快速构建为服务体系》这本书

SpringBoot-Start

说明

  • 玄铁重剑是神雕侠侣中杨过的兵器,外表看似笨重无比,但内在却精致有细。
  • 在脚本语言和敏捷开发大行其道的时代,JavaEE的开发显得尤为笨重,这使得很多开发人员本应该如此,Spring在提升JavaEE的开发效率上从未停止过努力,SpringBoot的出现时具有颠覆性和划时代意义的。

开始准备

  • JDK1.7+
  • Maven3.x+
  • Tomcat8.5+
  • Spring4.3.x+
  • IntelliJ IDEA / MyEclipse(强烈推荐IDEA,我认为IDEA目前所有 IDE 中最具备沉浸式的 IDE,没有之一

优缺点

优点

  • 习惯优于配置:使用SpringBoot只需要很少的配置,在绝大部分时候我们只需要使用默认配置
  • 项目极速搭建,可无配置整合其他第三方框架,极大提高开发效率
  • 完全不使用XML配置,只使用自动配置和JavaConfig
  • 内嵌Servlet容器,可打成jar包独立运行
  • 强大的运行时监控
  • 浑然天成的集成云计算

缺点

  • 流汗没有

优雅的开始

  • Spring 官方网站搭建

    1. 访问:http://start.spring.io/
    2. 选择构建工具Maven Project、Spring Boot版本1.5.1以及一些工程基本信息,可参考下图所示
      SpringInitializr
    3. 点击Generate Project下载项目压缩包
    4. 导入到你的工程,如果是IDEA,则需要:
      a.菜单中选择File–>New–>Project from Existing Sources...
      b.选择解压后的项目文件夹,点击OK
      c.点击Import project from external model并选择Maven,点击Next到底为止。
      d.若你的环境有多个版本的JDK,注意到选择Java SDK的时候请选择Java 7以上的版本
  • IntelliJ IDEA创建(强烈推荐
    在File菜单里面选择 New > Project,然后选择Spring Initializr,接着如下图一步步操作即可。
    SpringInitializr
    SpringInitializr-2
    SpringInitializr-3
    SpringInitializr-4

若上述步骤步骤没有出现网络错误导致的无法搭建,基本上已经没有什么问题了

项目目录

根据上面的操作已经初始化了一个Spring Boot的框架了,项目结构如下:
SpringBootProject-view

项目里面基本没有代码,除了几个空目录外,还包含如下几样东西。

  • pom.xml:Maven构建说明文件。
  • JoylauApplication.java:一个带有main()方法的类,用于启动应用程序(关键)。
  • JoylauApplicationTests.java:一个空的Junit测试类,它加载了一个使用Spring Boot字典配置功能的Spring应用程序上下文。
  • application.properties:一个空的properties文件,你可以根据需要添加配置属性。(还推荐一种yml文件的配置方式)

项目文件

我们来看pom.xml文件

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

这块配置就是Spring Boot父级依赖,有了这个,当前的项目就是Spring Boot项目了,spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖,使用它之后,常用的包依赖可以省去version标签。

并不是每个人都喜欢继承自spring-boot-starter-parent POM。也有可能我们需要使用的自己的公司标准parent,或者我们更喜欢显式声明所有的Maven配置。
如果不想使用spring-boot-starter-parent,仍然可以通过使用scope = import依赖关系来保持依赖关系管理:

1
2
3
4
5
6
7
8
9
10
11
12
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

该设置不允许使用spring-boot-dependencies所述的属性(properties)覆盖各个依赖项,要实现相同的结果,需要在spring-boot-dependencies项之前的项目的dependencyManagement中添加一个配置,例如,要升级到另一个Spring Data版本系列,可以将以下内容添加到pom.xml中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencyManagement>
<dependencies>
<!-- Override Spring Data release train provided by Spring Boot -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Fowler-SR2</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

项目依赖

起步依赖 spring-boot-starter-xx

Spring Boot提供了很多”开箱即用“的依赖模块,都是以spring-boot-starter-xx作为命名的。举个例子来说明一下这个起步依赖的好处,比如组装台式机和品牌机,自己组装的话需要自己去选择不同的零件,最后还要组装起来,期间有可能会遇到零件不匹配的问题。耗时又消力,而品牌机就好一点,买来就能直接用的,后续想换零件也是可以的。相比较之下,后者带来的效果更好点(这里就不讨论价格问题哈),起步依赖就像这里的品牌机,自动给你封装好了你想要实现的功能的依赖。就比如我们之前要实现web功能,引入了spring-boot-starter-web这个起步依赖。我们来看看spring-boot-starter-web到底依赖了哪些,如下图:
SpringBoot-starter-web-dependencies

最后

项目启动的三种方式

  1. main方法
    SpringBoot-Start1
  2. 使用命令 mvn spring-boot:run在命令行启动该应用,IDEA中该命令在如下位置
    SpringBoot-Start2
  3. 运行mvn package进行打包时,会打包成一个可以直接运行的 JAR 文件,使用java -jar命令就可以直接运行
    SpringBoot-Start3
    SpringBoot-Start4

MATE Desktop

上面的截图是我安装好之后界面,安装的是MATE桌面

说明

  • 1.阿里云官网默认的Linux Centos7系统镜像,都是没有安装桌面环境的,用户如果要使用桌面,需要自己在服务器上进行安装
  • 2.生产环境下不要安装桌面,毕竟生产环境下的资源都是很紧张的
  • groups是Centos7才有的命令

开始安装

  • 登录服务器,执行命令安装桌面环境(537M)

    1
    yum groups install "MATE Desktop"
  • 安装好MATE Desktop 后,再安装X Window System(19M)

    1
    yum groups install "X Window System"

配置

  • 设置服务器默认启动桌面
    1
    systemctl  set-default  graphical.target

启动

  • 重启服务器
    1
    reboot

在ECS控制台,用管理终端登录服务器,进入到服务器系统登录界面,用root密码登录服务器。

卸载

1
2
3
4
yum groupremove 'X Window System' -y
yum groupremove 'MATE Desktop' -y
// 恢复至默认启动界面
systemctl set-default multi-user.target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永无BUG //
////////////////////////////////////////////////////////////////////

说明

  • 有一些命令是centos7特有的,在低版本的可能无法使用

防火墙

  • 查看防火墙状态: systemctl status firewalld
  • 开启防火墙 : systemctl start firewalld
  • 停止防火墙: systemctl disable firewalld
  • 重启防火墙: systemctl restart firewalld.service
  • 开启80端口: firewall-cmd --zone=public --add-port=80/tcp --permanent
  • 禁用防火墙: systemctl stop firewalld
  • 查看防火墙开放的端口号: firewall-cmd --list-ports

Tomcat

  • 查看tomcat运行状态:ps -ef |grep tomcat
  • 看到tomcat的pid之后:kill -9 pid 可以强制杀死tomcat的进程

系统信息

  • 查看cpu及内存使用情况:top(停止刷新 -q)
  • 查看内存使用情况 :free

文件操作

  • 删除文件里机器子文件夹的内容:rm -rf /var/lib/mysql
  • 查找某个文件所在目录:find / -name filename

压缩与解压缩

  • 解压zip压缩文件:unzip file.zip,相反的,压缩文件 zip file (需安装yum install unzip zip,),解压到指定目录可加参数-d,如:unzip file.zip -d /root/
  • 将 test.txt 文件压缩为 test.zip,zip test.zip test.txt,当然也可以指定压缩包的目录,例如 /root/test.zip ,后面的test.txt也可以换成文件夹
  • linux下是不支持直接解压rar压缩文件,建议将要传输的文件压缩成zip文件
  • yum install p7zip 安装7z解压,支持更多压缩格式(卸载yum remove p7zip

快速删除文件夹/文件

  • 有时我们的文件夹里有很多文件,默认的删除方式是递归删除,文件夹深了及文件多了,删除会非常的慢,这时候:
  • 先建立一个空目录
    mkdir /data/blank
  • 用rsync删除目标目录
    rsync–delete-before -d /data/blank/ /var/spool/clientmqueue/
  • 同样的对于大文件:创建空文件
    touch /data/blank.txt
  • 用rsync清空文件
    rsync-a –delete-before –progress –stats /root/blank.txt /root/nohup.out

端口

  • 查看6379端口是否占用:netstat -tunpl | grep 6379 (注意,redis服务需要 root 权限才能查看,不然只能检查到6379被某个进程占用,但是看不到进程名称。)

主机

  • 修改主机名:hostnamectl set-hostname 新主机名

yum

  • 列出所有可更新的软件清单命令:yum check-update
  • 更新所有软件命令:yum update
  • 仅安装指定的软件命令:yum install <package_name>
  • 仅更新指定的软件命令:yum update <package_name>
  • 列出所有可安裝的软件清单命令:yum list
  • 删除软件包命令:yum remove <package_name>
  • 查找软件包 命令:yum search <keyword>
  • 清除缓存命令:
  • yum clean packages: 清除缓存目录下的软件包
  • yum clean headers: 清除缓存目录下的 headers
  • yum clean oldheaders: 清除缓存目录下旧的 headers

systemctl

  • systemctl restart nginx : 重启nginx
  • systemctl start nginx : 开启nginx
  • systemctl stop nginx : 关闭nginx
  • systemctl enable nginx : nginx开机启动
  • systemctl disable nginx : 禁用nginx开机启动
  • systemctl status nginx : 查看nginx服务信息
  • systemctl is-enabled nginx : 查看服务是否开机启动
  • systemctl list-unit-files|grep enabled : 查看已启动的服务列表
  • systemctl --failed : 查看启动失败的服务列表
  • systemctl daemon-reload : 重新加载service文件
  • systemctl reboot : 重启
  • systemctl poweroff : 关机

压缩解压命令

压缩

  • tar –cvf jpg.tar *.jpg //将目录里所有jpg文件打包成tar.jpg
  • tar –czf jpg.tar.gz *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
  • tar –cjf jpg.tar.bz2 *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
  • tar –cZf jpg.tar.Z *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
  • rar a jpg.rar *.jpg //rar格式的压缩,需要先下载rar for linux
  • zip jpg.zip *.jpg //zip格式的压缩,需要先下载zip for linux

解压

  • tar –xvf file.tar //解压 tar包
  • tar -xzvf file.tar.gz //解压tar.gz
  • tar -xjvf file.tar.bz2 //解压 tar.bz2
  • tar –xZvf file.tar.Z //解压tar.Z
  • unrar e file.rar //解压rar
  • unzip file.zip //解压zip

总结

  • .tar 用 tar –xvf 解压
  • .gz 用 gzip -d或者gunzip 解压
  • .tar.gz和*.tgz 用 tar –xzf 解压
  • .bz2 用 bzip2 -d或者用bunzip2 解压
  • .tar.bz2用tar –xjf 解压
  • .Z 用 uncompress 解压
  • .tar.Z 用tar –xZf 解压
  • .rar 用 unrar e解压
  • .zip 用 unzip 解压

yum更换为阿里源

  • 备份 :mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

  • 下载新的CentOS-Base.repo 到/etc/yum.repos.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## CentOS 5 :

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo

## 或者

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo

## CentOS 6 :

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo

## 或者

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo

## CentOS 7 :

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

## 或者

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
  • 之后运行 yum makecache 生成缓存

创建用户,权限,密码等

  • adduser es -s /bin/bash : 创建用户为 es ,shell指定为bash
  • passwd es 更改 es 用户的密码
  • chown -R es:es /project/elasticsearch-5.6.3 循环遍历更改 /project/elasticsearch-5.6.3 目录下的文件拥有者及用户组
  • su - es : 切换成es用户重新登录系统
  • su es : 表示与 es 建立一个连接,通过 es 来执行命令

注: 以上命令在安装 elasticsearch 时都会用的到

2018-06-22 更新

  1. vim 永久显示行号 vim /etc/vimrc 添加 set nu 或者 set number

  2. 最小化安装 centos 是没有 tab 键补全提示的, 需要安装 yum install bash-completion

  3. tab 补全提示不区分大小写 : vim /etc/inputrc 添加 set completion-ignore-case on

注: 以上 增加配置是全局的,只对当前用户的话,可以在当前目录下新建相应的文件,再添加配置,例如: ~/.inputrc

2018-9-12 更新

  1. killall -9 nginx : 批量结束指定进程,比如不小心运行了 nginx,会产生1个master和n个work进程,这时候一个个结束不实际,killall就是最好的方式
  2. 有时候我们安装 rpm 安装包会出现某些依赖库找不到,比如
    libSM.so.6: cannot open shared object file: No such file or directory
    这时候我们使用 yum provides libSM.so.6 来寻找包含的动态库
1
2
3
4
5
6
7
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
gperftools-libs-2.6.1-1.el7.i686 : Libraries provided by gperftools
Repo : Private-Base
Matched from:
Provides : libprofiler.so.0

找到后安装即可 yum install gperftools-libs

2018-12-20 更新

  1. /dev/null 2>&1 解释
    > 覆盖原来的内容
    >> 在原来的内容上追加新的内容
    0是标准输入 使用<或<<
    1是标准输出 使用>或>>
    2是标准错误输出 使用2>或2>>
    >/dev/null 2>&1 即错误输出与标准输出全部重定向到空,可以写成 1>/dev/null 2>/dev/null
    标准输入0和标准输出1可以省略。(当其出现重定向符号左侧时)
    文件描述符在重定向符号左侧时直接写即可,在右侧时前面加&
    文件描述符与重定向符号之间不能有空格

2019-01-23 更新

  1. 在命令后加个 & 代表该命令在后台运行, shell 的控制台会立即释放,但是和守护进程又不一样, shell 断开会终止运行
  2. command > file.log 2>&1 等价于 command 2>file.log 1>&2 前一个指的是标准错误重定向到标准输出,标准输出在重定向到文件 file.log 中, 其中 1 省略了;后一个指的是标准输出重定向到标准错误,标准错误又重定向到文件 file.log, 其中2 不能省略
  3. shell 脚本中无法报命令不存在的错误: 在 shell 脚本第一行使用 #!/usr/bin/env bash 或者 #!/usr/bin/bash 或者 #!/bin/bash
  4. 如果运行还是命令不存在的话: 创建一个软连接 ln -s command /usr/bin/command, 参数 -s 创建了个符号链接,相当于快捷方式,不加参数 -s 就是创建硬链接,相当于文件拷贝

2019-03-07 更新

没有联网的机器做时间服务器,写了个接口获取网络的时间,然后服务器使用 crontab 定时设置时间
java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 同步时间
*/
@GetMapping("traffic/syncDateTime")
public String syncDateTime() {
String taobaoTime = "http://api.m.taobao.com/rest/api.do?api=mtop.common.getTimestamp";
String suningTime = "http://quan.suning.com/getSysTime.do";
JSONObject jsonObject;
jsonObject = getDateTime(taobaoTime);
if (null != jsonObject && jsonObject.containsKey("data")) {
String time = jsonObject.getJSONObject("data").getString("t");
return LocalDateTime.ofEpochSecond(Long.parseLong(time) / 1000, 0, ZoneOffset.ofHours(8))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
jsonObject = getDateTime(suningTime);
if (null != jsonObject && jsonObject.containsKey("sysTime2")) {
return jsonObject.getString("sysTime2");
}
return null;

}

private JSONObject getDateTime(String url){
return restTemplate.getForObject(url,JSONObject.class);
}

shell:

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
time=$(curl -G -s http://34.0.7.227:9338/traffic/syncDateTime)
if [ ! -n "$time" ]; then
echo "time is null...."
else
date -s "${time}"
hwclock -w
fi

2019-03-15 更新

  1. shell 修改文件固定行的内容
1
sed -i "108c 'update content'" filename

2019-03-18 更新

删除 Ubuntu 多余内核

  1. dpkg --get-selections|grep linux : 查看全部安装的内核
  2. 确定当前使用的内核,一般为版本号最新的内核,删除旧内核: sudo apt remove linux-headers-4.15.0-43 linux-headers-4.15.0-43-generic linux-image-4.15.0-43-generic linux-modules-4.15.0-43-generic linux-modules-extra-4.15.0-43-generic
  3. 再次输入第一步的命令查看现在的内核信息,现在会看到刚才删除的内核会出现 deinstall 的状态
  4. 删除 deinstall 状态的内核: sudo dpkg -P xxxxxx
0%