Spring - 注解驱动开发一组件添加

【1】@Configuration和@bean

从Spring3.0开始,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)。

AnnotationConfigWebApplicationContext类继承图如下所示:

Spring - 注解驱动开发一组件添加_第1张图片


AnnotationConfigApplicationContext类继承图如下所示:

Spring - 注解驱动开发一组件添加_第2张图片


配置类实例如下:

package com.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.ComponentScans;

import com.web.bean.Person;

//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类
public class MainConfig {
	//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
	@Bean("person")
	public Person person01(){
		return new Person("lisi", 20);
	}

}


测试类如下:

package com.web.test;

import java.util.Map;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import com.web.config.MainConfig;

public class IOCTest {

/*用AnnotationConfigApplicationContext 替换ClassPathXmlApplicationContext。
如果加载spring-context.xml文件:
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
*/
	AnnotationConfigApplicationContext applicationContext = 
	new AnnotationConfigApplicationContext(MainConfig.class);

	@SuppressWarnings("resource")
	@Test
	public void test01(){
		AnnotationConfigApplicationContext applicationContext = 
		new AnnotationConfigApplicationContext(MainConfig.class);
		// 获取容器中注册的bean的信息
		String[] definitionNames = applicationContext.getBeanDefinitionNames();
		for (String name : definitionNames) {
			System.out.println(name);
		}
	}
}


【2】@Bean 注解##

@Bean是一个方法级别上的注解,效果等同于在xml中配置一个bean,并设置其属性。

xml配置如下:

  
 

等同于如下:

//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
	@Scope("singleton")
	@Lazy
	@Bean(name="person",initMethod="init",destroyMethod="cleanUp",
	autowire=Autowire.BY_NAME)
	public Person person01(){
		return new Person("lisi", 20);
	}

Spring容器中注册的bean,默认是单实例的。其作用域说明如下:

ConfigurableBeanFactory#SCOPE_PROTOTYPE    
ConfigurableBeanFactory#SCOPE_SINGLETON  
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST  request
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION	 sesssion


@Scope:调整作用域

prototype:

多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象。

singleton:

单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器(map.get())中拿。

request:同一次请求创建一个实例。

session:同一个session创建一个实例。

需要注意的是,单实例bean 初始化方法在容器创建时被调用,销毁方法在容器销毁时被调用。多实例bean,初始化方法在第一次获取bean的时候调用(非容器创建时,容器创建时会调用构造方法),销毁方法容Spring 容器不负责管理。


懒加载说明如下:

单实例bean:默认在容器启动的时候创建对象;

懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化。

多实例懒加载测试如下:

	@Test
	public void test02(){
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
		System.out.println("ioc容器创建完成....");
		Object bean = applicationContext.getBean("person");
		Object bean2 = applicationContext.getBean("person");
		System.out.println(bean == bean2);
	}

result as follows :

ioc容器创建完成....
给容器中添加Person....
给容器中添加Person....
false//两个bean不同哦

关于Spring中Bean的作用域与生命周期参考博文:Spring中Bean的作用域。


【3】@ComponentScan注解

xml配置中必不可少的标签,用来扫描bean并注册到IOC容器中。

xml配置示例如下:


 
 	
 	
 

其中,type有五种形式:

  • annotation-注解,
  • assignable-给定的类型,
  • regex-正则指定,
  • custom-自定义规则
  • aspectj-ASPECTJ表达式。

其javadoc如下:

Controls the type of filtering to apply to the expression. 
"annotation" indicates an annotation to be present at the type 
 level in target components; 
 "assignable" indicates a class (or interface) that the target components are assignable to (extend/implement); 
 "aspectj" indicates an AspectJ type pattern expression to be matched by the target components; 
 "regex"indicates a regex pattern to be matched by the target 
 components' class names; 
 "custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface. 
 Note: This attribute will not be inherited by child bean 
 definitions. 
 Hence, it needs to be specified per concrete bean 
 definition.

