@Configuration和@Component

Posted 白帆瀚宇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了@Configuration和@Component相关的知识,希望对你有一定的参考价值。

@Configuration注解和@Component注解的区别

首先看下@Configuration这个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration 

	@AliasFor(annotation = Component.class)
	String value() default "";
	
	boolean proxyBeanMethods() default true;

可以发现其实@Configuration注解本质上也是一个@Component注解.

@Configuration
@ComponentScan("com.aiqiong.admin")
public class AppConfig 


@Component
public class A 


    public static void main(String[] args) 
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        A a = (A) annotationConfigApplicationContext.getBean("a");
        System.out.println("a = " + a);
    

运行结果

可以看到A此时是一个普通的对象
把A类上的注解换成@Configuration

@Configuration
public class A 



运行结果:

可以看到加上Configuration注解之后,A变成了一个cglib的代理对象.

@Configuration注解生成cglib代理的作用

场景:


@Component
public class AppConfig 

    @Bean
    public B b()
        B b = new B();
        b.setA(a());
        return b;
    


    @Bean
    public A a()
        return new A();
    
    

public class A 

    public A()
        System.out.println("A....");
    

通过控制台的输出信息可以看到,其实A对象被创建了两次
换成@Configuration注解呢

@Configuration
public class AppConfig 

    @Bean
    public B b()
        B b = new B();
        b.setA(a());
        return b;
    

    @Bean
    public A a()
        return new A();
    


此时A只创建一次.

所以@Configuration注解就是为了避免用户显式地去调用@Bean方法,造成破坏了@Bean方法的bean的单例,而@Configuration注解能够做到这样避免用户显式调用@Bean方法的原理就是通过创建对应的配置类cglib代理对象去实现的.
下面我们重点看一下@Configuraton注解底层是怎么通过cglib代理去实现@Bean方法不被用户显式调用.

@Configuration动态代理的实现原理

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor接口又是继承于BeanFactoryPostProcessor接口,众所周知BeanFactoryPostProcessor接口接口是一个BeanFactory的后置处理接口,也就是在BeanFactory初始化之后会调用该接口的postProcessBeanFactory方法,也就是上面的方法,其中里面会再调用enhanceConfigurationClasses方法,该方法就是生成cglib代理的关键方法

	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
		int factoryId = System.identityHashCode(beanFactory);
		if (this.factoriesPostProcessed.contains(factoryId)) 
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + beanFactory);
		
		this.factoriesPostProcessed.add(factoryId);
		if (!this.registriesPostProcessed.contains(factoryId)) 
			// BeanDefinitionRegistryPostProcessor hook apparently not supported...
			// Simply call processConfigurationClasses lazily at this point then.
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		
		// 生成配置类cglib代理
		enhanceConfigurationClasses(beanFactory);
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) 
		Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
		// 遍历当前spring容器中所有的bd名称,这个for循环的目的就是找到所有全配置类(加了@Configuration注解的配置类)
		for (String beanName : beanFactory.getBeanDefinitionNames()) 
			// 根据bd名称获取到bd
			BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
			// 获取到CONFIGURATION_CLASS_ATTRIBUTE对应的属性值
			Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
			MethodMetadata methodMetadata = null;
			if (beanDef instanceof AnnotatedBeanDefinition) 
				methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
			
			if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) 
				// Configuration class (full or lite) or a configuration-derived @Bean method
				// -> resolve bean class at this point...
				AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
				if (!abd.hasBeanClass()) 
					try 
						abd.resolveBeanClass(this.beanClassLoader);
					
					catch (Throwable ex) 
						throw new IllegalStateException(
								"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
					
				
			
			// 条件成立:说明这个bd对应的是一个全配置类
			if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) 
				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.isInfoEnabled() && beanFactory.containsSingleton(beanName)) 
				
				configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
			
		
		// 条件成立:没有需要被增强的配置类
		if (configBeanDefs.isEmpty()) 
			// nothing to enhance -> return immediately
			return;
		
		// 如果这个类是一个全配置类,那么就使用cglib进行代理增强,目的是防止@Bean方法的手动重复调用造成单例的破坏
		ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
		for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) 
			AbstractBeanDefinition beanDef = entry.getValue();
			// If a @Configuration class gets proxied, always proxy the target class
			beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
			// Set enhanced subclass of the user-specified bean class
			Class<?> configClass = beanDef.getBeanClass();
			// 给这个全配置类创建一个cglib增强后的Class对象
			Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
			if (configClass != enhancedClass) 
				// 把cglib代理的Class对象设置到这个全配置类的bd中
				beanDef.setBeanClass(enhancedClass);
			
		
	

