IOC的底层原理
Spring 提供了IOC容器实现的两个接口:
- BeanFactory:IOC容器的基本实现,主要是内部调用使用的。
用此接口时,在加载配置文件的时候,不会创建对象,只有在使用的时候才会创建对象 - ApplicationContext:是BeanFactory的子类。
用此接口时,在加载配置文件的时候就会创建对象
在开发的时候多用ApplicationContext。
IOC的Bean管理
基于xml方式
- 创建一个xml配置文件,每创建一个bean都要创建一个相对应的bean标签
其中
- id为唯一标识,与下面的getBean()方法里的形保持一致
- class为bean所在的全类名
- 创建对象的时候,默认是使用无参的构造器,所以当对应的实体类中没有无参构造器时会报错。
<bean id="user" class="com.study.spring5_1.bean.User">
</bean>
ApplicationContext app = new ClassPathXmlApplicationContext("bean1_user.xml");
User user = (User)app.getBean("user");
- 属性值的注入:
- 使用set方法注入
在xml里使用property标签。使用这个标签时对应的类的属性需要有set方法
<bean id="user" class="com.study.spring5_1.bean.User">
<property name="name" value="tom"></property>
</bean>
- 使用有参的构造器注入
需要先声明一个有参构造器。如果这里用了有参构造器,那么即使没有声明无参构造器也不会报错了
<bean id="user" class="com.study.spring5_1.bean.User">
<constructor-arg name="name" value="tom"></constructor-arg>
</bean>
- 使用p名称空间注入
先添加p名称空间,然后就能调用p:属性名,此种方式也得基于set方法
<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="user" class="com.study.spring5_1.bean.User" p:name="tom">
</bean>
</beans>
- 特殊值的注入
空值
<bean id="user" class="com.study.spring5_1.bean.User">
<property name="address">
<null></null>
</property>
</bean>
特殊符号
使用!CDATA方式可以实现,格式为<![CDATA[属性值]]>
<bean id="user" class="com.study.spring5_1.bean.User">
<property name="address">
<value><![CDATA[<><><><>]]></value>
</property>
</bean>
- 外部bean的注入
例子中UserService类有类型为UserDao的属性,因此在注入的时候使用ref,ref关联的是bean的id
<bean id="userService" class="com.study.spring5_1.service.UserService">
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.study.spring5_1.Dao.UserDaoImpl"></bean>
- 内部bean的注入
当bean与bean之间有一对多的关系时,比如user的city属性的类型是City类,此时可以用内部bean注入的方式。
<bean id="user" class="com.study.spring5_1.bean.User">
<property name="address" value="123123"></property>
<property name="city">
<bean id="city" class="com.study.spring5_1.bean.City">
<property name="name" value="dalian"></property>
</bean>
</property>
</bean>
- 级联赋值
第一种写法(参考外部bean的注入方式)
第二种写法
首先得现在user的 bean里把city的属性写出来,使用city.name的方式给City类的属性进行赋值dadada,此时city的bean里写的value就无效了,最后user输出的是dadada而不是123.
需要注意的是使用city.name时需要在user里有getCity()方法,并且
< bean id=“city” class=“com.study.spring5_1.bean.City”/>
和< property name=“city” ref=“city”/>一样都不能少才可以。
<bean id="user" class="com.study.spring5_1.bean.User">
<property name="address" value="123123"></property>
<property name="city" ref="city"></property>
<property name="city.name" value="dadada"></property>
</bean>
<bean id="city" class="com.study.spring5_1.bean.City">
<property name="name" value="123"></property>
</bean>
- 集合类型的注入
当map类型的key都相同时,输出的是最后一次的值。
如下例,当key都是liaoning时输出的value是shanyang。
<bean id="emp" class="com.study.spring5_1.bean.Employee">
<property name="name">
<array>
<value>tom</value>
<value>lucy</value>
</array>
</property>
<property name="gender">
<list>
<value>male</value>
<value>female</value>
</list>
</property>
<property name="city">
<map>
<entry key="liaoning1" value="dalian"></entry>
<entry key="liaoning" value="shenyang"></entry>
</map>
</property>
<property name="phone">
<set>
<value>121212</value>
<value>323232</value>
</set>
</property>
<!-- 如果是list<User>类型的时候,可以用ref注入 bean为对应user bean的id-->
<property name="users">
<list>
<ref bean="user"></ref>
</list>
</property>
</bean>
如果多个bean中都有相同的list,可以将list提取出来
首先要加命名空间util
然后使用util:list标签声明一组list
在bean中有list类型的属性通过ref引用,所有的bean都可以引用这个公共的list
<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"
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/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="publicList">
<value>11111</value>
<value>22222</value>
</util:list>
<bean id="emp" class="com.study.spring5_1.bean.Employee">
<property name="gender" ref="publicList"></property>
</bean>
- FactoryBean
与普通的bean不同的是:普通bean在xml里注册的时候class的类与返回的类保持一致,但是FactoryBean却可以不一致,
如下例:
先创建一个类,实现FactoryBean接口,先给FactoryBean加泛型,此时getObject方法的返回值类型也得和泛型的类型保持一致。
返回的什么类是由getObject方法决定的,里面return什么类最后从配置文件里得到的就是什么类。
<bean id="myBean" class="com.study.spring5_1.bean.MyBean"></bean>
public class MyBean implements FactoryBean<User> {
//这个getObject()方法决定了返回的类型
@Override
public User getObject() throws Exception {
User user = new User();
user.setAddress("123123131");
return user;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
@Test
public void testFactoryBean(){
ApplicationContext app = new ClassPathXmlApplicationContext("bean1_user.xml");
//因为返回的是User类,就得用User去接收值,否则会报错。
User user = app.getBean("myBean", User.class);
System.out.println(user);
}
- bean的作用域
scope为singleton时,bean是单实例,spring默认是单实例
scope为prototype时,bean是多实例
当时prototype时,不会在引用bean的时候创建实例,而是在getBean的时候才创建实例
<bean id="user" class="com.study.spring5_1.bean.User" scope="prototype">
<property name="address" value="123123"></property>
<property name="city" ref="city"></property>
<property name="city.name" value="dadada"></property>
</bean>
举例
@Test
public void testUser(){
ApplicationContext app = new ClassPathXmlApplicationContext("bean1_user.xml");
User user = (User)app.getBean("user");
User user1 = (User)app.getBean("user");
System.out.println("user : " + user.toString() + "---" + user);
System.out.println("user1 : " + user1.toString() + "---" + user1);
user.setName("xiaoming");
System.out.println("user : " + user.toString() + "---" + user);
System.out.println("user1 : " + user1.toString() + "---" + user1);
}
单实例的时候:
注意到,单实例的时候,因为地址是同一个,所以当给user的属性赋值时,user1里的属性也跟着变化。其实是因为他们两个在jvm里指向的是同一个地址,任何一方对属性操作,所有人都会跟着变化
user : User{name='null', address='123123', city=City{name='dadada'}}---User{name='null', address='123123', city=City{name='dadada'}}
user1 : User{name='null', address='123123', city=City{name='dadada'}}---User{name='null', address='123123', city=City{name='dadada'}}
user : User{name='xiaoming', address='123123', city=City{name='dadada'}}---User{name='xiaoming', address='123123', city=City{name='dadada'}}
user1 : User{name='xiaoming', address='123123', city=City{name='dadada'}}---User{name='xiaoming', address='123123', city=City{name='dadada'}}
多实例的时候
此时对user属性赋值不会影响user1的属性值
user : User{name='null', address='123123', city=City{name='dadada'}}---User{name='null', address='123123', city=City{name='dadada'}}
user1 : User{name='null', address='123123', city=City{name='dadada'}}---User{name='null', address='123123', city=City{name='dadada'}}
user : User{name='xiaoming', address='123123', city=City{name='dadada'}}---User{name='xiaoming', address='123123', city=City{name='dadada'}}
user1 : User{name='null', address='123123', city=City{name='dadada'}}---User{name='null', address='123123', city=City{name='dadada'}}
- bean的生命周期
1. 调用无参构造器
2. 调用属性的set方法
前置处理器
3. 调用初始化方法
后置处理器
4. 创建bean实例 //执行getBean()方法的时候
5. 调用销毁方法
初始化方法和销毁方法需要在bean里配置
<bean id="user" class="com.study.spring5_1.bean.User" scope="prototype" init-method="initMethod" destroy-method="destoryMethod">
<property name="address" value="123123"></property>
</bean>
<bean id="processor" class="com.study.spring5_1.processer.myProcesser"></bean>
处理器的创建需要实现BeanPostProcessor接口,创建完之后还得在xml里创建bean进行注入才会生效。
前后置处理器在init方法前后执行
public class myProcesser implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("前置处理器");
return null;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器");
return null;
}
}
- 属性的自动注入
使用autowire可以完成属性自动注入。
因为user里由City类型的属性,先声明City的bean。
autowire有两种方式:
ByName:user的属性名和city的bean id要一致才能匹配上
ByType:根据类型自动注入,因为是City类型的,找xml里这个类型的bean进行关联。但是当有多个相同类型的bean时因为不确定关联哪个,所以会报错
<bean id="user" class="com.study.spring5_1.bean.User" scope="prototype" autowire="byName">
</bean>
<bean id="city" class="com.study.spring5_1.bean.City">
<property name="name" value="123"></property>
</bean>
因为在xml里没有给user进行city属性的赋值,但是因为有autowire,所以city属性自动关联到了id为city的bean的值
User{name='null', address='null', city=City{name='123'}}
- 引入外部属性
首先要引入context命名空间
然后使用context:property-placeholder标签引入外部的配置文件
(可能是druid才能用prop,但是自己定义的类肯定用不了prop)
<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:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="jdbctemplate" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"></property>
<property name="url" value="${prop.url}"></property>
<property name="userName" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
基于注解方式
- 准备工作:
- 引入spring-aop-5.3.8.jar包
- 在配置文件里引入context命名空间,然后配置扫描路径
<?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.study.spring5_1"/>
</beans>
组件扫描里的配置:
可以指定包里的哪些东西扫描,哪些东西不扫描
指定哪些东西扫描:< context:include-filter >
指定哪些东西不扫描:< context:exclude-filter >
<context:component-scan base-package="com.study.spring5_1" use-default-filters="false">
<!-- 表示只扫描被@Component注解标注的类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
<!-- 表示除了被@Component注解标注的类,剩下的都扫描 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
- 新建一个类,用注解的方式注入
需要注意的是:value里的值相当于bean里的id,如果不指定的话,默认是类名的第一个字母小写的值
@Component(value = "cityservice")
public class CityService {
public void test(){
System.out.println("cityService test method");
}
}
测试
@Test
public void testAnnotion(){
ApplicationContext app = new ClassPathXmlApplicationContext("bean_user_annotion.xml");
CityService city = app.getBean("cityservice", CityService.class);
city.test();
}
-
属性的注入(使用注解时不需要添加set方法)
@Autowired
@Qualifier
@Resource(name = “”)
@Value@Autowired:根据类型去注入
@Qualifier:根据name去注入,使用此注解的时候需要结合@Autowired一起使用,
使用场景为:当一个接口有多个实现类的时候,根据类型不知道注入哪一个实现类,此时需要指定实现类的id(默认是类型第一个字母小写)
例子:
@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
public void test(){
userDao.add();
System.out.println("cityService test method");
}
@Resource(name = “”)是autowired和qualifier的合体,既可以根据属性也可以根据name注入,当不写name的时候就是根据属性注入
@Value:对普通属性的注入
@Value("123")
private String name;
- 使用全注解开发,不需要配置文件,用配置类代替
在类名上先使用@Configuration注解
@ComponentScan是配置扫描路径
@Configuration
@ComponentScan(basePackages = {"com.study.spring5_1"})
public class CommonConfig {
}
使用配置类之后,ApplicationContext 的获取方式变成了AnnotationConfigApplicationContext(CommonConfig.class)
@Test
public void testConfig(){
ApplicationContext app = new AnnotationConfigApplicationContext(CommonConfig.class);
CityService city = app.getBean("cityservice", CityService.class);
city.test();
}