john's tech blog

hope is coming


  • 首页

  • 标签

  • 归档

解开spring中Profile的神秘面纱之一

发表于 2020-04-06

今天在看

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

```Java
/**
* Name of property to set to specify active profiles: {@value}. Value may be comma
* delimited.
* <p>Note that certain shell environments such as Bash disallow the use of the period
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
* is in use, this property may be specified as an environment variable as
* {@code SPRING_PROFILES_ACTIVE}.
* @see ConfigurableEnvironment#setActiveProfiles
*/
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";

/**
* Name of property to set to specify profiles active by default: {@value}. Value may
* be comma delimited.
* <p>Note that certain shell environments such as Bash disallow the use of the period
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
* is in use, this property may be specified as an environment variable as
* {@code SPRING_PROFILES_DEFAULT}.
* @see ConfigurableEnvironment#setDefaultProfiles
*/
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";

这里面的

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
那么这个类里就包含了对我们这些配置的读写操作。参考网上的说法:

如果当spring.profiles.active属性被设置时,那么Spring会优先使用该属性对应值来激活Profile。当spring.profiles.active没有被设置时,那么Spring会根据spring.profiles.default属性的对应值来进行Profile进行激活。如果上面的两个属性都没有被设置,那么就不会有任务Profile被激活,只有定义在Profile之外的Bean才会被创建。

我们先看对默认配置的读写操作:

### setDefaultProfiles

```Java
/**
* Specify the set of profiles to be made active by default if no other profiles
* are explicitly made active through {@link #setActiveProfiles}.
* <p>Calling this method removes overrides any reserved default profiles
* that may have been added during construction of the environment.
* @see #AbstractEnvironment()
* @see #getReservedDefaultProfiles()
*/
//https://blog.csdn.net/jamet/article/details/77508182
@Override
public void setDefaultProfiles(String... profiles) {
Assert.notNull(profiles, "Profile array must not be null");
synchronized (this.defaultProfiles) {
this.defaultProfiles.clear();
for (String profile : profiles) {
validateProfile(profile);
this.defaultProfiles.add(profile);
}
}
}

/**
* Validate the given profile, called internally prior to adding to the set of
* active or default profiles.
* <p>Subclasses may override to impose further restrictions on profile syntax.
* @throws IllegalArgumentException if the profile is null, empty, whitespace-only or
* begins with the profile NOT operator (!).
* @see #acceptsProfiles
* @see #addActiveProfile
* @see #setDefaultProfiles
*/
protected void validateProfile(String profile) {
if (!StringUtils.hasText(profile)) {
throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text");
}
if (profile.charAt(0) == '!') {
throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator");
}
}

其中defaultProfiles是个Set集合,它会先验证传入的一个或多个profile字符串不能为空且不能以!开头,然后放入集合中。

doGetDefaultProfiles

获取默认的Profiles

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
/**
* Return the set of default profiles explicitly set via
* {@link #setDefaultProfiles(String...)} or if the current set of default profiles
* consists only of {@linkplain #getReservedDefaultProfiles() reserved default
* profiles}, then check for the presence of the
* {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
* to the set of default profiles.
* @see #AbstractEnvironment()
* @see #getDefaultProfiles()
* @see #DEFAULT_PROFILES_PROPERTY_NAME
* @see #getReservedDefaultProfiles()
*/

//https://blog.csdn.net/jamet/article/details/77508182
protected Set<String> doGetDefaultProfiles() {
synchronized (this.defaultProfiles) {
if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.defaultProfiles;
}
}

如果当前defaultProfiles为保留的Profiles(就是一个单例的包含default的集合),那么就尝试从propertyResolver
中获取,然后返回defaultProfiles集合。我们要注意到这边的Set集合实现其实是LinkedHashSet。
下回我们讲下ActiveProfiles的操作。

参考资料:

  1. 详解Spring中的Profile

springboot应用在同IP下Session冲突该怎么解决

发表于 2020-04-05

昨天晚上想解决一个springboot应用中session失效的问题,源于公司生产环境目前是一台服务器部署两个
tomcat,应用通过同一个ip访问的时候session会互相冲突。我在网上找了几个解决方案,要么是在tomcat
中

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
server配置上添加servlet.sesssion.cookie.name属性,但是这两个我修改后都不见效,最后还把tomcat源码
下载了下来,看它里面的代码是这么写的

```java
private static String getConfiguredSessionCookieName(Context context) {

// Priority is:
// 1. Cookie name defined in context
// 2. Cookie name configured for app
// 3. Default defined by spec
if (context != null) {
String cookieName = context.getSessionCookieName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}

SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
cookieName = scc.getName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
}

return null;
}

先从context获取sessionCookieName,如果没有那么再从应用的servletContext中获取sessionCookieConfig,看来我们的思路貌似是
没问题的,但是为什么就不生效呢?在浏览器里看cookieName还是

一时半会找不到答案。但是想想解决思路应该是对的,
1
2
3
4
最终目的要把cookie名称给修改掉。

今早醒来突然想到我们的springboot应用里是根据shiro框架来实现权限和会话管理的,会不会跟这个有关系呢?脑子里突然想到这个应该是个
正确的解决方向,于是乎爬起来看了下工程代码,不看不知道,一看就知道应该就是这边的问题,我们看到shiro框架里的```DefaultWebSessionManager