上面代码其实分成了两大步骤:
第一步骤是用来找到扩展属性CONFIGURATION_CLASS_ATTRIBUTE对应的值等于CONFIGURATION_CLASS_FULL的BeanDefinition.
这个属性是在执行postProcessBeanFactory方法之前,spring会解析所有的配置类,然后解析成对应的BeanDefinition.
而BeanDefinition又分成了两种,一种是全配置类,一种是半配置类.

  • 全配置类:加了@Configuration注解并且proxyBeanMethods属性等于true
  • 半配置类:加了@Component,@ComponentScan,@Import,@ImportResource这几个注解,或者@Configuration注解并且proxyBeanMethods属性等于false

对于全配置类CONFIGURATION_CLASS_ATTRIBUTE值等于CONFIGURATION_CLASS_FULL
对于半配置类CONFIGURATION_CLASS_ATTRIBUTE值等于CONFIGURATION_CLASS_LITE

第二步是把上面找出来的BeanDefinition创建cglib代理.使用的就是ConfigurationClassEnhancer这个工具类.

创建代理对象详解

概述

	public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) 
		if (EnhancedConfiguration.class.isAssignableFrom(configClass)) 
			
			return configClass;
		
		Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
		
		return enhancedClass;
	
	private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) 
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(configSuperClass);
		enhancer.setInterfaces(new Class<?>[] EnhancedConfiguration.class);
		enhancer.setUseFactory(false);
		enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
		enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
		enhancer.setCallbackFilter(CALLBACK_FILTER);
		enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
		return enhancer;
	

上面都比较简单,就是常规的创建代理对象需要的参数

	private Class<?> createClass(Enhancer enhancer) 
		Class<?> subclass = enhancer.createClass();
		Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
		return subclass;
	

最后调用enhancer的createClass方法,就可以得到一个增强后的Class对象,返回出去之后把这个增强的Class对象设置到BeanDefinition中就可以了
创建代理对象参数详解
(1) CallbackFilter

enhancer.setCallbackFilter(CALLBACK_FILTER);

作用:当执行目标方法的时候会被accept方法拦截,在accept方法中会根据传入的目标方法去返回一个Callback增强.
通过这个CallbackFilter就可以实现调用不同的方法使用不同的增强Callback.spring这里传入的是ConditionalCallbackFilter,这个CallbackFilter在创建的时候需要传入一个Callback数组.
这个数组会被accept方法过滤,返回一个具体的Callback返回.

private static class ConditionalCallbackFilter implements CallbackFilter 

		private final Callback[] callbacks;

		private final Class<?>[] callbackTypes;

		public ConditionalCallbackFilter(Callback[] callbacks) 
			this.callbacks = callbacks;
			this.callbackTypes = new Class<?>[callbacks.length];
			for (int i = 0; i < callbacks.length; i++) 
				this.callbackTypes[i] = callbacks[i].getClass();
			
		
		// 
		@Override
		public int accept(Method method) 
			for (int i = 0; i < this.callbacks.length; i++) 
				Callback callback = this.callbacks[i];
				if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) 
					return i;
				
			
			throw new IllegalStateException("No callback available for method " + method.getName());
		

		public Class<?>[] getCallbackTypes() 
			return this.callbackTypes;
		
	

上面提到一个Callback数组,那么这个数组中的元素是啥呢?

	private static final Callback[] CALLBACKS = new Callback[] 
			new BeanMethodInterceptor(),
			new BeanFactoryAwareMethodInterceptor(),
			// 不进行增强
			NoOp.INSTANCE
	;

根据对应isMatch方法来看一下另外两个都是什么时候回被调用