配置类如下:

//配置类==配置文件
@Configuration  //告诉Spring这是一个配置类

@ComponentScans(
	value = {
		@ComponentScan(value="com.web",includeFilters = {
			@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
			@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
			@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
		},useDefaultFilters = false)	
	}
)
//@ComponentScan  value:指定要扫描的包
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则
public class MainConfig {
	
	//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
	@Bean(name="person")
	public Person person01(){
		return new Person("lisi", 20);
	}

}

FilterType.CUSTOM,classes={MyTypeFilter.class}中的MyTypeFilter类如下:

public class MyTypeFilter implements TypeFilter {

	/**
	 * metadataReader:读取到的当前正在扫描的类的信息
	 * metadataReaderFactory:可以获取到其他任何类信息的
	 */
	@Override
	public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException {
		// TODO Auto-generated method stub
		//获取当前类注解的信息
		AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
		//获取当前正在扫描的类的类信息
		ClassMetadata classMetadata = metadataReader.getClassMetadata();
		//获取当前类资源(类的路径)
		Resource resource = metadataReader.getResource();
		
		String className = classMetadata.getClassName();
		System.out.println("--->"+className);
		// 这里自定义规则
		if(className.contains("er")){
			return true;
		}
		return false;
	}

}


【4】@Conditional条件判断

@Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean。

配置类如下:

/**
	 * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
	 * 
	 * 如果系统是windows,给容器中注册("bill")
	 * 如果是linux系统,给容器中注册("linus")
	 */
	@Conditional(WindowsCondition.class)
	@Bean("bill")
	public Person person01(){
		return new Person("Bill Gates",62);
	}
	
	@Conditional(LinuxCondition.class)
	@Bean("linus")
	public Person person02(){
		return new Person("linus", 48);
	}

Conditional接口如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition}s that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class[] value();

}

既可以作用于方法上面,也可以配置在类上面,参数为Condition的实现类。


Condition接口如下:

public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
	 * or {@link org.springframework.core.type.MethodMetadata method} being checked.
	 * @return {@code true} if the condition matches and the component can be registered
	 * or {@code false} to veto registration.
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

条件类如下:

//判断是否linux系统
public class LinuxCondition implements Condition {

	/**
	 * ConditionContext:判断条件能使用的上下文(环境)
	 * AnnotatedTypeMetadata:注释信息
	 */
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// TODO是否linux系统
		//1、能获取到ioc使用的beanfactory
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		//2、获取类加载器
		ClassLoader classLoader = context.getClassLoader();
		//3、获取当前环境信息
		Environment environment = context.getEnvironment();
		//4、获取到bean定义的注册类
		BeanDefinitionRegistry registry = context.getRegistry();
		
		String property = environment.getProperty("os.name");
		
		//可以判断容器中的bean注册情况,也可以给容器中注册bean
		boolean definition = registry.containsBeanDefinition("person");
		if(property.contains("linux")){
			return true;
		}
		
		return false;
	}

}

如果该注解配置在类上面,则该配置类的所有方法都将使用该注解。示例如下:

@Conditional({WindowsCondition.class})
@Configuration
public class MainConfig2 {
	//...
}

@Confitional扩展如下图:

Spring - 注解驱动开发一组件添加_第3张图片


【5】@import导入组件

@Import ,快速给容器中导入一个组件。

其注解如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class[] value();

}

其使用方式如下:

1)@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名。

@Configuration
@Import({Color.class,Red.class})
public class MainConfig2 {
	//...
}

2)ImportSelector:返回需要导入的组件的全类名数组;

//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {

	//返回值,就是到导入到容器中的组件全类名
	//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// TODO Auto-generated method stub
		//importingClassMetadata
		//方法不要返回null值
		return new String[]{"com.web.bean.Blue","com.web.bean.Yellow"};
	}
}

此时配置类如下:

