spring的依赖注入
Spring 的依赖注入容器的核心是 Beanfactory 接口。 Beanfactory 负责管理组件,包括依赖项以及它们的生命周 期
Beanfactory 示例
public interface Oracle { String defineMeaningOfLife () ;}
public class BookwormOracle implements Oracle {
@Override
public String defineMeaningOfLife() {
return "go see the world instead";
}
}
xml-bean-factory-config.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="oracle" name="wiseworm" class="com.anocation.spring5.ch3.BookwormOracle"/>
</beans>
public class XmlConfigWithBeanFactory {
public static void main(String[] args) {
DefaultListableBeanFactory factory= new DefaultListableBeanFactory();
XmlBeanDefinitionReader rdr =new XmlBeanDefinitionReader(factory);
rdr.loadBeanDefinitions(new ClassPathResource("xml-bean-factory-config.xml"));
Oracle oracle = factory.getBean("oracle", Oracle.class);
System.out.println(oracle.defineMeaningOfLife());
}
}
配置ApplicationContext
声明spring组件
app-context-xml.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"
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 id="provider" class="com.anocation.spring5.ch3.annotation.HelloWorldMessageProvider"/>
<bean id="renderer" class="com.anocation.spring5.ch3.annotation.StandardOutMessageRenderer"
p:messageProvider-ref="provider"/>
</beans>
public class DeclareSpringComponents {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-xml.xml");
ctx.refresh();
MessageRenderer messageRenderer = ctx.getBean("renderer", MessageRenderer.class);
messageRenderer.render();
ctx.close();
}
}
用注解创建 bean 定义--setter注入
@Component("provider")
public class HelloWorldMessageProvider implements MessageProvider {
@Override
public String getMessage() {
return "Hello World!";
}
}
使用@Autowired注入MessageProvider,可以在setter上添加,或使用@Resource
@Service("renderer")
public class StandardOutMessageRenderer implements MessageRenderer {
// @Autowired
private MessageProvider messageProvider;
@Override
public void render() {
if (messageProvider==null){
throw new RuntimeException("you must set the property messageProvider of class:"
+ StandardOutMessageRenderer.class.getName());
}
System.out.println(messageProvider.getMessage());
}
@Override
@Autowired
// @Resource(name = "provider")
public void setMessageProvider(MessageProvider provider) {
this.messageProvider = provider;
}
@Override
public MessageProvider getMessageProvider() {
return this.messageProvider;
}
}
app-context-annotation.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"
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="com.anocation.spring5.ch3.annotation"/>
</beans>
public class AnnotationDeclareSpringComponents {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-annotation.xml");
ctx.refresh();
MessageRenderer messageRenderer = ctx.getBean("renderer", MessageRenderer.class);
messageRenderer.render();
ctx.close();
}
}
使用GenericXmlApplicationContext而不是DefaultListableBeanFactory的一个实例被实例化。GenericXmlApplicationContext类实现了ApplicationContext接口, 并能够通过XML文件中定义的配置启动Sring的ApplicationContext
使用java配置
@Configuration
public class HelloWorldConfiguration {
@Bean
public MessageProvider provider(){
return new HelloWorldMessageProvider();
}
@Bean
public MessageRenderer renderer(){
MessageRenderer renderer = new StandardOutMessageRenderer();
renderer.setMessageProvider(provider());
return renderer;
}
}
public class HelloWorldSpringAnnotated {
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
MessageRenderer mr = ctx.getBean("renderer", MessageRenderer.class);
mr.render();
}
}
AnnotationConfigApplicationContext类实现了 ApplicationContext接口,并且能够根据 HelloWorldConfiguration类定义 的配置信息启动 Spring 的 ApplicationContext
使用注解扫描注解
@ComponentScan(basePackages = "com.anocation.spring5.ch3.annotation")
@Configuration
public class HelloWorldConfiguration {
}
Xml和 Java 配置混合使用
@ImportResource(locations = "classpath:app-context-annotation.xml")
@Configuration
public class HelloWorldConfiguration {
}
使用构造函数注入
/**
* 使用构造函数注入
*/
public class ConfigurableMessageProvider implements MessageProvider{
private String message;
@Autowired
public ConfigurableMessageProvider(@Value("默认值") String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
app-context-xml2.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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- <bean id="provider" class="com.anocation.spring5.ch3.annotation.ConfigurableMessageProvider">-->
<!-- <constructor-arg value="Hello World!!!"/>-->
<!-- </bean>-->
<bean id="provider" class="com.anocation.spring5.ch3.annotation.ConfigurableMessageProvider"
c:message="Hello World!!!"
/>
</beans>
@ComponentScan(basePackages = "com.anocation.spring5.ch3.annotation")
//@ImportResource(locations = "classpath:app-context-annotation.xml")
@ImportResource(locations = "classpath:app-context-xml2.xml")
@Configuration
public class HelloWorldConfiguration {
}
public class HelloWorldSpringAnnotated {
public static void main(String[] args){
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
MessageRenderer mr = ctx.getBean("renderer", MessageRenderer.class);
mr.render();
}
}
消息外部化
<?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"
xmlns:c="http://www.springframework.org/schema/c"
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="com.anocation.spring5.ch3.annotation"/>
<!-- 一0表示构造函数参数的索引-->
<bean id="message" class="java.lang.String"
c:_0="Hello World!!!!"/>
</beans>
@Component("provider")
public class ConfigurableMessageProvider implements MessageProvider{
private String message;
// @Autowired
// public ConfigurableMessageProvider(@Value("默认值") String message) {
// this.message = message;
// }
@Autowired
public ConfigurableMessageProvider(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
构造者的困惑
/**
* 构造者的困惑
* 使用了两个具有相同参数数量的 构造函数的参数名称相同
*/
public class ConstructorConfusion {
private String someValue;
public ConstructorConfusion(String someValue) {
System.out.println("ConstructorConfusion(String) called");
this.someValue = someValue;
}
public ConstructorConfusion(int someValue) {
System.out.println("ConstructorConfusion(int) called");
this.someValue = "Number:"+someValue;
}
@Override
public String toString() {
return someValue;
}
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:constructorConfusion-xml.xml");
ctx.refresh();
ConstructorConfusion cc = (ConstructorConfusion)ctx.getBean("constructorConfusion");
System.out.println(cc);
ctx.close();
}
}
constructorConfusion-xml.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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="constructorConfusion" class="com.anocation.spring5.ch3.ConstructorConfusion">
<constructor-arg>
<value>100</value>
</constructor-arg>
</bean>
</beans>
此时会打印100
想要使用int构造,可以添加类型
<constructor-arg type="int">
<value>100</value>
</constructor-arg>
使用注解方式
@Service("constructorConfusion")
public class ConstructorConfusion {
private String someValue;
public ConstructorConfusion(String someValue) {
System.out.println("ConstructorConfusion(String) called");
this.someValue = someValue;
}
@Autowired
public ConstructorConfusion(@Value("1000") int someValue) {
System.out.println("ConstructorConfusion(int) called");
this.someValue = "Number:"+someValue;
}
@Override
public String toString() {
return someValue;
}
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-annotation.xml");
ctx.refresh();
ConstructorConfusion cc = (ConstructorConfusion)ctx.getBean("constructorConfusion");
System.out.println(cc);
ctx.close();
}
}
app-context-annotation.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
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="com.anocation.spring5.ch3.valueAnno"/>
<bean id="constructorConfusion" class="com.anocation.spring5.ch3.valueAnno.ConstructorConfusion">
<constructor-arg type="int">
<value>123456</value>
</constructor-arg>
</bean>
</beans>
使用字段注入
@Component
public class Inspiration {
private String lyric = "I can keep the door cracked open , to let light through";
public Inspiration(@Value("For all my running, I can understand")String lyric) {
this.lyric = lyric;
}
public String getLyric() {
return lyric;
}
public void setLyric(String lyric) {
this.lyric = lyric;
}
}
@Service("singer")
public class Singer {
@Autowired
private Inspiration inspiration;
public void sing(){
System.out.println("..."+inspiration.getLyric());
}
}
public class Fieldinjection {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-annotation.xml");
ctx.refresh();
Singer singer = ctx.getBean(Singer.class);
singer.sing();
ctx.close();
}
}
<context:component-scan base-package="com.anocation.spring5.ch3.annotated"/>
- 虽然使用字段注入可以很容易地添加依赖项,但必须格外小心,不要违反单一责任原则。 拥有更多的依赖 项意味着担负更多的责任, 这可能会在重构时出现难以分离的问题。当使用构造函数或setter 设置依赖项时, 可以很容易看出类变得脏肿,但是当使用字段注入时却很难看出来。
- 虽然注入依赖项的责任在 Spring 中由容器承担,但类应该通过方法或构造函数清楚地传达使用公共接口所 需的依赖项类型。 而如果使用字段注入, 则可能很难搞清楚什么类型的依赖项是真正需要的,以及依赖项 是不是强制性的。
- 字段注入引入了 Spring 容器的依赖、项,因为@Autowired 注解是 Spring 组件; 因此, 这个 bean 不再是一个 POJO, 并且不能独立实例化。
- 字段注入不能用于 final 字段。这种类型的字段只能使用构造函数注入来初始化。
- 由于必须手动注入依赖项,因此在编写测试时, 字段注入会带来困难。
使用注入参数
-
注入简单值
public class InjectSimple { private String name; private int age; private float height; private boolean programmer; private Long ageInSeconds; public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:app-config.xml"); ctx.refresh(); InjectSimple simple = ctx.getBean(InjectSimple.class); System.out.println(simple); ctx.close(); } //省略 //setter //getter //toString }
<?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: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 id="injectSimple" class="com.anocation.spring5.ch3.xml.InjectSimple" p:name="fly" p:age="11" p:height="1.21" p:programmer="false" p:ageInSeconds="121212121211212" /> </beans>
注解方式
@Service("injectSimple") public class InjectSimple { @Value("fly") private String name; @Value("12") private int age; @Value("1.54") private float height; @Value("false") private boolean programmer; @Value("1213131231231") private Long ageInSeconds; public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); // ctx.load("classpath:app-config.xml"); ctx.load("classpath:app-context-annotation.xml"); ctx.refresh(); InjectSimple simple = ctx.getBean(InjectSimple.class); System.out.println(simple); ctx.close(); } //省略 //setter //getter //toString }
<context:component-scan base-package="com.anocation.spring5.ch3.xml"/>
通过使用 SpEL 注入值
SpEL 能够动态评估表达式
public class InjectSimpleConfig { private String name = "fly"; private int age = 12; private float height = 1.43f; private boolean programmer = false; private Long ageInSeconds = 121212121L; public String getName() { return name; } public int getAge() { return age; } public float getHeight() { return height; } public boolean isProgrammer() { return programmer; } public Long getAgeInSeconds() { return ageInSeconds; } }
InjectSimpleConfig.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" 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 id="injectSimpleConfig" class="com.anocation.spring5.ch3.xml.InjectSimpleConfig"/> <bean id="injectSimpleSpel" class="com.anocation.spring5.ch3.xml.InjectSimpleSpel" p:name="#{injectSimpleConfig.name+1}" p:age="#{injectSimpleConfig.age+1}" p:height="#{injectSimpleConfig.height}" p:programmer="#{injectSimpleConfig.programmer}" p:ageInSeconds="#{injectSimpleConfig.ageInSeconds}" /> </beans>
/** * 测试配置 * {@link InjectSimpleConfig} */ public class InjectSimpleSpel { private String name; private int age; private float height; private boolean programmer; private Long ageInSeconds; public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:InjectSimpleConfig.xml"); ctx.refresh(); InjectSimpleSpel simple = ctx.getBean(InjectSimpleSpel.class); System.out.println(simple); ctx.close(); } //省略 //getter //setter //toString }
当使用注解式的值注入时,只需要用 SpEL 表达式替换值注解即可
@Component("injectSimpleConfig") public class InjectSimpleConfig { //省略 }
@Service("injectSimpleSpel") public class InjectSimpleSpel { @Value("#{injectSimpleConfig.name}") private String name; @Value("#{injectSimpleConfig.age}") private int age; @Value("#{injectSimpleConfig.height}") private float height; @Value("#{injectSimpleConfig.programmer}") private boolean programmer; @Value("#{injectSimpleConfig.ageInSeconds}") private Long ageInSeconds; //省略 }
<context:component-scan base-package="com.anocation.spring5.ch3.xml"/>
在相同的xml中注入bean--使用<ref>
public class BookwormOracle implements Oracle { @Override public String defineMeaningOfLife() { return "go see the world instead"; } }
public class InjectRef { private Oracle oracle; public void setOracle(Oracle oracle) { this.oracle = oracle; } public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:injectRef-xml.xml"); ctx.refresh(); InjectRef injectRef = ctx.getBean(InjectRef.class); System.out.println(injectRef); ctx.close(); } @Override public String toString() { return oracle.defineMeaningOfLife(); } }
<?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="oracle" name="wiseworm" class="com.anocation.spring5.ch3.BookwormOracle"/> <bean id="injectRef" class="com.anocation.spring5.ch3.InjectRef"> <property name="oracle"> <!--<ref bean="oracle"/>--> <ref bean="wiseworm"/> </property> </bean> </beans>
注入和 ApplicationContext嵌套
Spring 支持ApplicationContext 的层次结构,因此一个上下文(以及相关的 BeanFactory)被认为是另一个上下文的 父级。通过ApplicationContexts 嵌套, 能够将配置分割成不同的文件, 这对于包含许多 bean 的大型项目来说简直就 是天赐之物
public class Song {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
parent-context.xml
<bean id="childTitle" class="java.lang.String" c:_0="daughters"/>
<bean id="parentTitle" class="java.lang.String" c:_0="gravity"/>
child-context.xml
<bean id="song1" class="com.anocation.spring5.ch3.Song" p:title-ref="parentTitle"/>
<bean id="song2" class="com.anocation.spring5.ch3.Song" p:title-ref="childTitle"/>
<bean id="song3" class="com.anocation.spring5.ch3.Song">
<property name="title">
<ref parent="parentTitle"/>
</property>
</bean>
song! 和 song3 bean 都引用了父 ApplicationContext 中的 bean, 而 song2 bean 则引用了子 Application Context 中的 bean
注入集合
可以选择<list>、<map>、
public class LyricHolder {
private String value = "You be the DJ, I'll be the driver";
@Override
public String toString() {
return value;
}
}
<bean id="lyricHolder" class="com.anocation.spring5.ch3.xml.LyricHolder"/>
<bean id="injection" class="com.anocation.spring5.ch3.xml.Collectioninjection">
<property name="map">
<map>
<!-- <entry key="someValue"><value>It's a Friday, we finally made it</value></entry>-->
<!-- <entry key="someBean"><ref bean="lyricHolder"/></entry>-->
<entry key="someValue" value="It's a Friday, we finally made it"/>
<entry key="someBean" value-ref="lyricHolder"/>
</map>
</property>
<property name="props">
<props>
<prop key="firstName">John</prop>
<prop key="secondName">Mayer</prop>
</props>
</property>
<property name="set">
<set>
<value>I can’t believe I get to see your face</value>
<ref bean="lyricHolder"/>
</set>
</property>
<property name="list">
<list>
<value>You’ve been working and I’ve been waiting</value>
<ref bean="lyricHolder"/>
</list>
</property>
</bean>
public class Collectioninjection {
private Map<String,Object> map;
private Properties props;
private Set set;
private List list;
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-xml.xml");
ctx.refresh();
Collectioninjection injection = (Collectioninjection)ctx.getBean("injection");
injection.displayInfo();
ctx.close();
}
public void displayInfo(){
System.out.println("Map contents:\n");
map.entrySet().stream().forEach(e-> System.out.println(e.getKey()+"---"+e.getValue()));
System.out.println("\nProperties contents:\n");
props.entrySet().stream().forEach(e-> System.out.println(e.getKey()+"---"+e.getValue()));
System.out.println("\nSet contents:\n");
set.forEach(System.out::println);
System.out.println("\nList contents:\n");
list.forEach(System.out::println);
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public void setProps(Properties props) {
this.props = props;
}
public void setSet(Set set) {
this.set = set;
}
public void setList(List list) {
this.list = list;
}
}
注解方式
@Service("lyricHolder")
public class LyricHolder {
private String value = "You be the DJ, I'll be the driver";
@Override
public String toString() {
return value;
}
}
<?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"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<context:component-scan base-package="com.anocation.spring5.ch3.annotation"/>
<util:map id="map" map-class="java.util.HashMap">
<entry key="someValue" value="It's a Friday, we finally made it"/>
<entry key="someBean" value-ref="lyricHolder"/>
</util:map>
<util:properties id="props">
<prop key="firstName">John</prop>
<prop key="secondName">Mayer</prop>
</util:properties>
<util:set id="set" set-class="java.util.HashSet">
<value>I can’t believe I get to see your face</value>
<ref bean="lyricHolder"/>
</util:set>
<util:list id="list" list-class="java.util.ArrayList">
<value>You’ve been working and I’ve been waiting</value>
<ref bean="lyricHolder"/>
</util:list>
</beans>
@Service("injection")
public class Collectioninjection {
@Resource(name = "map")
private Map<String,Object> map;
@Resource(name = "props")
private Properties props;
@Resource(name = "set")
private Set set;
@Resource(name = "list")
private List list;
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-annotation2.xml");
ctx.refresh();
Collectioninjection injection = (Collectioninjection)ctx.getBean("injection");
injection.displayInfo();
ctx.close();
}
public void displayInfo(){
System.out.println("Map contents:\n");
map.entrySet().stream().forEach(e-> System.out.println(e.getKey()+"---"+e.getValue()));
System.out.println("\nProperties contents:\n");
props.entrySet().stream().forEach(e-> System.out.println(e.getKey()+"---"+e.getValue()));
System.out.println("\nSet contents:\n");
set.forEach(System.out::println);
System.out.println("\nList contents:\n");
list.forEach(System.out::println);
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public void setProps(Properties props) {
this.props = props;
}
public void setSet(Set set) {
this.set = set;
}
public void setList(List list) {
this.list = list;
}
}
不仅限于注入原始值的集合,还可以注入bean 的集合或其他集合
@Autowired 注解始终将数组、集合和映射视为相应 bean 的集合,而目标 bean 类型从声明的集合值类型派生。例如,如采一个类 具有 List <ContentHolder>类型的属性并且定义了@Autowired i主解,那么 Spring会尝试将当前 ApplicationContext 中 所有 ContentHolder类型的 bean注入该属性(而不是配置文件中声明的<util:list>),这将导致意外的依赖项被注入;或 者如果没有 ContentHolder 类型的 bean, Spring 将抛出一个异常。 因此,对于集合类型注入,必须明确指示 Spring 通过指定@Resource 注解支持的 bean 名称来执行注入。
通过使用@Autowired 和@Qualifier,可以获得使用 bean 名称注入集合的等效配置
@Autowired
@Qualifier("map")
使用方法注入
查找方法注入
-
方法替换
Spring 使用了 CGLIB 的动态字节码增强功能
查找方法注入
public class Singer { private String lyric = "You be the DJ, I'll be the driver"; public void sing() { // System.out.println(lyric); } }
public interface DemoBean { Singer getMySinger(); void doSomething(); }
public class StandardLookupDemoBean implements DemoBean { private Singer mySinger; public void setMySinger(Singer mySinger) { this.mySinger = mySinger; } @Override public Singer getMySinger() { return this.mySinger; } @Override public void doSomething() { getMySinger().sing(); } }
public abstract class AbstractLookupDemoBean implements DemoBean { public abstract Singer getMySinger(); @Override public void doSomething() { getMySinger().sing(); } }
<bean id="singer" class="com.anocation.spring5.ch3.xml.Singer" scope="prototype"/> <bean id="abstractLookupBean" class="com.anocation.spring5.ch3.xml.AbstractLookupDemoBean"> <lookup-method name="getMySinger" bean="singer"/> </bean> <bean id="standardLookupBean" class="com.anocation.spring5.ch3.xml.StandardLookupDemoBean"> <property name="mySinger" ref="singer"/> </bean>
public class LookUpDemo {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-xml.xml");
ctx.refresh();
DemoBean abstractBean = ctx.getBean("abstractLookupBean", DemoBean.class);
DemoBean standardBean = ctx.getBean("standardLookupBean", DemoBean.class);
displayInfo("abstractBean",abstractBean);
displayInfo("standardBean",standardBean);
ctx.close();
}
public static void displayInfo(String beanName,DemoBean bean){
Singer singer1 = bean.getMySinger();
Singer singer2 = bean.getMySinger();
System.out.println(beanName+" ? "+(singer1==singer2));
StopWatch stopWatch = new StopWatch();
stopWatch.start("lookupDemo");
for (int x = 0; x < 100000; x++) {
Singer singer = bean.getMySinger();
singer.sing();
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis()+"ms");
//abstractBean ? false
//281ms
//standardBean ? true
//1ms
}
}
对于 abstractLookupBean bean, 每次调用 getMySinger都应该检索一个新的 Singer 实例,因此引用不应该相同。 而对于 standardLookupBean Bean,一个单一的 Singer 实例通过setter 注入被传递给 bean,并且该实例被存储且在每 次调用 getMySinger时返回,所以这两个引用应该是相同的。
使用注解
@Component("singer")
@Scope("prototype")
public class Singer {
private String lyric = "You be the DJ, I'll be the driver";
public void sing() {
// System.out.println(lyric);
}
}
@Component("standardLookupBean")
public class StandardLookupDemoBean implements DemoBean {
private Singer mySinger;
@Autowired
@Qualifier("singer")
public void setMySinger(Singer mySinger) {
this.mySinger = mySinger;
}
@Override
public Singer getMySinger() {
return this.mySinger;
}
@Override
public void doSomething() {
getMySinger().sing();
}
}
@Component("abstractLookupBean")
public abstract class AbstractLookupDemoBean implements DemoBean {
@Lookup("singer")
public abstract Singer getMySinger();
@Override
public void doSomething() {
getMySinger().sing();
}
}
当想要使用两个具有不同生命周期的 bean 时,可以使用查找方法注入。 当 bean 共享相同的生命周期时, 应该 避免使用查找方法注入,尤其是当这些 bean 都是单例时
方法替换
有一个在 Spring 应用程序中使用的第三方库, 并且需要更改某个方法的逻辑。 此时,无法更改源代 码,因为它是由第三方提供的,所以一种解决方案是使用方法替换,使用自己的实现来替换该方法的逻辑。
public class ReplacementTarget {
public String formatMessage(String msg){
return "<h1>"+msg+"</h1>";
}
public String formatMessage(Object msg){
return "<h1>"+msg+"</h1>";
}
}
/**
* 如果想要替换一个方法,首先需要创建MethodReplacer接口的一个实现
*/
public class FormatMessageReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
if (isFormatMessageMethod(method)){
String msg = (String) args[0];
return "<h2>"+msg+"</h2>";
}else {
throw new IllegalArgumentException("Unable to reimplement method"+method.getName());
}
}
private boolean isFormatMessageMethod(Method method){
if (method.getParameterTypes().length!=1){
return false;
}
if (!("formatMessage".equals(method.getName()))){
return false;
}
if (method.getReturnType()!=String.class){
return false;
}
if (method.getParameterTypes()[0]!=String.class){
return false;
}
return true;
}
}
<bean id="methodReplacer" class="com.anocation.spring5.ch3.xml.FormatMessageReplacer"/>
<bean id="replacementTarget" class="com.anocation.spring5.ch3.xml.ReplacementTarget">
<replaced-method name="formatMessage" replacer="methodReplacer">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="standardTarget" class="com.anocation.spring5.ch3.xml.ReplacementTarget"/>
public class MethodReplacementDemo {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:app-context-xml.xml");
ctx.refresh();
ReplacementTarget replacementTarget = ctx.getBean("replacementTarget", ReplacementTarget.class);
ReplacementTarget standardTarget = ctx.getBean("standardTarget", ReplacementTarget.class);
displayInfo(replacementTarget);
displayInfo(standardTarget);
ctx.close();
}
private static void displayInfo(ReplacementTarget target){
System.out.println(target.formatMessage("Thanks for playing , try again !"));
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 1000000; i++) {
target.formatMessage("No filter in my head");
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis()+"ms");
//<h2>Thanks for playing , try again !</h2>
//206ms
//<h1>Thanks for playing , try again !</h1>
//27ms
//动态替换的方法比静态定义的芳法慢很多倍
}
}
在不同情况下,可以证明方法替换是非常有用的,尤其是当只想覆盖单个bean 的特定方法而不是相同类型的所 有 bean 时。 即使有这种说法,很多人也仍然倾向于使用标准的 Java 机制来重写方法,而不是依赖于运行时字节码 的增强。
应该避免针对多个不相关的方法使用单个 MethodReplacer, 这样会导致在代码查找应该重新实现的方法时执行不必要的字符串比较。 执行简单的检查以确保MethodReplacer使用正确的方法是很有用的,并且不会为代码增 加太多开销。