配置类到底要不要加@Configuration
开始之前我们先来看一个问题
/*Config*/
@Configuration
@ComponentScan("com.kg.service")
public class MyConfig {
@Bean
public Myservice myservice() {
return new Myservice();
}
}
/*Service*/
@Service
public class Myservice {
public void query(){
System.out.println("query");
}
}
/*Test*/
public class test {
@Test
public void test(){
AnnotationConfigApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
Myservice myservice= (Myservice) app.getBean("myservice");
myservice.query();
}
}
我们会发现不管加不加@Configuration,我们的结果都是:
query
那到底要不要加@Configuration?我们来看看源码是怎么加载配置类的。
大家可以跟着我的步骤找一下源码。
ps:我的spring版本是5.0的
- AnnotationConfigApplicationContext(Config.class);
- refresh();
- invokeBeanFactoryPostProcessors();
- PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
- invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
- postProcessor.postProcessBeanDefinitionRegistry(registry); postProcessor是一个接口,大家可以跟着断点进去,或者直接找他的实现类ConfigurationClassPostProcessor
- processConfigBeanDefinitions(registry);
- ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory);
checkConfigurationClassCandidate就是我们今天的主角之一
/*截取部分源码*/
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
String className = beanDef.getBeanClassName();
if (className == null || beanDef.getFactoryMethodName() != null) {
return false;
}
AnnotationMetadata metadata;
if (beanDef instanceof AnnotatedBeanDefinition &&
className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
}
else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
metadata = new StandardAnnotationMetadata(beanClass, true);
}
else {
try {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
metadata = metadataReader.getAnnotationMetadata();
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex);
}
return false;
}
}
//判断当前这个beanDefinition中存在的类是不是加了@Configruation注解
if (isFullConfigurationCandidate(metadata)) {
//如果存在则加上标记Full
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (isLiteConfigurationCandidate(metadata)) {
//加了以下注解,则认为是Lite注解
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
Integer order = getOrder(metadata);
if (order != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, order);
}
return true;
}
直接看注释可以知道,Spring把Conifg分为Full和Lite,那哪些类是Lite呢?我们看isLiteConfigurationCandidate(metadata)这个方法
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// candidateIndicators是一个Set,其中存放了Lite类的Config定义
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
我们可以看见@Component、@ComponentScan、@Import、@ImportResource都可以作为Lite类的注解。也就是只要有以上的注解类型,那么Config配置类可以不加@Configuration。
那么Full和Lite标记的类有什么差别呢?我们不妨先做个试验。
public class test {
@Test
public void test(){
AnnotationConfigApplicationContext app=new AnnotationConfigApplicationContext(MyConfig.class);
MyConfig myConfig= (MyConfig) app.getBean("myConfig");
System.out.println(myConfig);
}
}
加@Configuration的输出:
com.kg.config.MyConfig$$EnhancerBySpringCGLIB$$767797@60d8c9b7
不加@Configuration输出:
com.kg.config.MyConfig@731f8236
可以明显看出,加了@Configuration的MyConfig使用的是CGLIB代理创建的。
那使用cglib创建的MyConfig和普通创建的MyConfig有什么差别呢?
我们再来看一个例子:
我们修改一下测试代码
/*修改Myservicfe构造方法*/
@Service
public class Myservice {
public Myservice(){
System.out.println("init");
}
public void query(){
System.out.println("query");
}
}
/*新增Myservice2*/
@Service
public class Myservice2 {
}
/*在修改Myconfig*/
@Configuration
@ComponentScan("com.kg.service")
public class MyConfig {
@Bean
public Myservice myservice() {
return new Myservice();
}
@Bean
public Myservice2 myservice2(){
myservice(); /*Mark*/
return new Myservice2();
}
}
public class test {
@Test
public void test() {
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(MyConfig.class);
Myservice2 myservice2 = (Myservice2) app.getBean("myservice2");
}
}
加@Configuration:
init
不加@Configuration
init
init
可以看出不加@Configuration时,调用了两次构造函数(创建了两次),这就违背了Spring单例原则(默认情况下Spring的bean是单例的)。设想一下,如果我们要避免这种多次创建情况,应该怎么做?
- 如果需要New一个对象,先判断spring是否存在这个Bean
- 如果存在则从Bean工厂中获取这个Bean返回
- 如果不存在则创建一个新的Bean
但是,MyConfig类的myservice2()已经写好了,这时候想要插手对象的生成怎么能做到?
spring的解决方案就是CGLIB,使用动态代理。
下面我们看看spring是怎么实现的,同样提供一个步骤,方便大家更容易找到代码:
- new AnnotationConfigApplicationContext(Config.class);
- refresh();
- invokeBeanFactoryPostProcessors(beanFactory);
- PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
- invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
- postProcessor.postProcessBeanFactory(beanFactory); postProcessor是一个接口,大家可以跟着断点进去,或者直接找他的实现类ConfigurationClassPostProcessor
- enhanceConfigurationClasses(beanFactory);
enhanceConfigurationClasses就是我们今天第二个主角
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
//判断是否是Full类型配置注解
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
//如果是Full则存进configBeanDefs
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
//遍历configBeanDefs 设置为set是为了去重
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
if (configClass != null) {
//对Full类型注解进行cglib代理
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
beanDef.setBeanClass(enhancedClass);
}
}
}
catch (Throwable ex) {
throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
我们继续进入enhancer.enhance(configClass, this.beanClassLoader)方法
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
//首先判断是否被代理过
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Ignoring request to enhance %s as it has " +
"already been enhanced. This usually indicates that more than one " +
"ConfigurationClassPostProcessor has been registered (e.g. via " +
"<context:annotation-config>). This is harmless, but you may " +
"want check your configuration and remove one CCPP if possible",
configClass.getName()));
}
return configClass;
}
//没有被代理进行cglib代理
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isDebugEnabled()) {
logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
}
return enhancedClass;
}
重点是newEnhancer(configClass, classLoader)方法 我们继续跟进
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
//cglib是基于继承来增强父类的
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
// BeanFactoryAwareGeneratorStrategy 会在动态生成的代理类中添加一个成员变量,cglib的使用不做详细介绍,感兴趣的同学可以百度一下
// 这里主要是传入一个beanFactory。为什么要传入beanFactory?
// 其实正如我们之前预想的,我们需要去bean工厂中获取已有的bean
// 同时我们可以看到变量的名字是$$beanFactory
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
//过滤方法,类似于我们之前所说的如果bean存在则从bean工厂拿,不存在则新建
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
spring的过滤和我们的过滤有什么区别?
我们跟代码
- CALLBACK_FILTER
- CALLBACKS
private static final Callback[] CALLBACKS = new Callback[] {
//类似于我们之前所说的如果bean存在则从bean工厂拿
new BeanMethodInterceptor(),
//设置一个beanFactory
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
- new BeanMethodInterceptor();
- intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,MethodProxy cglibMethodProxy);
/*
enhancedConfigInstance:代理类
*/
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// 通过enhancedConfigInstance中$$beanFactory获得beanFactory。
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
}
else {
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
//判断到底是用new还是get?
//spring实现和我们之前预想的是不同的,spring是通过判断执行的方法和调用方法是不是同一个方法来判断是否是初次创建,我们在下面详细介绍
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
if (logger.isWarnEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
"result in a failure to process annotations such as @Autowired, " +
"@Resource and @PostConstruct within the method's declaring " +
"@Configuration class. Add the 'static' modifier to this method to avoid " +
"these container lifecycle issues; see @Bean javadoc for complete details.",
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
}
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
判断到底是用new还是get?
还是上面的配置类
@Configuration
@ComponentScan("com.kg.service")
public class MyConfig {
@Bean
public Myservice myservice() {
return new Myservice();
}
@Bean
public Myservice2 myservice2(){
myservice(); /*Mark*/
return new Myservice2();
}
}
spring把方法分成两种,一种是当前调用的方法,第二种是要执行的方法
我们看他的代码
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}
如果当前调用的方法和要执行的方法是同一个,则new一个新的实例作为Bean。
反之,如果不是同一个,则使用&&BeanFactory获取目标bean。
如果对应到我们自己写的MyConfig上:
1.调用myservice()方法时,当前调用的方法=myservice,要执行的方法=myservice //新建bean
2.调用myservice2()方法时有两条
一、当前调用的方法=myservice2,要执行的方法=myservice //beanFactory获取bean
二、当前调用的方法=myservice2,要执行的方法=myservice2 //新建bean
总结
- @Configuration加与不加有什么区别?
加@Configuration会被spring标记为Full,后续会使用cglib代理MyConfig
不加@Configuration且有@Component、@ComponentScan、@Import、@ImportResource会被spring标记为Lite后续会普通创建MyConfig - 使用cglib创建和普通创建有什么区别?
cglib可以插手bean的创建,使用cglib在初次创建对象时会new一个bean到BeanFactory,第二次创建这个对象则会使用BeanFactory中的bean
普通创建则无法控制bean的产生,可能会出现重复创建,这就违背了spring单例原则 - spring是怎么判断是否是第一次创建的?
spring对方法定义了两种类型,既当前调用的方法和要执行的方法,如果当前调用的方法和要执行的方法是同一个,则new一个新的实例作为Bean,反之,如果不是同一个,则使用&&BeanFactory获取目标bean。
转载申明
- 博客中标注原创的文章,版权归原作者 kkgggkg 所有;
- 未经原作者允许不得转载本文内容,否则将视为侵权;
- 转载或者引用本文内容请注明来源及原作者;
- 对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。