@Configuration
@Import({Color.class,Red.class,MyImportSelector.class})
//@Import导入组件,id默认是组件的全类名
public class MainConfig2 {
	//...
}

3)ImportBeanDefinitionRegistrar:手动注册bean到容器中。

ImportBeanDefinitionRegistrar接口类如下:

public interface ImportBeanDefinitionRegistrar {

	/**
	 * Register bean definitions as necessary based on the given annotation metadata of
	 * the importing {@code @Configuration} class.
	 * 

Note that {@link BeanDefinitionRegistryPostProcessor} types may not be * registered here, due to lifecycle constraints related to {@code @Configuration} * class processing. * @param importingClassMetadata annotation metadata of the importing class * @param registry current bean definition registry */ public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry); }


实现类如下:


public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * AnnotationMetadata:当前类的注解信息
	 * BeanDefinitionRegistry:BeanDefinition注册类;
	 * 		把所有需要添加到容器中的bean;调用
	 * 		BeanDefinitionRegistry.registerBeanDefinition手工注册进来
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		
		boolean definition = registry.containsBeanDefinition("com.web.bean.Red");
		boolean definition2 = registry.containsBeanDefinition("com.web.bean.Blue");
		if(definition && definition2){
			//指定Bean定义信息;(Bean的类型,Bean。。。)
			RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
			//注册一个Bean,指定bean名
			registry.registerBeanDefinition("rainBow", beanDefinition);
		}
	}

}

此时配置类如下:

@Import({Color.class,Red.class,MyImportSelector.class,
MyImportBeanDefinitionRegistrar.class})
//@Import导入组件,id默认是组件的全类名
public class MainConfig2 {
	//...
}

【6】FactoryBean

FactoryBean : 是一个Java Bean,但是它是一个能生产对象的工厂Bean,它的实现和工厂模式及修饰器模式很像。

其接口如下:

/**
 * Interface to be implemented by objects used within a {@link BeanFactory} which
 * are themselves factories for individual objects. If a bean implements this
 * interface, it is used as a factory for an object to expose, not directly as a
 * bean instance that will be exposed itself.
 *
 * 

NB: A bean that implements this interface cannot be used as a normal bean. * A FactoryBean is defined in a bean style, but the object exposed for bean * references ({@link #getObject()}) is always the object that it creates. * *

FactoryBeans can support singletons and prototypes, and can either create * objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean} * interface allows for exposing more fine-grained behavioral metadata. * *

This interface is heavily used within the framework itself, for example for * the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the * {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for * custom components as well; however, this is only common for infrastructure code. * *

{@code FactoryBean} is a programmatic contract. Implementations are not * supposed to rely on annotation-driven injection or other reflective facilities. * {@link #getObjectType()} {@link #getObject()} invocations may arrive early in * the bootstrap process, even ahead of any post-processor setup. If you need access * other beans, implement {@link BeanFactoryAware} and obtain them programmatically. * *

Finally, FactoryBean objects participate in the containing BeanFactory's * synchronization of bean creation. There is usually no need for internal * synchronization other than for purposes of lazy initialization within the * FactoryBean itself (or the like). * * @author Rod Johnson * @author Juergen Hoeller * @since 08.03.2003 * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.aop.framework.ProxyFactoryBean * @see org.springframework.jndi.JndiObjectFactoryBean */ public interface FactoryBean { /** * Return an instance (possibly shared or independent) of the object * managed by this factory. *

As with a {@link BeanFactory}, this allows support for both the * Singleton and Prototype design pattern. *

If this FactoryBean is not fully initialized yet at the time of * the call (for example because it is involved in a circular reference), * throw a corresponding {@link FactoryBeanNotInitializedException}. *

As of Spring 2.0, FactoryBeans are allowed to return {@code null} * objects. The factory will consider this as normal value to be used; it * will not throw a FactoryBeanNotInitializedException in this case anymore. * FactoryBean implementations are encouraged to throw * FactoryBeanNotInitializedException themselves now, as appropriate. * @return an instance of the bean (can be {@code null}) * @throws Exception in case of creation errors * @see FactoryBeanNotInitializedException */ T getObject() throws Exception; /** * Return the type of object that this FactoryBean creates, * or {@code null} if not known in advance. *

This allows one to check for specific types of beans without * instantiating objects, for example on autowiring. *

In the case of implementations that are creating a singleton object, * this method should try to avoid singleton creation as far as possible; * it should rather estimate the type in advance. * For prototypes, returning a meaningful type here is advisable too. *

This method can be called before this FactoryBean has * been fully initialized. It must not rely on state created during * initialization; of course, it can still use such state if available. *

NOTE: Autowiring will simply ignore FactoryBeans that return * {@code null} here. Therefore it is highly recommended to implement * this method properly, using the current state of the FactoryBean. * @return the type of object that this FactoryBean creates, * or {@code null} if not known at the time of the call * @see ListableBeanFactory#getBeansOfType */ Class getObjectType(); /** * Is the object managed by this factory a singleton? That is, * will {@link #getObject()} always return the same object * (a reference that can be cached)? *

NOTE: If a FactoryBean indicates to hold a singleton object, * the object returned from {@code getObject()} might get cached * by the owning BeanFactory. Hence, do not return {@code true} * unless the FactoryBean always exposes the same reference. *

The singleton status of the FactoryBean itself will generally * be provided by the owning BeanFactory; usually, it has to be * defined as singleton there. *

NOTE: This method returning {@code false} does not * necessarily indicate that returned objects are independent instances. * An implementation of the extended {@link SmartFactoryBean} interface * may explicitly indicate independent instances through its * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean} * implementations which do not implement this extended interface are * simply assumed to always return independent instances if the * {@code isSingleton()} implementation returns {@code false}. * @return whether the exposed object is a singleton * @see #getObject() * @see SmartFactoryBean#isPrototype() */ boolean isSingleton(); }


