目录
一、配置spring容器(将bean装配到spring容器中)
4.1 Singleton Beans with Prototype-bean Dependencies
4.1.1 ApplicationContextAware接口
5.1 Initialization Callbacks(初始化回调)
5.2 Destruction Callbacks(销毁回调)
5.3 Default Initialization and Destroy Methods(默认初始化和销毁方法)
5.3.1 XML(init-method和destroy-method)
一、配置spring容器(将bean装配到spring容器中)
使用ioc首先就要配置好spring容器,要告诉容器我需要它管理的bean是什么,以及bean之间的依赖关系
配置spring(初始化spring环境)的方式有三种(spring的编程风格):
- schemal-based-------xml
- annotation-based-----annotation
- java-based----java Configuration
我们首先创建好项目
IndexDao接口:
package priv.cy.dao;
public interface IndexDao {
public void test();
}
IndexDao实现类:
package priv.cy.dao;
public class IndexDaoImpl implements IndexDao {
private String str;
@Override
public void test() {
System.out.println(str);
System.out.println("Impl");
}
public void setStr(String str) {
this.str = str;
}
public String getStr() {
return str;
}
}
Service类:
package priv.cy.dao;
public class IndexService {
private IndexDao dao;
public IndexService(IndexDao dao) {
this.dao = dao;
}
public void service() {
dao.test();
}
}
1.1、XML方式
首先讲第一种,使用XML
先在resources创建一个XML,XML保存到resource包中,XML文件的名字可以任意,这里命名为spring.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">
<!--上面这些叫做命名空间-->
<!--在bean标签中指定要装配给spring容器的类,在class属性写好类的全限定名,设置其id,如果不设置name默认name和id相同-->
<bean id="dao" class="priv.cy.dao.IndexDaoImpl">
<property name="str" value="aaa"></property>
</bean>
<bean id="service" class="priv.cy.dao.IndexService">
<!--使用构造方法注入-->
<constructor-arg ref="dao"></constructor-arg>
<!--使用set方法注入,这里配置的是service这个类依赖的对象,ref中写的是xml文件中定义的该对象的id-->
<property name="dao" ref="dao"></property>
</bean>
</beans>
在这个XML文件中来指定要委托给spring容器管理的对象,然后指定对象之间的依赖关系,依赖关系是通过注入的方式来实现的,这里再讲一下注入方式,一共有三种
- 构造方法注入。
- setter方法注入。
- 接口注入
接口注入在spring3提供的,现在的spring版本已经取消了
上面展示了setter方法注入(使用<property>标签)和构造方法注入(使用<constructor-arg>标签),这两种选一种使用就可以。注意,如果依赖的是已经交给spring容器管理的bean(即已经在xml中配置过的bean),依赖关系用ref,等号右边写定义的name。配置bean的时候如果只定义了id,那么它的name默认和id一样。
如果注入的不是被spring容器管理的bean,就不要用ref,比如indexDaoImpl依赖一个String对象,就用value后面直接写String的值就可以了。
通过上面的配置,spring容器已经接管了我们的bean
创建测试类进行测试,注意创建spring容器对象时构造方法传入的是classpath:spring.xml。
package priv.cy.dao;
public class Test {
public static void main(String[] args) {
/**
* XML是放到resource包下的,这样编译后才会被复制到classpath路径下,XML的编译就是原样复制,.java的编译时编程.class再放到classpath目录中
*/
// 如果使用了XML,就需要用这个类来获取Spring容器,通过该对象来获取bean
ClassPathXmlApplicationContext classPathXmlApplicationContext
= new ClassPathXmlApplicationContext("classpath:spring.xml");
IndexService service = (IndexService) classPathXmlApplicationContext.getBean("service");
service.service();
}
}
通过spring容器获取bean,实现了IOC控制反转
1.2、注解方式
再讲第二种。使用注解的方式来配置spring容器
首先注解的方式依旧还是需要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">
<!--上面这些网址叫做命名空间-->
<!--刚刚使用的是XML的方式来管理spring容器,下面讲解用注解的方式管理容器-->
<!--还可以使用注解来管理spring容器,但是spring默认是关闭注解支持的,并且不会去扫描注解,这么做是为了节约性能-->
<!--开启支持注解解析-->
<context:annotation-config></context:annotation-config>
<!--开启扫描com包下面的所有注解-->
<context:component-scan base-package="priv"></context:component-scan>
<!--以前spring要使用注解管理spring容器这两条必须要写,但是现在把第一条合并到第二条了,所以现在写下面这一条就可以了-->
</beans>
Dao类:
通过注解,将IndexDaoImpl装配到Spring容器中去,可以通过@Value标签给依赖的String对象注入常量值
package priv.cy.dao;
import org.springframework.stereotype.Component;
// 这里可以指定放入容器之后这个类的id是dao,如果不写的话默认就是类名
@Component("dao")
public class IndexDaoImpl implements IndexDao {
@Value("aaa")
private String str;
@Override
public void test() {
System.out.println(str);
System.out.println("Impl 0");
}
public void setStr(String str) {
this.str = str;
}
public String getStr() {
return str;
}
}
Service类:
通过注解将该类装配到Spring容器中,并且直接通过@Autuwired注解将依赖的对象注入给IndexDaoImpl
package priv.cy.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
// 可以在括号里指定他的id,如果不指定id默认就是类名
@Service
public class IndexService {
@Autowired // 默认使用的是构造方法进行注入
private IndexDao dao;
public IndexService(IndexDao dao) {
this.dao = dao;
}
public void service() {
dao.test();
}
}
测试类:
也就是使用解析XML的那个类来获取spring容器对象,进而来获取bean
package priv.cy.dao;
public class Test {
public static void main(String[] args) {
/**
* XML是放到resource包下的,这样编译后才会被复制到classpath路径下,XML的编译就是原样复制,.java的编译时编程.class再放到classpath目录中
*/
// 如果使用了XML,就需要用这个类来获取Spring容器,通过该对象来获取bean
ClassPathXmlApplicationContext classPathXmlApplicationContext
= new ClassPathXmlApplicationContext("classpath:spring.xml");
IndexService service = (IndexService) classPathXmlApplicationContext.getBean("service");
service.service();
}
}
AnnotationConfigApplicationContext和ClassPathXmlApplicationContext这两个对象的getBean方法传入的参数可以是bean的name,也可以是xxx.class,即可以通过name来获取bean,也可以通过type来获取bean。如果传入的是xx.class,那么返回的对象就是这个类型,不需要强转,如果传入的是name,那么返回的类型就是Object,需要强转,除非在传入的参数中添加一个class参数指名bean的类型。例如:
IndexDaodao = annotationConfigApplicationContext.getBean("indexDao", IndexDao.class);
1.3、JavaConfig
最后讲一下javaConfig的当时来管理spring容器,因为从上面两种可以看出,都是需要有XML文件的,即使是注解方式,也需要有XML文件来开启支持注解。但是javaConfig方法,就可以完全不需要XML文件。
package priv.cy.dao;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 前面讲的是XML和注解的管理spring的方式,可是注解的方式本身还需要通过XML来开启注解支持,所以本质还是离不开XML
* 所以就可以使用Javaconfig的方式,直接创建一个Java类来管理spring,完全不需要XML
*/
// 将该类设置为一个配置类,该类就相当于一个XML
@Configuration
// 开启扫描,指定要扫描哪些包,就和XML中开启扫描的作用是一样的,如果不开启就没有办法使用注解。必须要指定扫描位置
@ComponentScan("priv")
public class Spring {
}
通过这个JavaConfig类,也就开启了注解支持,后面的描述bean之间的依赖关系就可以直接使用注解了,完全不需要XML
在获取spring容器对象的时候会有些不同,因为spring解析XML和解析javaConfig功能的实现是使用的不同的类,所以通过javaConfig配置spring容器的话,获取spring容器对象的时候需要使用另一个对象,注意传入的是spring.class。
AnnotationConfigApplicationContext(通过注解的方式初始化应用程序上下文)这个类本身是支持解析注解的功能的,之前的ClassPathXmlApplicationContext(通过XML的方式初始化应用程序上下文)不支持解析注解,所以在之前如果要使用注解,还需要在XML中开启支持解析注解。但是AnnotationConfigApplicationContext类本身就支持解析注解,所以也就不用再从XML中开启了。只需要在javaConfig类中指定要扫描的位置就可以了。
测试类:
package priv.cy.dao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
//如果没有使用XML,使用的Javaconfig的方式来进行管理spring的,就不能使用上面的方法来获取Spring容器了,要使用这个类来获取
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Spring.class);
IndexService service1 = (IndexService) annotationConfigApplicationContext.getBean("service");
service.service();
}
}
1.4、混合使用
也可以混合使用,即用Javaconfig也用XML,不用注解将indexDaoImpl装载到spring容器了,也不用注解来标识各个bean之间的依赖关系,而是直接通过XML来用bean标签进行装载
然后在Javaconfig那个类上再加一个注解@ImportResource("classpath:spring.xml"),表示也根据xml的配置来管理spring容器,解析这个XML,进行混合使用
package priv.cy.dao;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("priv")
@ImportResource("classpath:spring.xml")
public class Spring {
}
我不是很喜欢写XML,所以我一般就是使用javaConfig的方式和注解的方式来配置spring容器
1.5、实现bean的单独注册
所谓的初始化spring环境就是将我们交给spring管理的类实例化
这里注册的意思其实就是装配
我们已经讲了上面几个初始化spring的环境的方法
1、xml ClassPathXmlApplicationContext 提供了类的扫描和单独bean的注册(就是在xml定义一个类的bean标签,spring容器就会完成这个类的声明和注册,也就是实例化)这两个功能,xml可以不用扫描代码就可以完成对指定bean的装配,只要是在xml声明了,它就会对这个类完成注册,实现实例化。
2、annotation 使用注解
3、javaConfig AnnotationConfigAoolicationContext 类的扫描 类的定义(Java代码) 不能单独注册一个类,就是说必须进行扫描,才可以实现类的定义,类的扫描和类的定义是不能分开的。从代码层面就是说如果我们不开启扫描注解,我们添加的注解就是无效的。
但实际上spring给Annotation提供了单独注册方法,只不过用的很少,可以不使用扫描直接去将指定的类在spring容器中注册,完成bean的装配。我们可以使用register这个方法来完成对bean的单独注册
先说一下register这个方法:
javaConfig:
@Configuration
@ComponentScan("priv")
public class AppConfig {
}
service:
@Service
public class IndexService {
public void service() {
System.out.println("service");
}
}
测试类:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext();
// 实现类的单独注册,不需要扫描注解就可以将其来spring容器中实例化
annotationConfigApplicationContext.register(AppConfig.class);
// 使用register方法来实现类的单独注册必须要refresh
annotationConfigApplicationContext.refresh();
System.out.println(annotationConfigApplicationContext.getBean(IndexService.class).getClass().getName());
}
}
执行结果:
我们发现直接将配置JavaConfig配置文件直接注册进register方法也是可以实现相同的效果,扫描注解,实现装配,但是使用register方法进行注册必须要手动refreshsh。这些都可以从源码中找到原因。
这个是AnnotationConfigApplicationContext类的构造方法源码,也就是我们平时将javaConfig的class传入进行spring容器配置的方法。
public AnnotationConfigApplicationContext(Class<?>...annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
我们发现原有的构造方法本质也是调用的register方法实现的配置文件注册,并且帮我们refresh了,所以如果我们直接调用register方法,我们还需要自己手动refresh。
那么如何使用register实现单独bean注册呢,很简单
我们不传入javaConfig类,并且将service类上的@Service注解去掉,看看如何在无注解和不进行扫描的情况下手动将我们的bean装配到spring容器中
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext();
// 实现类的单独注册,不需要扫描注解就可以将其来spring容器中实例化
annotationConfigApplicationContext.register(IndexService.class);
// 使用register方法来实现类的单独注册必须要refresh
annotationConfigApplicationContext.refresh();
System.out.println(annotationConfigApplicationContext.getBean(IndexService.class).getClass().getName());
}
}
执行结果:
很简单,我们只需要将要装配的类的class传入到register方法中就实现了单独注册装配,没有添加javaConfig配置,不需要扫描,也不需要加注解。
如果阅读了register源码,我们就能发现register支持注册两种bean,一个是普通bean,一个就是javaConfig的bean,所以它不光支持读取我们的配置类,也支持让我们单独注册bean到spring容器当中
1.6 插一句:
XML中还有一个depends-on 这个可以规定bean在容器中的创建顺序。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
比如上面的这种配置,通过depends-on配置,那么manager就会先于beanOne创建,但是这两个bean之间是没有依赖关系的。这种操作的应用场景就是如果beanOne的初始化需要用到manager的一些信息或者beanOne中的一些静态方法需要用到manager的信息,那么就得保证manager先于beanOne创建。但是这两个bean之间并没有依赖关系,所以只能使用depends-on的方法
二、自动装配
下面我们再说一下自动装配这个问题
定义还是要定义的,就是说要将哪个类作为bean装配给spring容器管理,但是通过自动装配的技术bean之间的依赖关系不需要我们来设置了,比如如果使用XML来配置,我们就不需和上面一样还要在bean标签中再嵌套设置该bean所依赖的对象了。
只需要把所有要装配给spring容器的对象在XML文件中设置好或者用注解指定好,然后spring会直接根据定义类时的代码获取到依赖关系,自动给需要依赖的对象进行装配,这就大大减少了描述依赖关系的配置。
手动装配的优先级高于自动装配。
自动装配的优点:
- 大大减少我们描述依赖关系是的大篇幅配置
- 如果对象依赖关系更新,也不需要重新配置
自动装配的方法:
- no/default
- byName
- byType
- constructor
指定自动装配的方式有两种:
- 全局配置:全局都用指定的自动装配方法,如全局都用byName
- 局部配置:指定的自动装配方法只在局部有效
2.1、使用XML来完成自动装配
全局指定,直接在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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="service" class="priv.cy.dao.IndexService"></bean>
</beans>
2.1.1、byType
就是通过类型来查找相应的依赖关系
一上面的XML配置文件为例
开启了自动装配之后,首先spring先扫描XML文件中设置的要装配给spring容器管理的bean,然后扫描到了indexService这个bean之后,会先去扫描定义这个类的代码,发现里面有一个类型是IndexDao的依赖对象,然后就会返回来扫描XML配置文件,查看有没有交给spring容器管理的这个类型的bean,最后发现了id为dao的这个bean,然后就直接将这个bean注入给indexService对象了,这就是通过类型来进行自动装配。
XML中配置的bean,底层都会讲他们添加到一个map中,所以上面扫描bean的过程在底层就是遍历这个map。
但是上述方法有一个问题,刚才的例子只是将一种IndexDao接口的实现类装配到spring容器中去管理,如果将两种IndexDao接口的实现类装配到spring容器中,就会出现报错,因为spring容器发现这两种类型都可以注入给IndexService,不知道应该使用哪个了
例如定义两个IndexDao实现类:
package priv.cy.dao;
public class IndexDaoImpl implements IndexDao {
@Override
public void test() {
System.out.println("Impl 0");
}
}
package priv.cy.dao;
public class IndexDaoImpl1 implements IndexDao {
@Override
public void test() {
System.out.println("Impl 1");
}
}
将其交给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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService"></bean>
</beans>
运行时报错如下:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'priv.cy.dao.IndexDao' available: expected single matching bean but found 2: dao,dao1
上面的意思就是说应该注入IndexDao类型的对象,但是发现了两个bean,dao和dao1都可以注入,这才会报错。这时就需要用byName来解决
而且还有一点要注意,使用自动装配的时候默认使用的是无参构造方法来创建对象,所以要确保所有的bean都有无参构造方法,要么不显示地写构造方法,要么显示写出无参构造方法(如果写了有参构造方法必须也写上无参构造方法,因为有参构造方法会使默认的隐式无参构造方法失效)
2.1.2、byName
这里就是通过指定bean的Name来进行自动装配
<?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"
default-autowire="byName">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService"></bean>
</beans>
前面说过,如果不指定bean的name,那么name默认和id一样,使用byName进行自动装配的时候,使用的是类的set方法名来进行Name匹配的,如:
package priv.cy.dao;
public class IndexService {
private IndexDao dao;
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
比如上面的service类,setter方法的方法名是setDao,那么再进行自动装配的时候就会自动将方法名前面的set去掉,将剩下的第一个字母变小写,得到的name去和XML中设置的bean的name进行比较,找到name为dao的这个bean,将其注入到service类中。此时运行service方法,就会输出Impl 0
所以这里要识别的名字只和set方法中的哪个后缀名字有关,和service类中定义的依赖对象的名字 private IndexDao dao;,也就是dao是没有关系的。
比如我将
<bean id="dao1" name="aaa" class="priv.cy.dao.IndexDaoImpl1"></bean>
IndexDaoImpl1这个类的name改成aaa,然后将service中的setter改为setAaa
public class IndexService {
private IndexDao dao;
public void service() {
dao.test();
}
public void setAaa(IndexDao dao) {
this.dao = dao;
}
}
这个时候输出的就是Impl 1而不是Impl 0l了,说明byName只和bean的name值以及setter方法的名字有关系。和定义类的时候给依赖对象起的名字无关。
2.1.3、no和default
<?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"
default-autowire="default">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService"></bean>
</beans>
这两个作用一样,都是使用手动装配,如果没有手动装配的话,会报空指针错误
2.1.4、constructor
<?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"
default-autowire="constructor">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService"></bean>
</beans>
这个和byType差不多,都是依靠类型来进行自动装配,区别就是byType根据的是定义的成员属性的类型来进行自动装配的
public class IndexService {
private IndexDao dao;
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
它是根据里面private IndexDao dao; 这里面定义的依赖对象的类型IndexDao来spring容器中的bean进行类型匹配的
而constructor使用的是构造方法的类型来进行匹配的,所以使用这种方式要设置好构造方法
public class IndexService {
private IndexDao dao;
public IndexService (IndexDao dao) {
this.dao = dao;
}
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
他就会根据构造方法中传入的参数类型进行匹配,取得了构造方法中传入的参数类型是IndexDao,然后再去spring容器中找这个类型的bean,找到后将其注入到IndexService的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
>
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService" autowire="byName"></bean>
</beans>
像这种,在需要自动装配的bean标签中,添加autowire属性,等号右边是自动装配的方式。这样只有service这个bean使用自动装配,其他的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService" autowire="byName"></bean>
</beans>
向这种写法就可以实现service这个bean使用byName进行自动装配,其他的bean使用byType进行自动装配。这样就可以防止service的bean使用byType时发现有多个相同类型的bean都可以注入到service中,出现冲突报错了
2.2、使用JavaConfig和注解来进行自动装配
下面我们使用JavaConfig和注解来进行自动装配
这里就需要说一下使用注解的时候,bean的命名规则:
- 在使用@Component、@Repository、@Service、@Controller等注解创建bean时,如果指定bean的name,则是指定的名称.
- 如果不指定bean的name,bean名称的默认规则是类名的首字母小写,如SysConfig - sysConfig,Tools - tools。
- 如果类名前两个或以上个字母都是大写,那么bean名称与类名一样,如RBACUserLog - RBACUserLog,RBACUser - RBACUser,RBACRole - RBACRole。
这里和使用XML时的命名规则有些不同,使用XML来配置spring容器的时候bean标签写上id,然后如果不指定name的话,那么bean的name默认和id一样。
这里插一条,spring中bean的命名规则是通过BeanNameGenerator这个接口来实现的,我们可以通过实现这个接口来完成自己的命名规则,编写好命名规则类,然后告诉在告诉spring要扫描位置的同时,指定命名规则类就可以了。配置方法如下:
MyNameGenerator是我们自己编写的命名规则类
javaConfig:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
XML:
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
javaConfig类:
package priv.cy.dao;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("priv")
public class Spring {
}
service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class IndexService {
@Autowired
private IndexDao indexDaoImpl;
public void service() {
indexDaoImpl.test();
}
public void setDao(IndexDao dao) {
this.indexDaoImpl = dao;
}
}
dao:
import org.springframework.stereotype.Repository;
@Repository
public class IndexDaoImpl implements IndexDao {
@Override
public void test() {
System.out.println("Impl 0");
}
}
import org.springframework.stereotype.Repository;
@Repository
public class IndexDaoImpl1 implements IndexDao {
@Override
public void test() {
System.out.println("Impl 1");
}
}
上面这些代码通过注解将类交给spring容器去管理,默认的bean name就是他们的首字母小写的类名
先讲一下@Autowired和@Resource的区别
- @Autowired默认先使用的是byType,byType使用的是属性的类型,如果byType会出现报错的话,再就会再使用byName,这里使用的name也是属性的属性名,用属性的属性名来去spring容器中匹配查找相同的bean name
- @Resource默认是使用的是byName,但是这里的byName和之前讲XML自动装配的byName不太一样,之前的byName都是通过setter方法名来匹配name的,但是@Resource使用的是属性名来匹配name的,和setter方法没有关系。@Resource可以指定使用byName还是byType。
以上两种自动装配的注解都不需要使用setter方法
2.2.1 @Autowired
例子:
@Service
public class IndexService {
@Autowired
private IndexDao indexDaoImpl;
public void service() {
indexDaoImpl.test();
}
public void setDao(IndexDao dao) {
this.indexDaoImpl = dao;
}
}
按道理说如果使用byType会出现报错,因为有两个IndexDao接口的实现类,但是它不会直接报错,@Autowired会再换成byName再试一次,这个时候的name使用的是属性名的name,也就是用这个indexDaoImpl去查找,如果byName也没有找到,就会报之前那个有两个bean的冲突错误了。
所以注解(@Autowired和@Resource)使用byName都是直接使用依赖的对象的属性名来进行查找的,和setter方法没有任何关系,比如上面的这个代码在spring容器中代理的IndexDaoImpl.class这个bean默认的name时indexDaoImpl,上面的代码中setter方法名时setDao,按之前的道理IndexService取到的依赖类的名字应该是dao才对,但是这里依旧能将bean注入,但是此时spring容器中IndexDaoImpl这个类的bean的name是indexDaoImpl(默认命名规则)。说明此时IndexService是用indexDaoImpl这个name来从spring容器中进行查找匹配的(也就是使用的private IndexDao indexDaoImpl 里面这个对象的名字来进行匹配的,如果此时吧这个对象的名字改成aaa,也就会报错出现问题了)
2.2.2 @Resource
使用@Resource的话如果不指定的话,就默认是byName,如果是默认byName,那么过程和上面的@Autowired一样,但是@Resource可以指定使用byName还是byType
指定byType:
@Service
public class IndexService {
@Resource(type = IndexDaoImpl1.class)
private IndexDao dao;
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
直接指定使用byType,并且使用IndexDaoImpl1.class这个类,这样不管怎么样service这个类注入的已经是IndexDaoImpl1这个实现类
也可以指定byName
@Service
public class IndexService {
@Resource(name = "indexDaoImpl1")
private IndexDao dao;
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
这样就通过bean的name来进行查找,查找spring容器中bean的name是indexDaoImpl1的这个bean,将其注入给IndexService对象
三、spring懒加载
bean在调用的时候才会被加载
使用懒加载的前提是这个bean的作用域必须是单例的。
3.1 使用XML设置:
用lazy-init。告诉spring容器是否以懒加载的方式创造对象。用的时候才加载构造,不用的时候不加载
取值:true(懒,真正调用到的时候再加载)、false(非懒,已启动spring容器就创建对象)、default(懒)
<bean id="dao" class="cn.java.ioc.MW" lazy-init="default" ></bean> |
3.2 使用注解设置:
@Lazy
@Bean("dao")
public Dao dao() {
System.out.println("dao");
return new Dao();
}
3.3 懒加载与非懒加载的优缺点:
- 懒加载:对象使用的时候才去创建,节省资源,但是不利于提前发现错误。
- 非懒加载:容器启动的时候立刻创建对象。消耗资源。利于提前发现错误。
当scope=“prototype” (多例)时,默认以懒加载的方式产生对象。
当scope=“singleton” (单例)时,默认以非懒加载的方式产生对象。
四、spring作用域
spring容器中的bean可以分为五个范围。所有范围的名称都是说明的,
1.singleton:这种bean范围是默认的,这种范围确保不管接受到多个请求,每个容器中有一个bean的实例,单例模式由bean factory自身来维护。
2.prototype:原先通过范围与单例范围相反,为每一个bean请求提供一个实例。
3.request:在请求bean范围内会为每一个来自客户端的网络请求创建一个实例,在请求完成之后,bean会失效并被垃圾回收器回收。该属性仅对HTT请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。
4.session:与请求范围类似,确保每个session中的bean的实例在session过期后bean会随之消失。该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。Session
5.global-session:global-session和portlet公用全局存储变量的话,那么这全局变量需要存储在global-session中。该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
下面重点讨论singleton、prototyp作用域,request、session和global-session类作用域放到Spring MVC章节讨论,这里不再做详细讲述。
可以通过@Scope(" ")注解来设置
spring容器bean的作用域默认是单例,非懒加载的
4.1 Singleton Beans with Prototype-bean Dependencies
意思是在Singleton 当中引用了一个Prototype的bean的时候引发的问题,依赖的这个对象的原型作用域失效,也变成了单例了
其实这个问题就可以总结为一个类依赖另一个类,但是这两个的作用域是不同的,就会出现问题。
比如下面这一套代码:
javaConfig:
@Configuration
@ComponentScan("priv")
public class Spring {
}
service:
@Service
@Scope("singleton")
public class IndexService {
@Autowired
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
System.out.println("dao" + dao.hashCode());
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
DAO:
@Repository
@Scope("prototype")
public class IndexDaoImpl implements IndexDao {
@Override
public void test() {
System.out.println("Impl 0");
}
}
Test:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Spring.class);
IndexService service = (IndexService) annotationConfigApplicationContext.getBean("indexService");
IndexService service1 = (IndexService) annotationConfigApplicationContext.getBean("indexService");
IndexService service2 = (IndexService) annotationConfigApplicationContext.getBean("indexService");
service.service();
service1.service();
service2.service();
}
}
结果:
可以发现service是单例的,而它依赖的dao是原型的,但是最终的结果确实dao的原型作用域失效,这就是作用域不同进行依赖错产生的问题。因为service是单例的,他在spring容器中只会创建一次,而它依赖的对象是随着它的加载而加载的,所以service只加载一次,即使dao是原型的,也只会被加载一次。
我们的解决办法只能是在service调用dao的时候想办法重新获取一次新的dao对象,这样既能保证service对象是单例,也能保证每一次调用dao都是新产生的对象。
如果这个对象要依赖的类和自己的作用域不同,spring提供了两种解决方法:
4.1.1 ApplicationContextAware接口
一种是让这个对象实现ApplicationContextAware接口,这个还需要覆写一个set方法,和引入ApplicationContext对象
这样的解决办法就是在service类每一次需要使用依赖的这个dao时,就通过ApplicationContext对象重新取一次这个bean,这样dao就可以依旧保持原型的作用域,每一次使用它就会是一个新bean,而service对象一直保持单例不变。
javaConfig:
@Configuration
@ComponentScan("priv")
public class Spring {
}
service:
@Service
@Scope("singleton")
public class IndexService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired //加不加这个注解都无所谓了,反正最后使用的也不是这个dao对象 用ApplicationContext这个Autowired可加可不加
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
//每次调用都使用applicationContext对象重新获取IndexDaoImpl的bean
System.out.println("dao" + applicationContext.getBean("indexDaoImpl").hashCode());
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
DAO:
@Repository
@Scope("prototype")
public class IndexDaoImpl implements IndexDao {
@Override
public void test() {
System.out.println("Impl 0");
}
}
Test:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Spring.class);
IndexService service = (IndexService) annotationConfigApplicationContext.getBean("indexService");
IndexService service1 = (IndexService) annotationConfigApplicationContext.getBean("indexService");
IndexService service2 = (IndexService) annotationConfigApplicationContext.getBean("indexService");
service.service();
service1.service();
service2.service();
}
}
结果:
但是这种实现spring接口的方法,会大大增加项目的耦合度,侵入性太强,使代码严重spring的API,spring的设计理念之一就是非侵入性,所以spring还提供了Lookup Method Injection这个方法来解决Singleton Beans with Prototype-bean Dependencies问题
4.1.2 Lookup Method Injection
使用@Lookup注解,就可以不需要依赖spring的API了
其他代码和之前还是一样,只需要修改service代码:
@Service
@Scope("singleton")
public class IndexService {
// @Autowired 使用Lookup必须要将Autowired去掉,否则会出错
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
System.out.println("dao" + getDao().hashCode());
}
@Lookup
public IndexDao getDao() {
return null;
}
}
这里只需要添加一个get方法,用来每一次调用时获取全新的dao对象,方法名不重要,自定义就行,返回值就是你要获取的对象类型,只需要在这个方法上面添加@Lookup注解,这个方法内部什么逻辑代码都不需要写,直接写一个return null就可以了。在每次获取dao对象的位置调用这个方法就可以了。还有一点就是因为是通过这个方法来获取dao的bean了,所以也就不需要@Autowired这个注解进行注入了,要将其删除,不删除的话会出现报错。
@Lookup是通过方法的返回值类型,根据这个类型从spring容器中查找这个bean,如果找到了就直接通过这个方法返回回来。但是这里就有可能出现前面讲的多个相同类型的bean冲突问题,如果IndexDao是一个接口,而这个接口有多个实现类被委托给spring容器进行管理,那么这个时候如果还通过类型来查找就找到多个bean,进而出现错误
Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'priv.cy.dao.IndexDao' available: expected single matching bean but found 2: indexDaoImpl,indexDaoImpl1
解决这个问题就还需要通过byName来进行查找,方法就是@Lookup("indexDaoImpl"),直接在这个注解指定bean的name,然后他就会通过这个name去spring容器中查找这个对应的bean,这样就不会起冲突了
@Service
@Scope("singleton")
public class IndexService {
// @Autowired
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
System.out.println("dao" + getDao().hashCode());
}
@Lookup("indexDaoImpl")
public IndexDao getDao() {
return null;
}
}
五、spring生命周期和回调
就相当于finally
生命周期回调有三种:
- Initialization Callbacks(初始化回调)
- Destruction Callbacks(销毁回调)
- Default Initialization and Destroy Methods(默认初始化和销毁方法)
5.1 Initialization Callbacks(初始化回调)
在初始化的时候回调,初始化是在实例化方法执行之后,需要实现InitializingBean接口,然后覆写afterPropertiesSet()方法,这样每次初始化的时候就会回调afterPropertiesSet方法
代码:
@Repository
public class IndexDaoImpl1 implements IndexDao , InitializingBean {
public IndexDaoImpl1() {
System.out.println("Constructor");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init");
}
}
测试类:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Appconfig.class);
}
}
执行结果:
5.2 Destruction Callbacks(销毁回调)
当容器被销毁的时候,会进行回调。需要实现DisposableBean这个接口,然后覆写afterPropertiesSet()方法,这样容器被销毁的时候就会回调afterPropertiesSet方法
代码:
@Repository
public class IndexDaoImpl1 implements IndexDao , InitializingBean , DisposableBean {
public IndexDaoImpl1() {
System.out.println("Constructor");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy");
}
}
5.3 Default Initialization and Destroy Methods(默认初始化和销毁方法)
可以自定义回调方法,不需要去实现接口,不依赖spring的API,降低了系统的耦合,非侵入性。
可以指定一个初始化方法和销毁方法来实现方法回调
有两种设置方式,XML和注解
5.3.1 XML(init-method和destroy-method):
dao:
@Repository
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor");
}
public void init() {
System.out.println("init");
}
public void destory() {
System.out.println("destory");
}
}
test:
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext
= new ClassPathXmlApplicationContext("classpath:spring.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-init-method="init"
default-destroy-method="destory" >
<bean id="dao" class="priv.cy.dao.IndexDaoImpl1"></bean>
</beans>
就是直接在需要添加回调方法的类中编写自己想要的方法就行了,方法名自定义,只需要在XML配置文件中指定初始化回调方法和销毁回调方法的方法名就可以了
5.3 2 注解(@PostConstruct和@PreDestory):
也可以直接使用注解,不需要用XML配置
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
使用以上两个注解直接用在回调方法上,也能实现自定义的回调方法
例如:
@Repository
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor");
}
@PostConstruct
public void init() {
System.out.println("init");
}
@PreDestory
public void destory() {
System.out.println("destory");
}
}
懒加载
只有在调用的时候才会被加载
使用@Lazy标签
例如将上面的回调方法代码加上@Lazy标签:
@Repository
@Lazy
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor");
}
@PostConstruct
public void init() {
System.out.println("init");
}
public void destory() {
System.out.println("destory");
}
}
Test:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext(Appconfig.class);
//IndexDaoImpl1 dao = (IndexDaoImpl1) annotationConfigApplicationContext.getBean(IndexDao.class);
}
}
执行结果:
这个代码的构造方法和回调方法都不会执行了,说明只有当调用这个bean的时候这个bean在容器中才会被实例化
六、其他
6.1 @ComponentScan标签的其他用法
它不光可以指定要spring要扫描注解的范围,还可以指定spring不扫描的位置
使用注解的时候如果向注解中传入的参数只有一个,也就是value,那么就可以不用写value="",直接在括号里面写上参数就可以。但是如果传入的参数大于1,那么就必须表明每一个参数要传给谁
例如:
@Configuration
@ComponentScan(value = "priv", excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX,pattern = "priv.cy.service.*")})
public class Appconfig {
}
上面的注解意思就是扫描priv下面的所有包,但是priv.cy.service包下的内容除外,如果使用注解来装配bean的话,priv.cy.service包下的bean就会全部装配失败。type表示的是后面pattern表达式的类型,像REGEX类型的表达式后面必须要带*,其他类型的相关简介请看官方文档https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-scanning-filters
还有就是可以可以指定必须要扫描哪些位置,使用includeFilters 参数
例如:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
它的应用场景比如我对A包里面除了其中一个子包a需要扫描之外,其余的都不需要扫描,就需要用到includeFilters。
实现以上功能还可以使用XML配置
<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>
6.2 byType多个同类型的Bean冲突解决办法
再来说一下之前讲过使用byType会出现多个相同类型的bean出现冲突无法自动装配的解决方法,有两种
6.2.1、@Primary
这个是主数据源标签,出现冲突的情况下优先使用这个bean进行装配
例子:
service:
@Service
public class IndexService {
@Autowired
private IndexDao indexDao;
}
dao:
@Repository
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor 1");
}
@PostConstruct
public void init() {
System.out.println("init 1");
}
public void destory() {
System.out.println("destory 1");
}
}
@Repository
public class IndexDaoImpl2 implements IndexDao {
public IndexDaoImpl2() {
System.out.println("Constructor 2");
}
@PostConstruct
public void init() {
System.out.println("init 2");
}
public void destory() {
System.out.println("destory 2");
}
}
不使用@Primary标签,报错
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'priv.cy.dao.IndexDao' available: expected single matching bean but found 2: indexDaoImpl1,indexDaoImpl2
对IndexDaoImpl1使用@Primary标签
@Repository
@Primary
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor 1");
}
@PostConstruct
public void init() {
System.out.println("init 1");
}
public void destory() {
System.out.println("destory 1");
}
}
就可以正常注入了,会优先将IndexDaoImpl1注入到IndexService对象中
6.2.2、第二种方法使用限定注解@Qualifier
@Qualifier这个注解可以直接指定要注入哪个bean,它通过name去指定bean
代码:
@Service
public class IndexService {
@Qualifier("indexDaoImpl1")
private IndexDao indexDao;
}
直接指定使用indexDaoImpl1这个name的bean去注入
Java自带的一些注解可以替代spring的注解来完成相同的功能,比如@Resource,这个就属于jdk的注解,但是也能实现自动装配的功能。
6.3 加速spring扫描类的速度
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.4.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
添加了这个之后spring项目在编译的时候就会将所有要扫描的类添加到一个索引当中,以后再扫描就不需要使用类路径一个个扫描了,而是直接使用索引进行扫描,这样大大加快了扫描速度。
6.4 @Bean注解
@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上),也就是将方法的返回值所谓一个bean交给spring容器管理
比如一些必须要通过一个方法返回出来的对象想把它交给spring容器去管理,我们就需要用这个办法,在这个方法上面添加@Bean标签,这个方法的返回值就交给spring容器管理了。
配置类:
@Configuration
public class SpringConfiguration {
/**
* 在JavaConfig配置类中使用@Bean标签来完成bean的注册,效果就和在XML中配置<bean>标签是一样的
*/
@Bean
public HollowController hollowController(){
return new HollowController();
}
}
6.5 @Profile标签
这个标签用来切换环境
dao:
@Repository
@Profile("dao1")
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor 1");
}
@PostConstruct
public void init() {
System.out.println("init 1");
}
public void destory() {
System.out.println("destory 1");
}
}
@Repository
@Profile("dao2")
public class IndexDaoImpl2 implements IndexDao {
public IndexDaoImpl2() {
System.out.println("Constructor 2");
}
@PostConstruct
public void init() {
System.out.println("init 2");
}
public void destory() {
System.out.println("destory 2");
}
}
给两个dao设置了对应的环境名
测试类:
public class Test {
public static void main(String[] args) {
// 创建AnnotationConfigApplicationContext类的时候先不指定配置类
AnnotationConfigApplicationContext annotationConfigApplicationContext
= new AnnotationConfigApplicationContext();
// 获取annotationConfigApplicationContext对象的环境并对其进行修改
annotationConfigApplicationContext.getEnvironment().setActiveProfiles("dao2");
// 指定完环境之后再注册配置类
annotationConfigApplicationContext.register(Appconfig.class);
// 注册完成后刷新annotationConfigApplicationContext
annotationConfigApplicationContext.refresh();
// 再取bean就是在dao2这个环境下获取了
annotationConfigApplicationContext.getBean(IndexService.class);
}
}
此时service注入的就是dao2环境下的bean了
这个注解的用途就是可以设置很多种环境,可以通过切换不同的环境来使不同的bean交给spring容器管理,或者设置多个配置类环境,可以根据需要任意切换。