Spring 自动装配
Spring会自发得在容器中寻找bean所需的依赖,主要从以下两方面来实现
1.组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
2.自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
新建Cat、Dog、User类
public class Cat {
String name;
public void setName(String name) {
this.name = name;
}
public void shout(){
System.out.println(name+"miaomiaomiao");
}
}
public class Dog {
String name;
public void setName(String name) {
this.name = name;
}
public void shout(){
System.out.println(name+"wangwangwang");
}
}
public class User {
Dog dog;
Cat cat;
public void setDog(Dog dog) {
this.dog = dog;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public void show(){
dog.shout();
cat.shout();
}
}
可以看出User类依赖Dog和Cat类
配置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="cat" class="com.jojo.Cat">
<property name="name" value="小猫"></property>
</bean>
<bean id="dog" class="com.jojo.Dog">
<property name="name" value="小狗"></property>
</bean>
<bean id="user" class="com.jojo.User" autowire="byName">
</bean>
</beans>
可以看到user的bean中并没有给它注入属性,只是多加了一个autowire参数
进行测试
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user = applicationContext.getBean("user",User.class);
user.show();
}
输出:
小狗wangwangwang
小猫miaomiaomiao
可以看出属性已经成功注入了
autowire有两种方式,byName和byType
byName
修改配置文件中autowire为byName
<?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="cat" class="com.jojo.Cat">
<property name="name" value="小猫"></property>
</bean>
<bean id="dog" class="com.jojo.Dog">
<property name="name" value="小狗"></property>
</bean>
<bean id="user" class="com.jojo.User" autowire="byName">
</bean>
</beans>
此时Spring容器使用User类中的属性,例如cat的set方法setCat,根据set方法中小写的cat去Spring容器中寻找与之相匹配的id,若能寻找到,则注入,若未寻找到,则会报空指针异常
例如我们把setCat改为setCat2
public void setCat2(Cat cat) {
this.cat = cat;
}
此时去执行测试
@org.junit.Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user = applicationContext.getBean("user",User.class);
user.show();
}
输出:
小狗wangwangwang
java.lang.NullPointerException
at com.jojo.User.show(User.java:18)
at Test.test(Test.java:11)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
但,若是将xml中cat的id改为cat2
<bean id="cat2" class="com.jojo.Cat">
<property name="name" value="小猫"></property>
</bean>
再次进行测试
输出:
小狗wangwangwang
小猫miaomiaomiao
byType
byType 通过类型在Spring容器中寻找与之相符合的bean并进行注入,若容器中存在多个相同类型的bean,注入时则会报错
依旧以User类为例,修改配置xml
<bean id="user" class="com.jojo.User" autowire="byType"></bean>
进行测试
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user = applicationContext.getBean("user",User.class);
user.show();
}
输出结果
小狗wangwangwang
小猫miaomiaomiao
若此时修改setCat为setCat2,进行测试
public void setCat2(Cat cat) {
this.cat = cat;
}
输出:
小狗wangwangwang
小猫miaomiaomiao
可见,修改set方法名对于byType并没有影响
此时,若在xml中新增一个Dog的bean
<bean id="dog" class="com.jojo.Dog">
<property name="name" value="小狗"></property>
</bean>
<bean id="dog2" class="com.jojo.Dog">
<property name="name" value="小狗"></property>
</bean>
进行测试,则报错
NoUniqueBeanDefinitionException
因此,使用byType时,需要确保Spring容器中具有唯一类型的bean
使用注解的方式进行自动装配
1.配置Spring约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
2.开启注解支持
<context:annotation-config></context:annotation-config>
3.修改User代码,删除set方法
public class User {
@Autowired
Dog dog;
@Autowired
Cat cat;
public void show(){
dog.shout();
cat.shout();
}
}
进行测试
输出
nullwangwangwang
nullmiaomiaomiao
因为cat与dog属性中的name都未注入,因此输出为null,但是可以看到user中的dog与cat属性已成功注入了
注意
@AutoWired(required=false) 注入的对象可以为null
示例如下
在xml配置中删除cat的bean信息
<bean id="dog" class="com.jojo.Dog"></bean>
<bean id="user" class="com.jojo.User"></bean>
在User类中添加@AutoWired(required=false)
@Autowired(required = false)
Cat cat;
输出结果
nullwangwangwang
java.lang.NullPointerException
at com.jojo.User.show(User.java:14)
at Test.test(Test.java:11)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
可以看到,虽然required=false,但是仍然会报出空指针
@AutoWired自动装配时,先byType后byName,可能有些博文都说@AutoWired是byType的,并且需要和@Qualifier一起使用,才能达到同时识别byType和byName,但是经过测试,实际上@AutoWired是既支持byType,也支持byName的
修改xml配置
修改xml配置
<bean id="dog" class="com.jojo.Dog"></bean>
<bean id="dog2" class="com.jojo.Dog"></bean>
<bean id="cat" class="com.jojo.Cat"></bean>
<bean id="cat2" class="com.jojo.Cat"></bean>
<bean id="user" class="com.jojo.User"></bean>
先前byType的例子可以知道,如果@AutoWired是byType的,那么此时应该会报错NoUniqueBeanDefinitionException
但是测试可正常输出结果
wangwangwang
miaomiaomiao
修改xml配置
<bean id="dog2" class="com.jojo.Dog">
<property name="name" value="狗2"></property>
</bean>
<bean id="dog" class="com.jojo.Dog">
<property name="name" value="狗1"></property>
</bean>
<bean id="cat" class="com.jojo.Cat">
<property name="name" value="猫1"></property>
</bean>
<bean id="cat2" class="com.jojo.Cat">
<property name="name" value="猫2"></property>
</bean>
<bean id="user" class="com.jojo.User"></bean>
输出
狗1wangwangwang
猫1miaomiaomiao
由此可见,@AutoWired实际上是先byType,后byName,而非单纯的byType
@Resource
@Resource是jdk自带的注解,先byName,后byType
@Resource可以指定name,如@Resource(name=“cat”)
修改User类
public class User {
@Resource
Dog dog;
@Resource
Cat cat;
public void show(){
dog.shout();
cat.shout();
}
}
进行测试
输出
狗1wangwangwang
猫1miaomiaomiao
可见@Resource注解也生效了
修改User代码
public class User {
@Resource(name = "dog2")
Dog dog;
@Resource(name = "cat2")
Cat cat;
public void show(){
dog.shout();
cat.shout();
}
}
输出
狗2wangwangwang
猫2miaomiaomiao
小结
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。