是这么写的:

1
2
3
4
5
6
7
public DefaultWebSessionManager() {
Cookie cookie = new SimpleCookie("JSESSIONID");
cookie.setHttpOnly(true);
this.sessionIdCookie = cookie;
this.sessionIdCookieEnabled = true;
this.sessionIdUrlRewritingEnabled = true;
}

到这里就明白了,为啥我们改了tomcat也改了springboot本身的配置不起效果,就是因为我们是通过shiro来控制用户会话和权限控制的,
所以相当于是shiro把tomcat还有spring内部的cookie给替代了,那么我们只要定义一个bean继承自shiro的cookie注入然后放入shiro不就行了。

1
2
3
4
@Component
@ConfigurationProperties(prefix = "shiro.cookie")
public class ShiroCookie extends SimpleCookie {
}

问题到这就迎刃而解了,所以我们解决问题的时候也不能在一个方向钻牛角尖,有时候答案可能就在眼前,需要我们转变思路,
换个角度思考有可能就豁然开朗了,另外看源码确实可以一定程度上提高我们解决问题的效率。

参考资料:

  1. 在Intellij idea下为tomcat7设置sessionCookieName
  2. tomcat修改jsessionid在cookie中的名称
  3. Spring boot 去除URL 里的 JSESSIONID

spring5.x源码分析之自定义PropertySources

发表于 2020-04-03

这两天太忙了没时间更新,上一期我们聊到spring在解析资源配置文件的时候会去获取当前上下文环境,
如果环境变量

1
2
3
4
5
6
7
8
9
10
11

```Java
/**
* Create and return a new {@link StandardEnvironment}.
* <p>Subclasses may override this method in order to supply
* a custom {@link ConfigurableEnvironment} implementation.
*/
protected ConfigurableEnvironment createEnvironment() {
//StandardEnvironment 构造函数 会 使他的抽象父类 也执行构造函数
return new StandardEnvironment();
}

注释写的很清晰,作用就是创建一个

1
2
3
4
5
6
7
8
9
10

那我们再来看看StandardEnvironment内部做了什么,它继承自```AbstractEnvironment```,
然后有两个私有静态final变量:

```Java
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

这两个变量 一个是 操作系统环境变量,另一个是JVM系统环境变量,他们干嘛用呢,我们看到StandardEnvironment
还定义了一个方法

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

```Java
/**
* Customize the set of property sources with those appropriate for any standard(适用于任何标准的)
* Java environment:
* <ul>
* <li>{@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME}
* <li>{@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}
* </ul>
* <p>Properties present in {@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME} will
* take precedence over those in {@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}.
* @see AbstractEnvironment#customizePropertySources(MutablePropertySources)
* @see #getSystemProperties()
* @see #getSystemEnvironment()
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {

//https://blog.csdn.net/rtuujnncc/article/details/78248733
//addLast最低优先级
//https://www.cnblogs.com/Baronboy/p/6030443.html
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

  1. MapPropertySource 是 System.getProperties()
  2. SystemEnvironmentPropertySource 是System.getenv()

这个方法是继承自它的抽象类

StandardEnvironment```的时候
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
会先调用```AbstractEnvironment```的构造函数,函数内部会调用customizePropertySources方法,并且会把
propertySources参数传入。

```Java
//可变的属性源集合
private final MutablePropertySources propertySources = new MutablePropertySources();
//把propertySources放入 Resolver中
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);

/**
* Create a new {@code Environment} instance, calling back to
* {@link #customizePropertySources(MutablePropertySources)} during construction to
* allow subclasses to contribute or manipulate(操作) {@link PropertySource} instances as
* appropriate.
* @see #customizePropertySources(MutablePropertySources)
*/
//调用 子类的customizePropertySources
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}

customizePropertySources也允许子类去重写,这样就允许子类去操作这个propertySources变量了,在
Spring中我们看到很多这样的设计,包括接口和抽象类的运用到处都是。关于这个自定义propertySources有什么用
我们下回再解!

spring5.x源码解析之加载bean过程前奏--Environment

发表于 2020-03-31

上篇文章里最后我们提到了spring会获取当前所处环境后调用它的resolveRequiredPlaceholders
方法,也就是解析我们传递路径参数中的占位符,那么它是如何确定当前处于什么环境的呢?

AbstractRefreshableConfigApplicationContext类会调用它的抽象父类AbstractApplicationContext
类中的getEnvironment方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Return the {@code Environment} for this application context in configurable
* form, allowing for further customization.
* <p>If none specified, a default environment will be initialized via
* {@link #createEnvironment()}.
*/

@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}

这个方法的注释很清晰,意思就是以可配置的格式返回当前应用程序上下文的环境对象。我们看到这个方法
已经重写了,它的定义其实是在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
是干嘛的呢?我们看官方文档:

Interface indicating a component that contains and exposes an {@link Environment} reference.
是一个接口,它可以用来表明一个组件包含并暴露Environment的引用。我们看它的定义就知道了:

```Java
public interface EnvironmentCapable {
/**
* Return the {@link Environment} associated with this component.
*/
//返回跟这个组件关联的环境
Environment getEnvironment();

}

