转载:
https://blog.csdn.net/a745233700/article/details/89307518
https://blog.csdn.net/m0_37556444/article/details/83108929
https://www.jianshu.com/p/0c3f7e00eba2
注入
平常的Java开发中,程序员在某个类中需要依赖其它类的方法。 通常是new一个依赖类的实例再调用该实例的方法,这种开发存在的问题是new的类实例不好统一管理。
Spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过Spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。
依赖注入的另一种说法是”控制反转”。通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员。而控制反转是指new实例工作不由我们程序员来做而是交给Spring容器来做。
Spring有多种依赖注入的形式,本篇文章仅介绍Spring通过xml进行IOC配置的方式。
Set()注入:
这是最简单的注入方式,假设有一个SpringAction,类中需要实例化一个SpringDao对象,那么就可以定义一个private的SpringDao成员变量,然后创建SpringDao的set方法(这是ioc的注入入口):
package com.bless.springdemo.action;
public class SpringAction {
//注入对象springDao
private SpringDao springDao;
//一定要写被注入对象的set方法
public void setSpringDao(SpringDao springDao) {
this.springDao = springDao;
}
public void ok(){
springDao.ok();
}
}
随后编写spring的xml文件中,name属性是class属性的一个别名,class属性指类的全名,因为在SpringAction中有一个公共属性Springdao,所以要在标签中创建一个标签指定SpringDao。标签中的name就是SpringAction类中的SpringDao属性名,ref指下面,这样其实是spring将SpringDaoImpl对象实例化并且调用SpringAction的setSpringDao方法将SpringDao注入:
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(1)依赖注入,配置当前类中相应的属性-->
<property name="springDao" ref="springDao"></property>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
构造器注入:
这种方式的注入是指带有参数的构造函数注入,看下面的例子,我创建了两个成员变量SpringDao和User,但是并未设置对象的set方法,所以就不能支持第一种注入方式,这里的注入方式是在SpringAction的构造函数中注入,也就是说在创建SpringAction对象时要将SpringDao和User两个参数值传进来:
public class SpringAction {
//注入对象springDao
private SpringDao springDao;
private User user;
public SpringAction(SpringDao springDao,User user){
this.springDao = springDao;
this.user = user;
System.out.println("构造方法调用springDao和user");
}
public void save(){
user.setName("卡卡");
springDao.save(user);
}
}
在XML文件中同样不用的形式,而是使用标签,ref属性同样指向其它标签的name属性:
<!--配置bean,配置后该类由spring管理-->
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(2)创建构造器注入,如果主类有带参的构造方法则需添加此配置-->
<constructor-arg ref="springDao"></constructor-arg>
<constructor-arg ref="user"></constructor-arg>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
<bean name="user" class="com.bless.springdemo.vo.User"></bean>
解决构造方法参数的不确定性:你可能会遇到构造方法传入的两参数都是同类型的,为了分清哪个该赋对应值,则需要进行一些小处理:下面是设置index,就是参数位置:
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<constructor-arg index="0" ref="springDao"></constructor-arg>
<constructor-arg index="1" ref="user"></constructor-arg>
</bean>
另一种是设置参数类型:
<constructor-arg type="java.lang.String" ref=""/>
静态工厂/实例工厂的方法注入:
<!-- 使用静态工厂进行创建-->
<!-- class的值不是写User对象的全路径,而是写静态工厂的全路径-->
<!-- factory-method的值写要调用的方法-->
<bean id="user2" class="com.imooc.entity.factory.StaticFactory" factory-method="getUser" scope="singleton" />
<!-- 使用实例工厂进行创建-->
<!-- 需要先创建factoryBean对象,再通过factoryBean对象进行调用-->
<bean id="userFactory" class="com.imooc.entity.factory.UserFactory"/>
<bean id="user3" factory-bean="userFactory" factory-method="getUser" scope="singleton" />
自动装配
XML中使用的自动装配
- byName方式:把该Bean的id改成了与引用它的Bean属性相同的名字(id=”account” 可忽略属性首字每大小写),然后使用byName的方式来自动装配,对user bean来说省略配置一个元素。
<!--以下是使用自动装配,假设这里定义的id为account-->
<bean id="account" class="twm.demo.Account"/>
<bean id="user" class="twm.demo.User" autowire="byName">
<property name="username" value="Yanglan"/>
</bean>
- byType方式:把autowire属性值改为byType后,在注入account属性时,并不关心bean id了,而是查找容器中是否有类型为twm.demo.Account的bean。但是如果有多个bean的类型都匹配的情况,那么就会出错,因为byType方式只允许匹配一个类型相同的Bean。如果在容器中存在多个类型相同的bean怎么办呢?见后面的多个bean的问题。
spring提供了另外两种选择,可以设置一个首选bean,或者排除一些bean。元素的primary属性代表是否是首选bean,如果标注为true,那么该bean将成为首选bean。但是spring默认每个bean的primary属性都是true,所以如果需要设置首选bean需要将那些非首选bean的primary属性标注为false。
<bean id="ac_anyname" class="twm.demo.Account"/>
<bean id="user" class="twm.demo.User" autowire="byType">
<property name="username" value="Yanglan"/>
</bean>
<!--。。。。。。。分割线。。。。。。。。。-->
<bean id="account" class="twm.demo.Account"/>
<bean id="account_ent" class="twm.demo.Account" primary="false" />
- constructor构造方式:构造函数的参数通过byType进行装配
<bean id="yanglan" class="twm.demo.User" autowire="constructor">
</bean>
- autodetect最佳自动装配:首先使用constructor方式进行装配,如果不行,就使用byType方式装配。使用方法跟以上介绍的都是一样的 ,这里不多说了
<bean id="yanglan" class="twm.demo.User" autowire="autodetect">
</bean>
- 默认自动装配:在元素中添加一个default-autowire属性,该配置文件当中的所有bean将会进行自动装配,如果有特定的bean需要使用其他的方式,在该bean上直接设置autowire属性就可以了,会覆盖掉默认自动装配的配置,代码如下。
<beans ... default-autowire="byType">
</beans>
- 自动装配侯选者:XML配置中默认所有的bean都是自动装配的侯选者。如果设置元素的autowire-candidate属性为false,该bean将不用于自动装配。autowire-candidate默认值为true。元素的default-autowire-candidates属性的值允许使用通配符,例如我们制定default-autowire-candidates=“*abc”,则所有以“abc”结尾的Bean都将被包含到自动装配的待选类中。该属性可以指定多个匹配字符串,匹配任一字符串的Bean都将作为侯选者。
使用注解自动装配
如果不想在xml文件中使用autowire属性来启用自动装配,还可以直接在类定义中使用@Autowired或@Resource来装配bean。
使用@Autowired注解:
@Autowired注解默认使用的是byType的方式向Bean里面注入相应的Bean。使用@Autowired自动装配时,容器中只能有一个适合的Bean待选,否则的话,spring会抛出异常。@Autowired注解可以用在任何方法上,不一定非得是setter方法,只要方法有需要自动装配的参数都可以,但是一般都是使用在setter方法和构造器上的。
- 用于setter方法:看如下代码:把@Autowired注解在setter方法上,在spring创建该类的bean的时候,就会自动寻找匹配的参数注入到该bean当中。
@Autowired
public void setNotifyservice(NotifyService notifyservice) {
this.notifyservice = notifyservice;
}
- 用于构造函数:@Autowired另外一个用法就是注解构造函数
public class Order {
@Autowired
public Order(NotifyService notifyservice) {
this.notifyservice = notifyservice;
}
//......省略部分代码
- 直接注解在属性(最常用):
@Autowired
private NotifyService notifyservice;
@Resource
可以用@Resource代替@Qualifier和@Autowired,但是 @Resource 是 jdk 的注解,而 @Qualifier 和 @Primary 是spring 的注解,这是它们的区别。 @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
@Service("service")
public class DemoServiceImpl {
@Resource(name="d1")
private DemoDao demoDao;
public void service(){
demoDao.test();
}
}
自动装配时有多个bean的问题
当一个接口有多个不同的实现类时,使用@Autowired注解时会报org.springframework.beans.factory.NoUniqueBeanDefinitionException异常信息。
@Primary
在众多相同的bean中,优先使用用@Primary注解的bean。
public interface DemoDao {
void test();
}
@Repository("d1")
public class DemoDaoImpl1 implements DemoDao{
public void test() {
System.out.println("11111111");
}
}
@Repository("d2")
@Primary
public class DemoDaoImpl2 implements DemoDao{
public void test() {
System.out.println("22222222");
}
}
@Service("service")
public class DemoServiceImpl {
@Autowired
private DemoDao demoDao;
public void service(){
demoDao.test();
}
}
@Configuration
@ComponentScan("com.msj.demo02")
public class AppConfig {
}
public class TestDemo {
@Autowired
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
DemoServiceImpl s = (DemoServiceImpl)context.getBean("service");
s.service();
}
}
@Qualifier
Qualifier的意思是合格者,通过这个标示,指定某个bean有没有资格进行注入。
在上面的例子进行修改,修改DemoServiceImpl.java
@Service("service")
public class DemoServiceImpl {
@Autowired
@Qualifier("d1")
private DemoDao demoDao;
public void service(){
demoDao.test();
}
}
两个方案进行对比,当一个接口有多个实现类的时候,使用 @Qualifier 比较好,可以通过 @Qualifier(“xxx”) ,就可以看出使用哪个实现类。