Spring IOC_容器功能
番外篇
农夫需要把菜运输到大象国去卖
于是自己创建了一个工厂专门生成运输工具(工厂模式)
后来农夫觉得自己开工厂太费劲了,于是去寻找有直接生产运输工具的工厂。终于找到了一个叫做IoC的工厂
农夫从IoC工厂那里直接购买了一个运输工具(getBean),由于农夫之前也没见过飞机和轮船,不知道这个究竟是个什么运输工具。但是由于有运输工具的使用说明书(接口方法),农夫成功的使用运输工具把菜运输到了大象国。
接下来我们来去探讨采访下IoC工厂的流水线是怎么加工生产产品的。
RES员工
(Resource)负责接收生成运输工具需求,包括运输工具的创建参数(xml配置)和图纸(Class),以及依赖的其他零部。
BDR员工
(BeanDefinitionReader)负责分析需求,并且把需求整理为工厂内部规范的格式BeanDefinition。
BF员工
(BeanFactory)拿到BDR员工
提供的BeanDefinition开始制造产品,三号员工同时负责外部交易,通过向客户提供getBean方法让客户来这里拿产品。
ClassPathResource res = new ClassPathResource("spring.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res);
后来厂长觉得前面三号员工这样相互配合环节还是太多了,于是招了一个全栈工程师ApplicationContext,同时能够做前面三号员工的工作。
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); applicationContext.getBean(TestLifeCycle.class);
这个全栈工程师是一个勤奋的员工,学习了很多技能:
- 从ApplicationEventPublisher那里学会了事件监听机制,可以让工厂里面的产品之间发送接收消息;
- 从MessageSource学会了外语交流能力;
- 从InitializingBean那里学会了售后服务,在卖出产品后,接收用户的需求调整;
- 从BeanPostProcessor那里学会了售前售后。
Spring IoC容器功能:
基于版本4.0.0.RELEASE
在Spring中,控制反转IoC(Inversion of Control):由Spring IoC 容器来负责对象生命周期和对象之间的关系。
为什么需要控制反转
DI(依赖注入)其实就是IOC的另外一种说法。控制的什么被反转了?就是:获得依赖对象的方式反转了。
最好以依赖项注入(DI)的方式编写大多数应用程序代码,这种代码是从Spring IoC容器中提供的,具有在创建时由容器提供的自己的依赖项,并且完全不需要了解容器本身。
把依赖项的构造与使用分开,从而达到构造与使用的解耦,可以更好的对依赖项进行配置,类似Spring的配置文件中进行简单配置即可替换掉依赖注入项的实现。否则,你只能修改源代码。
IoC容器基本使用方法
元数据配置实现DI
1、基于XML配置
基本格式如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <bean id="..." class="..."> <!-- collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions go here --> </beans>
2、基于注解配置
注解注入在XML注入之前执行,因此对于通过两种方法注入属性,XML配置将覆盖注解配置。
实例化容器
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
使用容器
通过调用T getBean(String name, Class<T> requiredType)方法,你可以检索到bean的实例。
// retrieve configured instance PetStoreService service = context.getBean("petStore", PetStoreService.class); // use configured instance List<String> userList = service.getUsernameList();
bean配置概览
一个Bean
配置会解析成BeanDefinition
对象,该对象包含以下内容:
- 包限定的类名:通常是所定义的Bean的实际实现类;
- Bean行为配置元素,用于声明Bean在容器中的行为(作用域,生命周期回调等);
- 引用其他bean完成其工作所需的bean;这些引用也称为协作者或依赖项;
- 要在新创建的对象中设置的其他配置设置,例如,用于管理连接池的Bean中使用的连接数,或该池的大小限制。
IOC容器具体配置相关用法
基于构造函数的DI
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
<beans> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> </beans>
构造参数类型匹配:
package examples; public class ExampleBean { // No. of years to the calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
基于setter的DI
通过<property/>指定setter的参数
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested <ref/> element --> <property name="beanOne"><ref bean="anotherExampleBean"/></property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
基于静态工厂的DI
静态工厂的参数通过<constructor-arg/>标签指定
public class ExampleBean { // a private constructor private ExampleBean(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
更多标签用法
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
可以使用p-namespace命名空间简化
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean ... p:driverClassName="com.mysql.jdbc.Driver" ... </beans>
inner beans
直接定义需要注入的Bean,而不是通过ref标签引用:
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
集合注入
<list/>,<set/>,<map/>,和<props/>元素分别对应Java集合的List、Set、Map以及Properties,使用方法:
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="myDataSource"/> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
autowire自动装配模式
您可以允许Spring通过检查ApplicationContext的内容来为您的bean自动解析协作者(其他bean)。
自动装配模式(<bean/>标签有个autowire属性指定)
- no:不使用自动装配,在很多企业不鼓励使用自动装配,因为它对应Bean之间的参考依赖关系不清晰
- byName:通过Bean的属性名字进行自动装配,在配置文档中查找一个与将要装配的属性同样名字的Bean。
- byType:如果XML中正好有一个与属性类型一样的Bean,就自动装配这个属性。如果存在多个这样的Bean,就抛出一个异常。
- constructor:根据构造函数的参数来自动装配
- autodetect:通过对Bean检查类的内部来选择constructor或者byType,优先constructor。
方法注入
假如A是单例的Bean,依赖于非单例的B,需要每次执行某个方法,都需要创建一个B的新的实例,如何配置呢?
这个时候可以使用方法注入:
package fiona.apple; // no more Spring imports! public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriate Command interface Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
<!-- a stateful bean deployed as a prototype (non-singleton) --> <bean id="command" class="fiona.apple.AsyncCommand" scope="prototype"> <!-- inject dependencies here as required --> </bean> <!-- commandProcessor uses statefulCommandHelper --> <bean id="commandManager" class="fiona.apple.CommandManager"> <lookup-method name="createCommand" bean="command"/> </bean>
bean作用域范围
1、prototype
要使Spring容器释放由prototype作用域的bean占用的资源,请尝试使用自定义bean后处理器,该处理器包含对需要清理的bean的引用。
2、Singleton的beans引用prototype beans
如果在运行时每次都需要原型bean的新实例,请使用“方法注入”。
3、request, session, global session作用域
这几种作用域只能用于对web环境的ApplicationContext,如XmlWebApplicationContext。
如果你使用Servlet 2.4+的web容器,但不是使用Spring Web MVC(用到DispatcherServlet和DispatcherPortlet),那么,为了支持这几种作用域,您需要做一下初始化配置:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
如果你使用的是Servlet 2.3,你需要servlet Filter实现:
<web-app> ... <filter> <filter-name>requestContextFilter</filter-name> <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>requestContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... </web-app>
4、使用scope作用于的bean作用依赖
在request,session,globalSession和custom-scope级别的作用域需要使用<aop:scoped-proxy />元素。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <aop:scoped-proxy/> </bean> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
自定义bean的行为
为了与容器对bean生命周期的管理过程进行交互,您可以实现Spring InitializingBean和DisposableBean接口。 容器调用前者的afterPropertiesSet() 并调用后者的destroy(),以允许Bean在初始化和销毁Bean时执行某些自定义的操作。
生命周期回调
初始化回调
org.springframework.beans.factory.InitializingBean 接口提供了在bean必备的属性都设置完成之后,做一些其他初始化工作的回调方法:
void afterPropertiesSet() throws Exception;
不建议使用InitializingBean,这使得代码与Spring耦合。可以尝试使用 @PostConstruct 注解,或者提供一个具有init-method方法的POJO。
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
销毁回调
org.springframework.beans.factory.DisposableBean 接口提供了在bean销毁的时候做一些其他工作的方法:
void destroy() throws Exception;
不建议使用 DisposableBean,这使得代码与Spring耦合。可以尝试使用 @PreDestroy 注解,或者提供一个具有destroy-method方法的bean:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
默认的初始化和销毁方法
您可以在beans标签中指定默认的初始化和销毁方法,所有bean都使用该名称的方法作为初始化和销毁方法。
<beans default-init-method="init"> <bean id="blogService" class="com.foo.DefaultBlogService"> <property name="blogDao" ref="blogDao" /> </bean> </beans>
ApplicationContextAware和BeanNameAware
通过实现ApplicationContextAware方法,可以获取到ApplicationContext对象的引用。
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
获取到ApplicationContext之后,您可以转换为已知的子类型如ConfigurableApplicationContext,以操作更多的方法。
最常用的是通过ApplicationContext检索bean,也可以方位Resources,发布application events或者访问MessageSource。但是应当尽量避免直接使用ApplicationContext,因为这导致代码和Spring耦合,不遵循IoC原则。
您也可以通过自动装配 @Autowired 来获取ApplicationContext。
实现org.springframework.beans.factory.BeanNameAware可以获取到定义的bean的名称:
public interface BeanNameAware { void setBeanName(string name) throws BeansException; }
该回调方法在bean的属性装配完成之后,初始化回调执行之前执行。
其他aware接口
注意:使用这些接口将会导致你的接口与Spring API绑定在一起,不遵循IoC原则。所以,应该用于底层的需要编程式访问容器的Beans。
bean定义继承
<bean id="inheritedTestBean" abstract="true" class="org.springframework.beans.TestBean"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean" parent="inheritedTestBean" init-method="initialize"> <property name="name" value="override"/> <!-- the age property value of 1 will be inherited from parent --> </bean>
容器扩展点
一般我们不会继承ApplicationContext去扩展,而是使用Spring IoC容器的插件式的方式去实现特定的集成接口。
使用BeanPostProcessor自定义bean
BeanPostProcessor接口提供了一个可自定义实现的回调。如果写了多个实现类,可以通过Ordered接口指定执行顺序。该回调在容器的初始化方法之前执行(如IntializingBean的afterPropertiesSet或者任何定义的初始化方法)。
Spring的一些底层AOP类也是通过post-processor织入代理逻辑。
ApplicationContext自动检测识别配置元数据中实现了BeanPostProcessor接口的bean,作为post-processor。当然,也可以通过使用ConfigurableBeanFactory的addBeanPostProcessor方法编程式的去配置。通过编程式不支持Ordered接口,而是根据注册顺序来执行的,并且编程式注册的post-processor在通过元数据配置的之前执行。
- BeanPostProcessors以及被其引用的bean与普通的bean不一样,在ApplicationContext容器启动阶段的时候就初始化了;
- 所有的BeanPostProcessors按顺序注册到容器中,然后会应用到容器中后面所有加载的bean;
- 因为AOP代理是通过BeanPostProcessor实现了,所以BeanPostProcessor以及被其引用的bean中不会自动织入代理。
下面这个里面在bean创建的时候,调用了其toString()方法:
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; // we could potentially return any object reference here... } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean " + beanName + " created : " + bean.toString()); return bean; } }
<?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:lang="http://www.springframework.org/schema/lang" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd"> <lang:groovy id="messenger" script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/> </lang:groovy> <!-- when the above bean (messenger) is instantiated, this custom BeanPostProcessor implementation will output the fact to the system console --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans>
Spring的依赖注入注解就是通过RequiredAnnotationBeanPostProcessor这个BeanPostProcessor实现的。
使用BeanFactoryPostProcessor自定义配置元数据
org.springframework.beans.factory.config.BeanFactoryPostProcessor接口与BeanPostProcessor接口类似,不同点在于:BeanFactoryPostProcessor在Spring IoC容器读取配置元数据时对配置元数据进行自定义的处理。
注意:你的BeanFactoryPostProcessor必须实现Ordered接口才可以对属性进行修改。
Spring内置了一些bean factory post-processor,如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。
PropertyPlaceholderConfigurer允许从标准的Java Properties格式的文件读取bean的属性值。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/foo/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
以下是一个比较有用的例子:通过使用PropertyPlaceholderConfigurer,在运行时获取具体的实现类:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.foo.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
通过FactoryBean自定义实例化逻辑
通过实现org.springframework.beans.factory.FactoryBean接口可以让bean变成工厂。这对于一些不能够通过xml来配置的实例化逻辑很有帮助,主要方法:
- Object getObject(): 返回bean工厂的实例;
- boolean isSingleton(): 返回bean是否为单例;
- getObjectType(): 返回实例的类型。
可以通过getBean("&myBean")获取FactoryBean,使用getBean("myBean")获取FactoryBean的实例。
基于注解的容器配置
这是一种通过字节码元数据来配置容器的方法。
基于注解的注入会在基于XML的注入之前执行,所以可以通过XML配置覆盖注解配置。
通过下面的注解,可以隐性地注入这些注解的BeanPostProcessor:
<context:annotation-config/>
这个配置会自动注册一下的post-processor: AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, 还有前面提到的 RequiredAnnotationBeanPostProcessor。
@Required
这个注解应用于Bean属性的setter方法,来注入依赖:
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
@Autowired
可以通过@Autowired注解给setter方法注入依赖:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
也可以应用于任意名称(非setXxx)多个参数的方法,或者构造函数:
public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }
也支持数组注入:
public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ... }
集合或者Map类型的注入。Map类型的key为bean的名称:
public class MovieRecommender { private Map<String, MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
@Autowired注解也可以用于注入: BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, 和 MessageSource类型的bean,这些接口和他们的子类,无需做特殊的配置,会自动的通过类型查找进行装配。
public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... }
@Autowired, @Inject, @Resource以及@Value注解是通过BeanPostProcessor实现的,所以你不可以在你自己的BeanPostProcessor或者BeanFactoryPostProcessor中使用这些注解,而应该通过XML或者@Bean注解来配置。
使用@Qualifier指定需要注入的bean
当使用类型注入的时候,如果一个类型有多个bean,那么就需要使用@Qualifier注解了,该注解用于找到实际需要注入的bean。
可以在属性上面使用,也可以在构造函数或方法的参数里面指定:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main")MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
对应配置如下:
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier value="main"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
- 如果你趋向于使用bean名称来配置基于注解驱动的注入,请尽量不要使用@Autowired,虽然可通过@Qualifier来指定bean的名称。你应该尝试使用@Resource,该注解只会通过bean的名称而不是类型来寻找依赖;
- @Autowired应用于属性、构造函数以及多参函数,配合@Qualifier一起使用可以定位bean;而@Resource只支持属性、单参数的属性setter方法。所以如果你的配置目标是构造函数或者多参方法,请使用@Autowired和@Qualifier。
自定义Qualifier注解:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); }
public class MovieRecommender { @Autowired @Genre("Action") private MovieCatalog actionCatalog; private MovieCatalog comedyCatalog; @Autowired public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) { this.comedyCatalog = comedyCatalog; } // ... }
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="Genre" value="Action"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> _<qualifier type="example.Genre" value="Comedy"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
另外,你也可以不提供qualifier的value值,当成一个更加通用的bean来处理。或者添加更多的用于定位bean的字段:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public MovieQualifier { String genre(); Format format(); }
public enum Format { VHS, DVD, BLURAY }
public class MovieRecommender { @Autowired @MovieQualifier(format=Format.VHS, genre="Action") private MovieCatalog actionVhsCatalog; @Autowired @MovieQualifier(format=Format.VHS, genre="Comedy") private MovieCatalog comedyVhsCatalog; @Autowired @MovieQualifier(format=Format.DVD, genre="Action") private MovieCatalog actionDvdCatalog; @Autowired @MovieQualifier(format=Format.BLURAY, genre="Comedy") private MovieCatalog comedyBluRayCatalog; // ... }
使用泛型作为自动装配限定
如果bean是一个泛型类型,在注入的时候,会寻找到对应类型的bean:
// Inject all Store beans as long as they have an <Integer> generic // Store<String> beans will not appear in this list @Autowired private List<Store<Integer>> s;
CustomAutowireConfigurer
CustomAutowireConfigurer是一个BeanFactoryPostProcessor,可以通过它注册你自己的限定注解类型,即使该注解不包含@Qualifier注解:
<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomQualifier</value> </set> </property> </bean>
@Resource
@Resource包含一个name属性,该属性默认为bean名称。如果name属性未指定,那么默认会取属性的名称或者setter方法中的bean属性名称。
与@Autowired类似,在没有指定显式name的时候,@Resource通过类型匹配而不是特定名称的bean获取到这些可解决的依赖项:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher, 和MessageSource接口。
public class MovieRecommender { // 先查找customerPreferenceDao名称的bean,找不到,则查找CustomerPreferenceDao类型的bean @Resource private CustomerPreferenceDao customerPreferenceDao; // 基于ApplicationContext类型进行查找 @Resource private ApplicationContext context; public MovieRecommender() { } // ... }
@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor除了处理@Resource注解,也处理JSR-250生命周期注解。
假设CommonAnnotationBeanPostProcessor已在Spring ApplicationContext中注册,则在生命周期中与相应的Spring生命周期接口方法或者显式声明的回调方法会被调用:
public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // populates the movie cache upon initialization... } @PreDestroy public void clearMovieCache() { // clears the movie cache upon destruction... } }
类路径扫描与托管组件
通过扫描类路径可以发现bean组件。这节主要是提供一种不用XML文件配置bean的方法。主要通过注解、AspectJ 类型表达式,或者自定义的拦截规则,来判断哪些类被定义为Bean组件并且需要注入到容器中。
从Spring 3.0依赖,Spring JavaConfig项目作为Spring框架核心部分,提供了很多新的特性,这写特性允许你通过Java定义bean,而不是XML。可以看看相关的注解:@Configuration,@Bean,@Import以及@DependsOn。
@Component
@Component是Spring的一个泛化的组件管理注解,可以作用在任何层次的类中。同时针对不同的场景,延伸出了以下的注解:
- @Repository:用于将数据访问层的类标识为Spring Bean,该注解能将锁标注的类中抛出的数据访问异常封装为Spring的数据方位异常类型。Spring本身提供了一个丰富并且与持久层框架技术无关的数据访问异常结构,用于封装持久层框架抛出的异常,使得异常独立于底层框架;
- @Service:作用在业务层。目前该注解与@Component相同;
- @Controller:作用在控制层,目前该注解与@Componet相同。
通过在类上使用这些注解,Spring会自动创建相应的BeanDefinition对象,并且注册到ApplicationContext中,成为托管于Spring的组件。
元注解
元注解的意思就是用这个注解标注于其他注解类上面。Spring中内置的很多注解都可以作为元注解使用。
自动检测类以及注册Bean Definitions
为了能够自动检测并注册Bean Definitions,需要在Spring配置文件中添加注解:
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/> </beans>
- context:component-scan标签会自动引入context:annotation-config标签;同时会自动引入AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor。
- 扫描的包需要在classpath中。
使用过滤器自定义扫描规则
默认的情况下,自动扫描注册的组件包括通过使用 @Component, @Repository, @Service, @Controller以及自定义注解(使用到了@Component元注解)这些注解的类,但是,你可以通过include-filter或者exclude-filter来自定义过滤规则:
<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>
通过组件定义bean元数据
在组件扫描的过程中,bean的名称通过BeanNameGenerator识别。默认的,任何Spring的stereotype注解( @Component, @Repository, @Service, 和 @Controller)都会提供一个name属性用于设置bean的名称。缺省BeannameGenerator会返回首字母小写的非限定类目。
你可以通过实现BeanNameGenerator接口自定义一个bean-naming策略,然后配置到扫描器中:
<beans> <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" /> </beans>
为自动检测组件提供作用域
默认情况下,Spring托管的组件会使用singleton作用域。如果你需要自己制定作用域,可以使用@Scope注解:
@Scope("prototype") @Repository public class MovieFinderImpl implements MovieFinder { // ... }
如果要提供自定义的作用域解析策略,请实现ScopeMetadataResolver。然后配置到扫描器中:
<beans> <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" /> </beans>
使用非singleton作用域时,可能有必要为作用域对象生成代理。 为此,在component-scan元素上可以使用scoped-proxy属性。 三个可能的值是:no,interfaces和targetClass。
通过注解提供qualifier元数据
@Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
@Component @Genre("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
使用JSR 330标准注解
从Spring 2.0开始,支持JSR-330标准依赖注解。为了这用这些注解,引入javax.inject依赖即可:
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
1、@Inject和@Named依赖注入
import javax.inject.Inject; import javax.inject.Named; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(@Named("main") MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
2、@Named和@Component等效
import javax.inject.Inject; import javax.inject.Named; @Named("movieListener") public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
同时,需要配置扫描标签:
<beans> <context:component-scan base-package="org.example"/> </beans>
标准注解的局限性
基于JAVA的容器配置
基本注解:@Bean和@Configuration
Spring基于Java的配置新特性主要是通过@Bean和@Configuration注解来实现的。
@Bean注解用于指定一个方法来实例化,配置以及初始化一个新的Spring IoC容器托管对象。这与标签类似。你可以配合任何Spring @Component注解一起使用@Bean,最常用的是与@Configuration注解的bean配合使用。
用@Configuration注释类表示该类的主要目的是作为Bean定义的来源。此外,@ Configuration类允许通过简单地调用同一类中的其他@Bean方法来定义Bean之间的依赖关系。
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
等同于:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
使用AnnotationConfigApplicationContext初始化Spring容器
在传统的XML配置方式中,可以使用ClassPathXmlApplicationContext类来加载外部XML上下文文件。而使用基于Java的配置时,需要用到Spring 3.0中的新增功能-Spring的AnnotationConfigApplicationContext类。这种通用的ApplicationContext实现不仅能够注册@Configuration类,而且还可以注册普通的@Component类和带有JSR-330元数据注释的类。
当提供@Configuration类作为输入时,@ Configuration类本身将注册为Bean definition,并且该类中所有已声明的@Bean方法也将注册为Bean定义。
当提供@Component和JSR-330的类作为输入时,它们将注册为bean definition,这些类中可以使用诸如@Autowired或@Inject之类的作为DI元数据配置。
例子:
实例化y一个ClassPathXmlApplicationContext的时候,需要提供Spring XML文件,而实例化一个AnnotationConfigApplicationContext的时候,需要提供一个@Configuration注解的配置类(或者任何@Component或者JSR-330注解类),从而完全不用XML来配置容器:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
编程式地使用register(Class<?>…)构建容器:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
使用scan(String…)启用组件扫描
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
让AnnotationConfigWebApplicationContext支持web应用
使用子类:AnnotationConfigWebApplicationContext
<web-app> <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- Bootstrap the root application context as usual using ContextLoaderListener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Declare a Spring MVC DispatcherServlet as usual --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- map all requests for /app/* to the dispatcher servlet --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
使用@Bean注解
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
接收生命周期回调
在被@Bean定义的类中,你可以使用JSR-250的@PostConstruct和@PreDestroy注解接收生命周期回调;
你也可以通过实现InitializingBean、DisposableBean或者Lifecycle接口来接收生命周期回调;
Aware接口如BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware接口也支持回调。
另外,你也可以使用以下方式让bean支持回调,这有点类似XML配置中的init-method和destroy-method:
public class Foo { public void init() { // initialization logic } } public class Bar { public void cleanup() { // destruction logic } } @Configuration public class AppConfig { @Bean(initMethod = "init") public Foo foo() { return new Foo(); } @Bean(destroyMethod = "cleanup") public Bar bar() { return new Bar(); } }
上面i的初始化方法配置与下面的代码等效:
@Configuration public class AppConfig { @Bean public Foo foo() { Foo foo = new Foo(); foo.init(); return foo; } // ... }
指定Bean的作用域
1、使用@Scope注解
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
2、@Scope和scoped-proxy
类似于<aop:scoped-proxy/>注解,你可 以给 @Scope注解指定代理模式:
// an HTTP Session-scoped bean exposed as a proxy @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public UserPreferences userPreferences() { return new UserPreferences(); } @Bean public Service userService() { UserService service = new SimpleUserService(); // a reference to the proxied userPreferences bean service.setUserPreferences(userPreferences()); return service; }
3、自定义bean命名
@Configuration public class AppConfig { @Bean @Scope(name = "myFoo") public Foo foo() { return new Foo(); } }
4、bean别名
@Configuration public class AppConfig { @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } }
5、bean描述
当把bean导出来监控的时候(如通过JMX),这个描述信息显得很重要。
@Configuration public class AppConfig { @Bean @Desciption("Provides a basic example of a bean") public Foo foo() { return new Foo(); } }
使用@Configuration注解
这是类级别的注解,表明该类是一个bean definition。@Configuration类通过@Bean注解方法定义Bean。
1、内部bean注入
@Configuration public class AppConfig { @Bean public Foo foo() { return new Foo(bar()); } @Bean public Bar bar() { return new Bar(); } }
2、查找方法注入
如前面讲到的,针对在一个singleton的bean中依赖一个prototypebean这种场景,查找方法注入非常有用,代码可能这样写:
public abstract class CommandManager { public Object process(Object commandState) { // grab a new instance of the appropriate Command interface Command command = createCommand(); // set the state on the (hopefully brand new) Command instance command.setState(commandState); return command.execute(); } // okay... but where is the implementation of this method? protected abstract Command createCommand(); }
使用基于Java的配置,你可以创建一个CommandManager子类,让每次都返回一个新的 AsyncCommand对象:
@Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } @Bean public CommandManager commandManager() { // return new anonymous implementation of CommandManager with command() overridden // to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } }
组织基于Java的配置
1、使用@Import注解
类似于xml中的<import/>标签,@Import注解可以从另一个配置类中加载bean definition。
@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
2、注入通过import引入的bean
@Configuration public class ServiceConfig { @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public DataSource dataSource() { // return new DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes... TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
这种用法,你很难找到这些依赖是从哪个Configuration类引入的,为此,你可以直接通过Configuration类来引入:
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { @Bean public DataSource dataSource() { // return DataSource } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
3、有条件的引入@Configuration类或者@Beans
有时候我们想要某个@Configuration类或者@Beans不生效,如根据不同的环境启用同的@Configuration配置。@Profile正是这样的注解。
@Profile注解使用灵活的@Conditional实现,@Conditional注解作用是让@Bean在注册之前,判断是否符合特定的条件(org.springframework.context.annotation.Condition的实现)。
下面是一个Condition接口的实现类的部分代码,核心是这个matches方法,该实现类可以用于@Profile:
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { // Read the @Profile annotation attributes MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true; } } return false; } } return true; }
4、合并Java和XML配置
Spring的@Configuration配置类并不能完全的取代XML配置。诸如Spring XML名称空间之类的某些功能仍然是配置容器的理想方法,你可以选择:
- 基于XML如ClassPathXmlApplicationContext实例化容器;
- 基于Java的AnnotationConfigApplicationContext实例化容器,并通过@ImportResource注解导入需要的XML。
5、基于XML的@Configuration配置类
记住:@Configuration配置类实际是容器里面的一个Bean definition。下面是一个具体的基于XML的@Configuration配置类的例子:
@Configuration public class AppConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public TransferService transferService() { return new TransferService(accountRepository()); } }
<beans> <!-- enable processing of annotations such as @Autowired and @Configuration --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }
因为@Configuration带有元注解@Component,@Configuration注解的类也可以用于组件扫描:
<beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
<context:component-scan/>会引入<context:annotation-config/>标签的功能。
6、基于@Configuration配置类导入XML
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { return new DriverManagerDataSource(url, username, password); } }
<beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans>
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现