自定义工厂bean实现该接口:

  • 这里为该类添加了@Component注解。
@Component
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean {

	//返回一个Color对象,这个对象会添加到容器中
	@Override
	public Color getObject() throws Exception {
		// TODO Auto-generated method stub
		System.out.println("ColorFactoryBean...getObject...");
		return new Color();
	}

	@Override
	public Class getObjectType() {
		// TODO Auto-generated method stub
		return Color.class;
	}

	//是单例?
	//true:这个bean是单实例,在容器中保存一份
	//false:多实例,每次获取都会创建一个新的bean;
	@Override
	public boolean isSingleton() {
		// TODO Auto-generated method stub
		//return true;
		return false;
		
	}

}

测试如下:

public class IOCTest {
	AnnotationConfigApplicationContext applicationContext =
	new AnnotationConfigApplicationContext(MainConfig2.class);
	
	
	@Test
	public void testImport(){
		//工厂Bean获取的是调用getObject创建的对象
		Object bean2 = applicationContext.getBean("colorFactoryBean");
		Object bean3 = applicationContext.getBean("colorFactoryBean");
		System.out.println("bean的类型:"+bean2.getClass());
		System.out.println(bean2 == bean3);
		
	}

result as follows :

ColorFactoryBean...getObject...
ColorFactoryBean...getObject...
bean的类型:class com.web.bean.Color
false

可以看到根据id colorFactoryBean获取的实际bean为com.web.bean.Color!并且两次获取的bean不等。


如果要获取工厂bean本身,则如下:

Object bean4 = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean4.getClass());

result as follows :

class com.web.bean.ColorFactoryBean

原理如下图:

Spring - 注解驱动开发一组件添加_第4张图片

那么Servlet、Filter和Listener如何使用代码方式注入容器呢?

参考博文:

ServletContainerInitializer注册JAVA组件;

Servlet3.0 - Servlet、Filter和Listener(注解注册)

你可能感兴趣的:(Spring注解,Servlet3.0,Spring)