All Spring application contexts are EnvironmentCapable, and the interface is used primarily
for performing {@code instanceof} checks in framework methods that accept BeanFactory
instances that may or may not actually be ApplicationContext instances in order to interact
with the environment if indeed it is available.

所有的Spring应用上下文都是带EnvironmentCapable接口的(可装配Environment),而且接口主要用于在框架方法中执行instanceof检查,
这些方法接受BeanFactory实例,实例可能是ApplicationContext实例,也可能不是,如果是带EnvironmentCapable那么就可以访问Environment了。

As mentioned, {@link org.springframework.context.ApplicationContext ApplicationContext}
extends EnvironmentCapable, and thus exposes a {@link #getEnvironment()} method; however,
{@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}
redefines {@link org.springframework.context.ConfigurableApplicationContext#getEnvironment
getEnvironment()} and narrows the signature to return a {@link ConfigurableEnvironment}.
The effect is that an Environment object is ‘read-only’ until it is being accessed from
a ConfigurableApplicationContext, at which point it too may be configured。

总体意思就是ApplicationContext继承了EnvironmentCapable接口,但是ConfigurableApplicationContext重新定义了getEnvironment方法,
而且返回了一个ConfigurableEnvironment,影响就是Environment对象变只读了,除非从ConfigurableApplicationContext访问,
这个时候它就是可配置的了。

我们在文章一开始就看到AbstractApplicationContext类实现了ConfigurableApplicationContext接口,所以我们看到在getEnvironment方法
中我们可以自己配置environment变量了。那么这个createEnvironment内部到底做了些什么呢?

下回再分解!

spring5源码解析之加载bean过程前奏

发表于 2020-03-30

Java生态圈里的Spring框架众所周知,研究它源码的大神也多如牛毛,小弟不才,也对它的源码很感兴趣,
不光是要一窥框架设计之精妙,也要在平时的开发过程中能借鉴其中的设计思想。今天这篇文章我们就来
聊聊它加载bean的过程中有什么值得学习的地方。

首先我们可以写个简单的demo,来一步步调试和窥探:

1
2
3
4
5
6
7
8
public static void main( String[] args )
{

String XMLPath = "spring-config.xml";
//
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
Landlord landlord = (Landlord) applicationContext.getBean("landlord", Landlord.class);
landlord.service();
}

spring-config.xml配置文件中我们就定义了一个简单的bean元素,那么我们先自己简单地想下Spring内部要解决哪些
问题它才能获取到这个实例,我们要带着问题去阅读源码才会有所收获。我首先想到的就是如下这些:

  1. 它是如何定位xml配置文件并读取的
  2. 如何解析xml配置文件中的bean元素和属性
  3. 解析完了以后这些配置放到哪里呢
  4. getBean的时候又是去哪获取的呢

定位xml配置文件

我们在把一个文件名放入ClassPathXmlApplicationContext构造函数的时候,它内部就开始了定位逻辑,

因为它支持多个配置文件路径,所以我们传入一个路径的时候它会new一个String数组,调用其他构造函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
//这里我们学到了可变参数的用途
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)

throws BeansException {

//省略部分代码
//AbstractRefreshableConfigApplicationContext的setConfigLocations方法
setConfigLocations(configLocations);
}

最后就调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
我们可以在ClassPathXmlApplicationContext类中自己实现,这就是一个很好的设计之处。
AbstractRefreshableApplicationContext的setConfigLocations方法如下:
```Java
/**
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {

this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

它的注释说的很明白,如果我们不调用setConfigLocations,那它就会根据情况用一个默认路径了,后果不看设想。。
显然我们要重点关注的是

1
2
3
4
5
6
7
8
9
10
11
12

```Java
/**
* Resolve the given path, replacing placeholders with corresponding
* environment property values if necessary. Applied to config locations.
* @param path the original file path
* @return the resolved file path
* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}

我们又要看它的注释了,很清楚,根据给定的path路径字符串,如果必要的话会用环境变量替换它里面占位符,然后赋给配置路径,
比如我们把spring-config.xml 换成${java.version}spring-config.xml传入,那么它会把${java.version}用我们环境变量替换掉
具体要看org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)里面的代码了。

我们下回再解!

1…567…47

John

232 日志
43 标签
GitHub Twitter
欢迎关注我的公众号:沉迷Spring
© 2023 johnwonder
由 Hexo 强力驱动 v3.2.0
|
主题 – NexT.Pisces v7.1.1
|
0%