1、BeanMethodInterceptor

		public boolean isMatch(Method candidateMethod) 
		     // 不是Object对象、不是setBeanFactory方法,并且方法上要有@Bean注解
			return (candidateMethod.getDeclaringClass() != Object.class &&
					!BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
					BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
		

2、BeanFactoryAwareMethodInterceptor

		public boolean isMatch(Method candidateMethod) 
			return isSetBeanFactory(candidateMethod);
		
		
		public static boolean isSetBeanFactory(Method candidateMethod) 
			// 是setBeanFactory方法
			return (candidateMethod.getName().equals("setBeanFactory") &&
					candidateMethod.getParameterCount() == 1 &&
					BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
					BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
		

那么最后我们返回的是哪一个增强器呢?
(2) EnhancedConfiguration

enhancer.setInterfaces(new Class<?>[] EnhancedConfiguration.class);
public interface EnhancedConfiguration extends BeanFactoryAware 

所以自然地cglib代理对象中自然就会有这个BeanFactoryAware接口的setBeanFactory方法了.
所以可以回答上面一个问题了,创建代理的时候使用的增强器是BeanFactoryAwareMethodInterceptor
那么setBeanFactory方法是在什么时候被调用的呢?
这就要回顾之前讲到的Bean的生命周期了.如果一个bean实现了BeanFactoryAware接口的话,在实例化的过程中会回调BeanFactoryAware接口的setBeanFactory方法,然后在spring回调setBeanFactory方法的时候,BeanFactoryAwareMethodInterceptor这个Callback就能拦截到,执行intercept方法中的增强逻辑.
在intercept方法中做的其实就是把回调回来的BeanFactory对象赋值给属性名称为beanFactory的属性.
beanFactory这个属性是哪里的?在哪里设置的呢?

(3) beanFactory

	enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));

setStrategy属性可以设置生成代理类的class文件字节码策略,这里具体设置的策略是BeanFactoryAwareGeneratorStrategy.

	private static class BeanFactoryAwareGeneratorStrategy extends
			ClassLoaderAwareGeneratorStrategy 

		public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) 
			super(classLoader);
		

		@Override
		protected ClassGenerator transform(ClassGenerator cg) throws Exception 
			ClassEmitterTransformer transformer = new ClassEmitterTransformer() 
				@Override
				public void end_class() 
					// 给生成的代理类添加一个属性
					// 属性名为 beanFactory
					// 访问域是public
					// 属性类型是BeanFactory
					declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
					super.end_class();
				
			;
			return new TransformingClassGenerator(cg, transformer);
		

	
	private static final String BEAN_FACTORY_FIELD = "$$beanFactory";

总结下BeanFactoryAwareMethodInterceptor这个Callback的作用了,它的作用就是在spring回调BeanFactoryAware接口的seetBeanFactory方法的时候,把回调的BeanFactory对象赋值给
beanFactory属性,而这个属性的由来就是cglib在创建代理类的时候添加进来的.

下面讲讲另外一种增强器
BeanMethodInterceptor

		public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
					MethodProxy cglibMethodProxy) throws Throwable 
			// 获取到$$beanFactory属性的值,也就是BeanFactory对象
			ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
			String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

			// Determine whether this bean is a scoped-proxy
			if (BeanAnnotationHelper.isScopedProxy(beanMethod)) 
				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 Sco

@configuration和@component之间的区别

@configuration和@component之间的区别是:@Component注解的范围最广,所有类都可以注解,但是@Configuration注解一般注解在这样的类上:这个类里面有@Value注解的成员变量和@Bean注解的方法,就是一个配置类。

    @configuration和@component相同点是都是注解在类上的注解。

    Spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service 和 @Controller。在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,但是从注释类的命名上,很容易看出这 3 个注释分别和持久层、业务层和控制层(Web 层)相对应。虽然目前这 3 个注释和 @Component 相比没有什么新意,但 Spring 将在以后的版本中为它们添加特殊的功能。所以,如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释,而用 @Component 对那些比较中立的类进行注释。

    configuration 英[kənˌfɪgəˈreɪʃn]    美[kənˌfɪgjəˈreɪʃn]    

    词类:n.    配置; 布局,构造; [化] (分子中原子的) 组态,排列; [物] 位形,组态;    

    例句:Prices range from$ 119 to$ 199, depending on the particular configuration.

    价格因具体配置而异,从119美元至199美元不等。

    component    英[kəmˈpəʊnənt]    美[kəmˈpoʊnənt]    

    词类:n. 成分; 组分; 零件; [数] 要素;    

    adj.    成分的; 组成的; 合成的; 构成的;    

    例句:Enriched uranium is a key component of a nuclear weapon

    浓缩铀是核武器的关键组成部分。

