当启动类放置在
com.psj.A
包中,控制器类放置在com.psj.B
包中时,启动SpringBoot后是扫描不到控制器的
SpringBoot的默认扫描规则是扫描启动类所在的包及其子包中是否存在控制器
@SpringBootApplication
// @ComponentScan("com.psj.B") // 显式指定其它包,原来的默认扫描包就被忽略,即扫描不到com.psj.A下的控制器
@ComponentScans(value = { @ComponentScan(value ="com.psj.B") })
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
运行下面代码会出现错误:
Parameter 0 of constructor in com.psj.demo.service.impl.ServiceImpl required a bean of type 'java.lang.String' that could not be found.
@Service public class ServiceImpl { private String serviceName; public ServiceImpl(String serviceName){ this.serviceName = serviceName; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
当创建一个Bean时调用的方法是
AbstractAutowireCapableBeanFactory#createBeanInstance
,它主要包含两大基本步骤:
寻找构造器:Spring会先执行
determineConstructorsFromBeanPostProcessors
方法来获取构造器通过反射调用构造器创建实例:通过
autowireConstructor
方法带着构造器去创建实例
- 调用
instantiate
方法时需要获取参数argsToUse
argsToUse
通过调用createArgumentArray
方法来构建调用构造器的参数数组,该方法的最终实现是从BeanFactory
中获取BeanargsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
- 1
- 2
定义一个类为 Bean后如果再显式定义构造器,则该Bean在构建时会自动根据构造器参数定义寻找对应的Bean,然后反射创建这个Bean
// 在启动类中添加下面代码
@Bean
public String serviceName() {
return "psj";
}
在Spring框架中,Bean的生命周期由实例化、属性赋值和初始化/销毁三个主要阶段组成,而Bean的作用域是用来定义Bean的生命周期
Spring中的原型Bean(prototype Bean)是一种作用域,意味着每次需要一个新的Bean时,Spring都会创建一个新的实例。这和单例Bean(singleton Bean)不同,单例Bean意味着无论你请求多少次,Spring都只创建一个Bean实例。
在Spring中可通过在Bean的配置类上添加
@Scope("prototype")
注解或在XML配置文件中设置元素的
scope
属性为prototype
将Bean的作用域设置为原型使用原型Bean的主要好处是减少对象创建和销毁的开销(每次创建新的Bean实例则不需要额外的内存保存已创建的实例,也不需要额外的开销来处理这些实例的销毁),在处理大量对象或需要频繁创建新实例的情况下尤其有用。缺点是增加了应用程序的内存消耗(每个请求都会创建新的Bean实例)
当使用原型Bean时,如果Bean中包含任何依赖则每次请求新的Bean实例时,这些依赖也需被重新创建
通过下面代码定义Bean为原型Bean后,每次访问服务使用的Bean都是一样的,相当于创建的是单例Bean:
@Service @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ServiceImpl {}
- 1
- 2
- 3
@RestController public class HelloWorldController { @Autowired private ServiceImpl serviceImpl; @RequestMapping(path = "hi", method = RequestMethod.GET) public String hi(){ return "helloworld, service is : " + serviceImpl; }; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
当属性成员serviceImpl声明为
@Autowired
后,在创建HelloWorldController Bean
时,会先使用构造器反射出实例,然后来装配各个标记为@Autowired
的属性成员在执行过程会使用很多
BeanPostProcessor
来做完成工作,其中一种是AutowiredAnnotationBeanPostProcessor
,它会寻找到ServiceImpl类型的Bean然后设置给对应serviceImpl成员
由于
field.set()
方法的执行只发生了一次,后续就固定起来了,它并不会因为ServiceImpl标记了SCOPE_PROTOTYPE
而改变@Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; Object value; // 寻找bean if (this.cached) { try { value = resolveCachedArgument(beanName, this.cachedFieldValue); } catch (BeansException ex) { // Unexpected target bean mismatch for cached argument -> re-resolve this.cached = false; logger.debug("Failed to resolve cached argument", ex); value = resolveFieldValue(field, bean, beanName); } } else { value = resolveFieldValue(field, bean, beanName); } if (value != null) { // 将bean设置给成员字段 ReflectionUtils.makeAccessible(field); field.set(bean, value); } }
- 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
- 当一个单例的 Bean,使用autowired注解标记其属性时,该属性值会被固定下来
// 思想:保证每次访问接口都会创建新的Bean
@RestController
@Slf4j
public class HelloWorldController {
@Autowired
private ApplicationContext applicationContext;
@RequestMapping(path = "hi", method = RequestMethod.GET)
public String hi(){
return "helloworld, service is : " + getServiceImpl();
};
// 方法1:自动注入Context
public ServiceImpl getServiceImpl(){
return applicationContext.getBean(ServiceImpl.class);
}
// 方法2:使用@Lookup,通过该注解产生一个CGLIB子类,即创建一个新的Bean
// 该方法体中的内容并不会被执行,内容是什么并不重要
@Lookup
public ServiceImpl getServiceImpl(){
log.info("executing this method");
return null;
}
}
极客时间-Spring 编程常见错误 50 例
https://github.com/jiafu1115/springissue/tree/master/src/main/java/com/spring/puzzle/class1