Spring Core 官方文档阅读笔记(五)
- @ComponentScan
通过@ComponentScan注解,可以扫描指定路径下的所有Bean,并将可注册的Bean装配到Spring容器中。其功能等同于context:component-scan。
若使用context:component-scan,则隐式启用了context:annotation-config。
/**
* @Author: kuromaru
* @Date: Created in 10:10 2019/3/13
* @Description:
* @modified:
*/
@Service
public class MyService {
public void sayHello() {
System.out.println("Say Hello");
}
}
/**
* @Author: kuromaru
* @Date: Created in 10:10 2019/3/13
* @Description:
* @modified:
*/
@Repository
public class MyDao {
public void getString() {
System.out.println("get String");
}
}
/**
* @Author: kuromaru
* @Date: Created in 10:11 2019/3/13
* @Description:
* @modified:
*/
@Configuration
@ComponentScan(basePackages = "configure.componentScan")
public class MyConfig {
}
/**
* @Author: kuromaru
* @Date: Created in 10:12 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
System.out.println(acac.getBean(MyDao.class));
}
}
- 过滤器自定义扫描
可以通过@includeFilters和@excludeFilters注解来自定义扫描的范围,如下
/**
* @Author: kuromaru
* @Date: Created in 10:10 2019/3/13
* @Description:
* @modified:
*/
@Service
public class MyService {
public void sayHello() {
System.out.println("Say Hello");
}
}
/**
* @Author: kuromaru
* @Date: Created in 10:10 2019/3/13
* @Description:
* @modified:
*/
@Repository
public class MyDao {
public void getString() {
System.out.println("get String");
}
}
/**
* @Author: kuromaru
* @Date: Created in 10:11 2019/3/13
* @Description:
* @modified:
*/
@Configuration
@ComponentScan(basePackages = "configure.componentScan",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = RestController.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
public class MyConfig {
// 需要把defaultFilters禁用,否则还是可以查询到包里的除被Repository注解的以外的bean
}
若将useDefaultFilters设置为false,实际上就是禁用了对@Component、@Repository、@Service、@Controller和@Configuration注释的自动扫描。
/**
* @Author: kuromaru
* @Date: Created in 10:12 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
System.out.println(acac.getBean(MyDao.class));
System.out.println(acac.getBean(MyService.class));
}
}
以上代码执行的结果都是找不到Bean实例。
@ComponentScan.Filter的type属性,可以传入以下值:
过滤器类型 | 描述 |
---|---|
Annotation(default) | 匹配指定注解类型 |
assignable | 目标组件可分配给(扩展或实现)的类(或接口) |
aspectJ | 匹配AspectJ表达式 |
regex | 匹配正则表达式 |
custom | org.springframework.core.type.TypeFilter接口的自定义实现 |
其中单独对assignable做下说明,代码如下:
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParentAnnotation {
}
/**
* @Author: kuromaru
* @Date: Created in 10:56 2019/3/13
* @Description:
* @modified:
*/
@ParentAnnotation
public class MyBean {
}
/**
* @Author: kuromaru
* @Date: Created in 10:57 2019/3/13
* @Description:
* @modified:
*/
public class MySubBean extends MyBean {
}
/**
* @Author: kuromaru
* @Date: Created in 10:11 2019/3/13
* @Description:
* @modified:
*/
@Configuration
@ComponentScan(basePackages = "configure.componentScan",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = ParentAnnotation.class))
public class MyConfig {
}
/**
* @Author: kuromaru
* @Date: Created in 10:12 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
System.out.println(acac.getBean(MyBean.class));
System.out.println(acac.getBean(MySubBean.class));
}
}
输出结果:
三月 13, 2019 11:05:44 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 11:05:44 CST 2019]; root of context hierarchy
configure.componentScan.MyBean@1cab0bfb
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'configure.componentScan.MySubBean' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1092)
at configure.componentScan.Client.main(Client.java:20)
可以看到,如果单纯使用FilterType.ANNOTATION的情况下,子类是没有被装配的。对MyConfig类做如下改动:
/**
* @Author: kuromaru
* @Date: Created in 10:11 2019/3/13
* @Description:
* @modified:
*/
@Configuration
@ComponentScan(basePackages = "configure.componentScan",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyBean.class))
public class MyConfig {
}
/**
* @Author: kuromaru
* @Date: Created in 10:12 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
System.out.println(acac.getBean(MySubBean.class));
}
}
输出结果:
三月 13, 2019 11:09:31 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 11:09:31 CST 2019]; root of context hierarchy
configure.componentScan.MySubBean@2473b9ce
可以看到,子类也被装配到容器中了。因此,assignable可以理解为,将子类也一同装配到spring IOC容器中。
上述配置也可以用xml来代替,写法如下:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
-
@Inherited注解的语义是,被此注解标注的注解是可以被继承的。即自定义一个注解,并标注@inherited,在父类上使用此注解,新建子类继承父类,则子类也会被容器认为标注了该注解。但是,若注解标注在父类的抽象方法上,或者子类重写了父类标注了注解的方法,则注解不会被继承。
-
@Configuration
@Configuration注解可以用于将Bean装配至springIOC容器,其实现代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}
可以看到,@Configuration注解被标注了@Component,因此,本质上来讲,@Configuration也是一个@Component,被它注解的类可以被context:component-scan/或者@ComponentScan处理。
需要注意的是,在使用@Configuration是,有以下几点约束:
- 被注解的类必须作为类提供(例如,不是作为从工厂方法返回的实例),从而允许通过生成的子类进行运行时增强(cglib 动态代理)。
- 被注解的类必须是非final修饰的(final类不能被动态代理)
- 被注解的类必须是非private修饰的
- 任何嵌套的配置类必须声明为静态的
- @Bean方法可能不会创建进一步的配置类(任何此类实例都将被视为常规bean,其配置注释仍未被检测到,也就是说如果返回的类即使带有@Configuration,也只是被做为普通的Bean来处理)
@Configuration有一个对应的ConfigurationClassPostProcessor,是一个BeanFactoryPostProcessor,回顾前面Spring Core 官方文档阅读笔记(三)中整理的内容,可知ConfigurationClassPostProcessor会在容器启动的时候执行一次,里面的逻辑就是获取BeanFactory中的所有被@Configuration注解的Bean,并逐一通过CGLIB做增强处理,在增强处理中注册了两个Interceptor--BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor,在BeanMethodInterceptor的拦截处理中,会判断当前执行的方法是否是被@Bean注解的方法,如果是,则说明Bean需要被新建实例,如果不是,如在@Bean方法中引用另一个@Bean方法,则直接去容器中获取实例。因此,如下代码中的两个myBean()返回的是同一个实例:
@Configuration
public class MyConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public MyReference() {
return new MyReference(myBean());
}
}
/**
* @Author: kuromaru
* @Date: Created in 15:21 2019/3/13
* @Description:
* @modified:
*/
public class MyBean {
}
/**
* @Author: kuromaru
* @Date: Created in 15:22 2019/3/13
* @Description:
* @modified:
*/
public class MyReference {
private MyBean myBean;
public MyReference(MyBean myBean) {
this.myBean = myBean;
}
public MyBean getMyBean() {
return myBean;
}
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
}
/**
* @Author: kuromaru
* @Date: Created in 15:23 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
MyBean myBean = acac.getBean(MyBean.class);
MyReference myReference = acac.getBean(MyReference.class);
System.out.println(myBean == myReference.getMyBean());
}
}
输出结果:
三月 13, 2019 3:25:00 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 15:25:00 CST 2019]; root of context hierarchy
true
而@Component注解并没有去通过CGLIB来代理@Bean方法的调用,所以下面的代码会生成两个不同的myBean:
@Component
public class MyConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public MyReference() {
return new MyReference(myBean());
}
}
其他代码不做修改,执行结果如下:
三月 13, 2019 4:34:19 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 16:34:19 CST 2019]; root of context hierarchy
false
若想保证使用相同的实例,又不想使用代理,可以做如下改动:
@Component
public class MyConfig {
@Autowired
private MyBean myBean;
@Bean
public MyBean myBean() {
return new MyBean();
}
@Bean
public MyReference() {
return new MyReference(myBean);
}
}
执行结果如下:
三月 13, 2019 4:37:22 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 16:37:22 CST 2019]; root of context hierarchy
true
- InjectionPoint(注入点)
InjectionPoint做为依赖的注入点,保存了依赖的详细信息,如注解、参数、成员变量等等。
具体使用方法如下:
/**
* @Author: kuromaru
* @Date: Created in 16:40 2019/3/13
* @Description:
* @modified:
*/
public class MyBean {
private String name;
public MyBean(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/**
* @Author: kuromaru
* @Date: Created in 16:53 2019/3/13
* @Description:
* @modified:
*/
@Component
public class MyComponent {
@Autowired
private MyBean myBean;
public void sayHello() {
System.out.println("Hello ~ " + myBean.getName());
}
}
/**
* @Author: kuromaru
* @Date: Created in 16:40 2019/3/13
* @Description:
* @modified:
*/
@Configuration
public class MyConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyBean myBean(InjectionPoint injectionPoint) {
return new MyBean(injectionPoint.getMember().getName());
}
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}
/**
* @Author: kuromaru
* @Date: Created in 16:43 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
MyComponent myComponent = acac.getBean(MyComponent.class);
myComponent.sayHello();
}
}
输出结果:
三月 13, 2019 5:01:02 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 17:01:02 CST 2019]; root of context hierarchy
Hello ~ myBean
需要注意的是,InjectionPoint只对新建实例起作,而不适用于注入现有实例。如果将Scope设置为singleton,则会报错,如下:
/**
* @Author: kuromaru
* @Date: Created in 16:40 2019/3/13
* @Description:
* @modified:
*/
@Configuration
public class MyConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public MyBean myBean(InjectionPoint injectionPoint) {
return new MyBean(injectionPoint.getMember().getName());
}
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}
报错信息:
三月 13, 2019 5:07:10 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 17:07:10 CST 2019]; root of context hierarchy
Exception in thread "main" java.lang.IllegalStateException: No current InjectionPoint available for method 'myBean' parameter 0
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:831)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:467)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1181)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1075)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at configure.InjectionPoint.Client.main(Client.java:16)
- 将已注入的Bean的属性做为参数注入
在配置bean的过程中,可以将已注入的Bean的属性做为参数注入,如下代码:
/**
* @Author: kuromaru
* @Date: Created in 17:11 2019/3/13
* @Description:
* @modified:
*/
public class MyBean {
private String name;
public MyBean(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:11 2019/3/13
* @Description:
* @modified:
*/
public class MyReference {
private String name;
public MyReference(String name) {
System.out.println("reference bean name is " + name);
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:12 2019/3/13
* @Description:
* @modified:
*/
@Configuration
public class MyConfig {
@Bean
public MyBean myBean() {
return new MyBean("pretty bean");
}
@Bean
public MyReference myReference(@Value("#{myBean.name}") String name) {
return new MyReference(name);
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:15 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
acac.refresh();
}
}
输出结果:
三月 13, 2019 5:17:17 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27fa135a: startup date [Wed Mar 13 17:17:17 CST 2019]; root of context hierarchy
reference bean name is pretty bean
- BeanNameGenerator
如果存在Bean名字冲突的情况,可以通过实现BeanNameGenerator,自定义命名规则来解决。
BeanNameGenerator是一个接口,里面只有一个接口方法
String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
Spring已经提供了两个实现类:AnnotationBeanNameGenerator和DefaultBeanNameGenerator
- AnnotationBeanNameGenerator
AnnotationBeanNameGenerator可以为那些被@Component注解的或者被本身使用@Component做为元注解的注解的类提供命名规则(有点绕。。。),举个例子,@Repository本身使用@Component做为元注解,因此,被@Repository所注解的类的名称也被AnnotationBeanNameGenerator控制。 - DefaultBeanNameGenerator
DefaultBeanNameGenerator是接口BeanNameGenerator的默认实现。
可以自己实现接口或者继承上面两个类中任意一个来自定义命名规则,代码如下:
/**
* @Author: kuromaru
* @Date: Created in 17:27 2019/3/13
* @Description:
* @modified:
*/
@Component
public class MyBean {
private String name;
public String getName() {
return name;
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:27 2019/3/13
* @Description:
* @modified:
*/
@Component("myBean") // 与MyBean的名称一致
public class MyBean1 {
private String name;
// public MyBean1(MyBean myBean) {
// this.name = myBean.getName();
// }
public String getName() {
return name;
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:27 2019/3/13
* @Description:
* @modified:
*/
public class MyComponent {
private boolean isSame;
public MyComponent(MyBean myBean, MyBean1 myBean1) {
this.isSame = myBean.equals(myBean1);
}
public boolean isSame() {
return isSame;
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:27 2019/3/13
* @Description:
* @modified:
*/
@Component
@ComponentScan(basePackages = "configure.nameGenerator", nameGenerator = MyNameGenerator.class)
public class MyConfig {
@Autowired
private MyBean myBean;
@Autowired
private MyBean1 myBean1;
@Bean
private MyComponent myComponent() {
return new MyComponent(myBean, myBean1);
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:22 2019/3/13
* @Description:
* @modified:
*/
public class MyNameGenerator extends AnnotationBeanNameGenerator {
private static final String PREFIX_NAME = "kaku.";
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return getBeanName(definition, registry);
}
private String getBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
String beanClassName = Introspector.decapitalize(definition.getBeanClassName());
String id = PREFIX_NAME + beanClassName;
if (registry.containsBeanDefinition(id)) {
int counter = -1;
while (counter == -1 || registry.containsBeanDefinition(id)) {
counter++;
id = id + counter;
}
}
return id;
}
}
/**
* @Author: kuromaru
* @Date: Created in 17:29 2019/3/13
* @Description:
* @modified:
*/
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext();
acac.register(MyConfig.class);
// 这里可以不设置,如果MyConfig里面配置了
// @ComponentScan(basePackages = "configure.nameGenerator", nameGenerator = MyNameGenerator.class)
// 这两个配置是重复的
acac.setBeanNameGenerator(new MyNameGenerator());
acac.refresh();
Arrays.stream(acac.getBeanFactory().getBeanDefinitionNames()).forEach(System.out::println);
}
}
输出结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfig
kaku.configure.nameGenerator.MyBean
kaku.configure.nameGenerator.MyBean1
myComponent
上面代码中的acac.setBeanNameGenerator和@ComponentScan(nameGenerator="xxx")任选一种就可以,两个设置是重复的,也可以在xml里做配置,如下:
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
- ScopeMetadataResolver
可以通过实现ScopeMetadataResolver接口来自定义范围解析器,通过@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
来使用自定义范围解析器,通过@ComponentScan的scopedProxy = ScopedProxyMode.INTERFACES设置来指定scopeProxy,有三个值可供选择:no,interface(jdk代理), targetClass(cglib代理)。
也可以通过xml配置实现:
<beans>
<context:component-scan base-package="org.example"
scope-resolver="org.example.MyScopeResolver"
scopedProxy=ScopedProxyMode.INTERFACES/>
</beans>
- 若不希望调用Bean的销毁回调方法,可以如下配置:
@Bean(destoryMethod="")
- 可以对@Bean设置别名,如下
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
- 可以对@Bean设置描述,如下
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
- @PropertySource
@PropertySource可以添加配置项到Environment,供程序使用,如下
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
${...}占位符可以设置默认值,如下
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
上述代码的意思是,获取属性中的my.placeholder的值,如果没有,则以'default/path'为默认值