参考技术A Spring 注解@Component,@Service,@Controller,@Repository
Spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service 和 @Controller。在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,但是从注释类的命名上,很容易看出这 3 个注释分别和持久层、业务层和控制层(Web 层)相对应。虽然目前这 3 个注释和 @Component 相比没有什么新意,但 Spring 将在以后的版本中为它们添加特殊的功能。所以,如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释,而用 @Component 对那些比较中立的类进行注释。

在 一个稍大的项目中,通常会有上百个组件,如果这些组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找以及维护起来也不太方便。 Spring2.5为我们引入了组件自动扫描机制,他可以在类路径底下寻找标注了 @Component,@Service,@Controller,@Repository注解的类,并把这些类纳入进spring容器中管理。它的作用 和在xml文件中使用bean节点配置组件时一样的。要使用自动扫描机制,我们需要打开以下配置信息:
Java代码

1. <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
2. >
3.
4. <context:component-scan base-package=”com.eric.spring”>
5. </beans>
/*其中base-package为需要扫描的包(含所有子包)
@Service用于标注业务层组件,
@Controller用于标注控制层组件(如struts中的action),
@Repository用于标注数据访问组件,即DAO组件,
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
*/

6. @Service public class VentorServiceImpl implements iVentorService
7. @Repository public class VentorDaoImpl implements iVentorDao
8.
/*getBean的默认名称是类名(头字母小 写),如果想自定义,可以@Service(“aaaaa”)这样来指定,这种bean默认是单例的,如果想改变,可以使用 @Service(“beanName”) @Scope(“prototype”)来改变。可以使用以下方式指定初始化方法和销毁方法(方法名任意): @PostConstruct public void init()
*/
9.
10. @PreDestroy public void destory()
11.
注入方式:

把 DAO实现类注入到service实现类中,把service的接口(注意不要是service的实现类)注入到action中,注入时不要new 这个注入的类,因为spring会自动注入,如果手动再new的话会出现错误,然后属性加上@Autowired后不需要getter()和 setter()方法,Spring也会自动注入。至于更具体的内容,等对注入的方式更加熟练后会做个完整的例子上来。

注解:
在 spring的配置文件里面只需要加上<context:annotation-config/> 和<context:component-scan base-package="需要实现注入的类所在包"/>,可以使用base-package="*"表示全部的类。

<context:component-scan base-package=”com.eric.spring”>

其中base-package为需要扫描的包(含所有子包)
在接口前面标上@Autowired和@Qualifier注释使得接口可以被容器注入,当接口存在两个实现类的时候必须指定其中一个来注入,使用实现类首字母小写的字符串来注入,如:
@Autowired
@Qualifier("chinese")
private Man man;
使用Autowired注入的步骤:
1.首先根据类型找到所有可以满足条件的bean
2.判断bean长度,如果没有,则根据@autowired中的required属性进行判断是否抛出异常(默认为true)
3.如果多于一个,则尝试寻找最优的那一个,如果最优的未找到,则抛出异常
4.如果只有一个,则直接使用此bean
否则可以省略,只写@Autowired 。

@Service服务层组件,用于标注业务层组件,表示定义一个bean,自动根据bean的类名实例化一个首写字母为小写的bean,例如Chinese实例化为chinese,如果需要自己改名字则:@Service("你自己改的bean名")。

@Controller用于标注控制层组件(如struts中的action)

@Repository持久层组件,用于标注数据访问组件,即DAO组件

@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

@Service
public class VentorServiceImpl implements iVentorService


@Repository
public class VentorDaoImpl implements iVentorDao


getBean 的默认名称是类名(头字母小写),如果想自定义,可以@Service(“aaaaa”) 这样来指定,这种

bean默认是单例的,如果想改变,可以使用@Service(“beanName”) @Scope(“prototype”)来改变。

可以使用以下方式指定初始化方法和销毁方法(方法名任意):

@PostConstruct

public void init()



@PreDestroy

public void destory()


如果xml文件中配置了业务层的bean,则在业务实现层类上无需添加@Service注解。同样如果配置了持久层的bean也就无需在dao实现层类上添加@Repository

以上是关于@Configuration和@Component的主要内容,如果未能解决你的问题,请参考以下文章

@Configuration和@Component

@Configuration 和 @Component

一句话@Configuration和@Component的区别

@Configuration 和 @Component 到底有啥区别?

Spring中@Component和@Configuration的区别

@Configuration 和 @Component 的区别, 以及lite mode