IOC
- IOC容器概述
ApplicationContext是Spring IoC容器实现的代表,它负责实例化,配置和组装Bean。容器通过读取配置元数据获取有关实例化、配置和组装哪 些对象的说明 。配置元数据可以使用XML、Java注解或Java代码来呈现。它允许你处理应用程序的对象与其他对象之间的互相依赖关系。 - 容器的实例化
对象在Spring容器创建完成的时候就已经创建完成,不是需要用的时候才创建 - 容器的使用
ApplicationContext是能够创建bean定义以及处理相互依赖关系的高级工厂接口,使用方法TgetBean(String name, Class<T> requiredType)
获取容器实例// 创建spring上下文 加载所有的bean ApplicationContext context = new ClassPathXmlApplicationContext("service s.xml", "daos.xml"); // 获取bean PetStoreService service = context.getBean("petStore", PetStoreService.cla ss); // 使用bean的对象 List<String> userList = service.getUsernameList();
IOC基于XML的使用
编写XML配置文件
Bean
在Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为bean。bean是一个由Spring IoC容器实例化、组装和管理的对象。
Bean的命名
在xml中使用id或name属性来指定标识符。id是唯一的,若同时使用了name则视为别名,name也可以通过 (空格)、,(逗号)、;(分号)分隔来设置多个别名。若没有指定任何标识符,则Spring会自动生成一个唯一的标识符。
<bean class="zt.demo.beans.User" id="user" name="user2 user3,user4;user5">
</bean>
也可以使用alias设置别名
<alias name="user" alias="demo"></alias>
实例化Bean
-
使用构造器实例化 (默认,无法干预实例化过程)
- 空构造器(指定的类必须存在无参构造函数)
<bean class="zt.demo.beans.User" id="user"/>
- 带参构造器(指定的类必须存在有参构造函数)
- 基于name属性设置构造函数参数
- 可以只有value属性
- 如果省略name属性 一定注意参数顺序
- 如果参数顺序错乱
可以使用name,
还可以使用index:设置参数的下标 从0开始
还可以使用type: 在错乱的参数类型一致的情况下不能使用
public class User { private Integer id; private String username; private String realname; 、、、(set、get方法) public User(Integer id, String username, String realname) { this.id = id; this.username = username; this.realname = realname; } }
<bean class="zt.demo.beans.User" id="user"/> <constructor-arg name="id" value="1"/> <constructor-arg index="1" value="z"/> <constructor-arg type="java.lang.String" value="x"/> </bean>
-
使用静态工厂方法实例化
在类中添加静态工厂方法,可以创建自身或者子类。通过factory-method来调用方法,同时静态工厂也能指定参数。public static Person createPersonFactory(){ Child child = new Child(); child.setName("Son"); return child; } public static Person createPersonFactory1(String name){ Person person = new Person(); person.setName(name); return person; }
<bean class="zt.demo.beans.Person" id="person" factory-method="createPersonFactory" > </bean> <bean class="cn.tulingxueyuan.beans.Person" id="person" factory-method="createPersonFactory1" > <constructor-arg name="name" value="JUGG"/> </bean>
-
使用实例工厂方法实例化
需要额外的指定一个工厂bean,使用时class需要指定工厂类,也可创建自身或子类。同时要指定工厂方法作为factory-bean。<!-- 先注入工厂方法类 --> <bean class="zt.demo.beans.PersonFacotry" id="personFacotry"></bean> <bean class="zt.demo.beans.beans.Person" id="person" factory-bean="personFacotry" factory-method="createPersonFacotryMethod" > </bean>
// 工厂方法类 public class PersonFacotry { // public Person createPersonFacotryMethod() { // Child child = new Child(); // child.setName("儿子"); // return child; // } public Person createPersonFacotryMethod() { Person person = new Person(); person.setName("0.0"); return person; } }
Bean的作用域
- singleton单例的作用域(默认就是单例)
- 不管使用多少次,同一个id都只会加载一次bean
- 可以节省内存消耗。
- 单例模式下容易遇到线程安全问题-数据脏读,当一个共享的一个bean的内容,在多个线程共同读写的情况下容易出现数据的覆盖。
- protorype原型(多例)的作用域
- 每一次使用时,都会加载一次bean创建一个新的出来。
- 通过
scope="protorype"
设置
- 下列几种作用域需基于web的spring Application Context才可用
- request
每一个请求创建一个新的bean - session
每一个对话创建一个bean - application
每一个应用创建一个bean - websocket
每一个长链接创建一个bean
- request
Bean的生命周期回调
ioc来控制bean的生命周期
- 实现Initializing Bean和Disposable Bean回调接口
public class Person implements InitializingBean,DisposableBean { ... // 实例化 public void afterPropertiesSet() throws Exception { System.out.println("实例化Person1"); } // 销毁 public void destroy() throws Exception { System.out.println("销毁Person1"); } }
- 自定义init()和destroy()方法
public class Person implements InitializingBean,DisposableBean { ... //实例化 public void initByConfig() throws Exception { System.out.println("实例化Person2"); } // 销毁 public void destroyByConfig() throws Exception { System.out.println("销毁Person2"); } } <bean class="zt.demo.beans.Person" id="person" init-method="initByConfig" destroy-method="destroyByConfig"></bean>
- 什么时候会销毁
在spring容器关闭的时候close()
或者使用ConfigurableApplicationContext.registerShutdownHook
方法优雅的关闭。@Test public void test() { Person person = ioc.getBean("person", Person.class); System.out.println(person); // ioc.close(); ioc.registerShutdownHook(); }
Bean定义的继承
一个bean继承另一个bean,可以使用parent
属性指定父类bean
如果想让父类bean不能被实例化可以加上abstract="true"
<bean class="zt.demo.beans.User" id="user2">
<property name="idxx" value="1"/>
<property name="username" value="RTZ"/>
<property name="realname" value="dove"/>
</bean>
<bean class="zt.demo.beans.User" id="user" parent="user2">
<property name="username" value="EG"/>
</bean>
依赖注入
基于setter方法的依赖注入
- 因为基于setter方法,所以首先属性要有set方法
- name是根据set方法的名字来的,比如方法名字是: setxx ‐> name=“xx”
public class Person { private Integer id; private String name; public void setId(Integer id) { this.id = id; } public void setNameggg(String name) { this.name = name; } }
<bean class="zt.demo.beans.Person" id="person"> <property name="id" value="1"></property> <property name="nameggg" value="ggg"></property> </bean>
基于构造函数依赖注入
- 将会调用自定义构造函数来实例化对象,就不会调用默认的无参构造函数
- name是根据构造函数的参数名来的, 比如:User(String idxx) ‐> name=“idxx”
- name属性可以省,可以只有value属性,但是要注意参数的位置
- 参数顺序错乱时,可以使用 name 或者 index 或者 type
- index 是参数的下标 从0开始
- type 在错乱的参数类型一致的情况下不能使用
public class User { private Integer id; private String username; private String realname; public User(Integer id, String username, String realname) { this.id = id; this.username = username; this.realname = realname; } }
<bean class="zt.demo.beans.User" id="user7"> <!-- <constructor-arg name="id" value="1"/> --> <!-- <constructor-arg name="username" value="ame"></constructor-arg> --> <!-- <constructor-arg name="realname" value="萧瑟"></constructor-arg> --> <!-- <constructor-arg index="1" value="maybe"/> --> <!-- <constructor-arg type="java.lang.String" value="土堆"/> --> <constructor-arg value="2"/> <constructor-arg value="dc"/> <constructor-arg value="灿"/> </bean>
复杂数据类型的依赖注入
<!-- <bean class="zt.demo.beans.Wife" id="wife"> -->
<!-- <property name="age" value="22"/> -->
<!-- <property name="name" value="kun"/> -->
<!-- </bean> -->
<!--复杂数据类型的依赖注入-->
<bean class="zt.demo.beans.Person" id="person">
<property name="id" value="1"/>
<!--设置null-->
<property name="name">
<null/>
</property>
<property name="gender" value=""/>
<!-- 引用外部Bean, ref为外部bean的ID-->
<!-- <property name="wife" ref="wife"/> -->
<!-- 使用内部bean 依赖注入其他bean -->
<property name="wife">
<bean class="zt.demo.beans.Wife">
<property name="age" value="33"/>
<property name="name" value="你干嘛"/>
</bean>
</property>
<!--list 注入:
如果泛型是基本数据类型<value>
如果泛型是bean <bean>-->
<property name="hobbies">
<list>
<value>唱</value>
<value>跳</value>
<value>rap</value>
<value>篮球</value>
</list>
</property>
<property name="others">
<list>
<bean class="zt.demo.beans.User">
<property name="idxx" value="1"/>
<property name="username" value="1"/>
</bean>
<bean class="zt.demo.beans.Child">
<property name="name" value="12"/>
<property name="id" value="12"/>
</bean>
</list>
</property>
<!--map 注入
如果value是基本数据类型<entry key="1" value="Java"></entry>
如果value是bean value-ref-->
<property name="course">
<map>
<entry key="1" value="练习"/>
<entry key="2" value="两年半"/>
</map>
</property>
</bean>
使用c命名空间简化基于构造函数的XML
需要有bean有构造函数
<bean class="zt.demo.beans.User" id="user" c:id="2" c:realname="kun" c:username="jinitaimei">
</bean>
使用p命名空间简化基于setter属性注入XML配置
p:按Alt+Enter 自动加上命名空间
设置基本数据类型 或者p:wife-ref
引用外部bean
如果有集合类型就不支持,需要额外配置<property>
<bean class="zt.demo.beans.Wife" id="wife2" p:age="44" p:name="44">
</bean>
<bean class="zt.demo.beans.Person" id="person" p:wife-ref="wife2" >
<property name="hobbies">
<list>
<value>唱</value>
<value>跳</value>
</list>
</property>
</bean>
depends on
depends on可以控制bean加载顺序
bean的加载顺序是先加载bean,然后加载方法
当一个bean想让另一个bean在它之前加载可以设置depends-on
<bean class="zt.demo.beans.User" id="user" depends-on="wife"></bean>
<bean class="zt.demo.beans.Wife" id="wife"></bean>
懒加载
不会在spring容器加载的时候加载该bean,而是在使用的时候加载。
当某个bean实例在使用的时候才加载。不用的时候就不会加载。
适用于某些bean不需要在ioc初始化的时候加载。
可以在<bean>
上也可以在<beans>
上加lazy-init = true
<bean class="zt.demo.beans.User" id="user" lazy-init="true"></bean>
自动注入
autowire自动注入的方式有三种
- byType
根据类型去自动匹配,当出现多个类型或者匹配到类型则会报错<bean class="zt.demo.beans.Person" id="person" autowire="byType"> </bean> <bean class="zt.demo.beans.Wife" id="wife2"> <property name="name" value="mina"></property> </bean>
- by Name
根据set方法的名字去自动匹配// 这里是setWife 所以优先匹配id是wife的 public void setWife(Wife wife) { this.wife = wife; } <bean class="zt.demo.beans.Person" id="person" autowire="byName"> </bean> <bean class="zt.demo.beans.Wife" id="wife2"> <property name="name" value="mina"></property> </bean> <bean class="zt.demo.beans.Wife" id="wife"> <property name="name" value="apex"></property> </bean>
- construct
根据构造器去匹配,优先会去根据构造器的参数名字去匹配,若名字没有匹配到,就会根据参数类型去匹配
会根据构造函数的参数,进行完整的匹配注入,当构造函数的参数是Person(Wife wife, User user),那么ioc容器中必须要有上述两个bean
名字没有匹配到会根据类型匹配,类型加入出现多个时,会注入失败,单不会报错。
当根据类型匹配到多个,可以使用 设置某个bean为主要bean:primary=“true”
或者设置不需要自动注入的bean:autowire-candidate=‘false’以此忽略注入。
正常匹配// 这里参数名是wife3 所以优先匹配id是wife3的 public Person(Wife wife3) { this.wife = wife3; } <bean class="zt.demo.beans.Person" id="person" autowire="constructor"> </bean> <bean class="zt.demo.beans.Wife" id="wife"> <property name="name" value="mina"></property> </bean> <bean class="zt.demo.beans.Wife" id="wife3"> <property name="name" value="apex"></property> </bean>
设置autowire-candidate="false"
<bean class="zt.demo.beans.Person" id="person" autowire="constructor"> </bean> <bean class="zt.demo.beans.Wife" id="wife2"> <property name="name" value="mina"></property> </bean> <!-- 设置autowire-candidate="false"后,忽略此处自动注入,然后通过名字匹配不到,按类型会匹配到wife2 --> <bean class="zt.demo.beans.Wife" id="wife3" autowire-candidate="false"> <property name="name" value="apex"></property> </bean>
设置primary="true"
<bean class="zt.demo.beans.Person" id="person" autowire="constructor"> </bean> <bean class="zt.demo.beans.Wife" id="wife2"> <property name="name" value="mina"></property> </bean> <!-- 设置primary="true"后,优先注入wife4 --> <bean class="zt.demo.beans.Wife" id="wife4" primary="true"> <property name="name" value="apex"></property> </bean>
引用第三方bean
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
这种类似的配置一般会放在一个配置文件中。
利用以下方法引入外部属性资源文件
<context:property-placeholder location=“db.property”></context:property-placeholder>
文件内容:
mysql.username=root
引入后可以直接:
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${mysql.username}"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
IOC基于注解的使用
基础注解
- @Controller 标记在控制层的类注册为Bean主键
- @Service 标记在业务逻辑层的类注册为Bean主键
- @Repository 标记在数据访问层的类注册为Bean主键
- @Component 标记在非三层的普通类注册为Bean主键
这些虽然不是一定要对应上对应的层,但是一般建议还是对应上,利于管理,增加可读性
使用注解将一个类注册为bean
- 设置扫描包context:component-scan
- 在对应的类名加上对应的注解
使用上面注解会自动将类名的首字母小写设置为Bean的名字,即
@Controller
public class UserController {
}
等价于
<bean class="zt.demo.controller.UserController" id="userController"></bean>
配置XML文件
IOC基于JavaConfig的使用
绑定Java与XML配置
新建一个类并在类上使用@Configuration注解来标记此类为SpirngIoc配置类,相当于上文的xml配置文件。
@ComponentScan注解用来标记需要扫描的包。相当于xml中的 <context:component>
@Configuration // 相当于 xml文件 <beans></beans>
@ComponentScan(basePackages = "demo")
public class IOCJavaConfig {
@Bean
public User user3(){
return new User();
}
}
@Bean注解
- 使用@Bean注解将一个实例注册为bean,且会自动将返回值作为bean的类型,将方法名作为bean的id。
- @Bean(name = “xx”) 设置bean的名字,会替换原名
- @Bean(name = {“dataSource”,“dd”}) 设置多个名字
@Bean /** * @Bean(name = "xx") 可以用此方法设置bean的名字(替换) * @Bean(name = {"dataSource","dd"}) 也可以设置多个名字 */ public Role role() { return new Role(); }
- @Bean(initMethod = “”, destroyMethod = “”) 用此方法设置生命周期回调函数,相当于xml中的
<bean class="xx" id="xx" init-method="xxx" destroy-method="xxx"></bean>
。(销毁回调函数需要在单例模式下,关闭IOC的时候会出现)
// 方法 @Bean(initMethod = "initPerson", destroyMethod = "desPerson") public Person person() { return new Person(); }
// 对象类 @Data @Getter @Setter @Component @Scope("singleton") public class Person { @Value("zt") private String name; public Person (){ System.out.println("Person 加载"); } private void initPerson() { System.out.println("init Person"); } private void desPerson() { System.out.println("destory Person"); } }
- 依赖其他bean的方法
- 当需要自动依赖外部bean时:直接在方法里面写上需要依赖的参数即可,不需要使用@Autowired注解
@Bean public Role role(User user) { System.out.println(user.getName()); return new Role(); }
- 当需要自动依赖内部Bean时:直接调用方法即可
@Bean public Role role() { System.out.println(user1()); return new Role(); } @Bean public User user1() { return new User(); }
引入外部资源文件
使用@PropertySource 引入外部属性资源文件,并使用@value来干预bean的实例化过程
@Configuration
@ComponentScan(basePackages = "zt.demo")
@PropertySource("classpath:db.properties")
public class IoCJavaConfig {
@Value("${mysql.username}")
private String name;
@Value("${mysql.password}}")
private String password;
@Value("${mysql.url}")
private String url;
@Value("${mysql.driverClassName}")
private String driverClassName;
public DruidDataSource dataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setName(name);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
return dataSource;
}
}
使用AnnotationConfigApplicationContext初始化Spring容器
public class JavaConfigTest {
AnnotationConfigApplicationContext ioc;
@Before
public void before(){
ioc = new AnnotationConfigApplicationContext(IOCJavaConfig.class);
}
@Test
public void test4(){
Person bean = (Person) ioc.getBean("person");
System.out.println(bean);
ioc.close();
}
}
@Import注解
就像在Spring XML文件中使用元素来帮助模块化配置一样, @Import 注解允许从另一个配置类加载@Bean定义
- 引入其他的配置类
@Import(IOCJavaConfig2.class) public class IOCJavaConfig { }
- 将类注册为Bean
@Import(Role.class) public class IOCJavaConfig { }
- 导入ImportSelector接口实现类(可注册多个bean)
// 自定义ImportSelector接口实现类 @Component public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 可以以字符串的形式注册多个Bean,字符串必须是类的完整限定名,getBean不能根据名字获取,必须要根据类型获取 return new String[]{"demo.beans.Person", Role.class.getName()}; } } // 配置文件 @Import(MyImportSelector.class) public class IOCJavaConfig { }
- 导入ImportBeanDefinitionRegistrar接口实现类(可注册多个BeanDefinition)
// 自定义ImportBeanDefinitionRegistrar接口实现类 @Component public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 使用bean定义的方式 GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(Person.class); registry.registerBeanDefinition("person", beanDefinition); GenericBeanDefinition beanDefinition1 = new GenericBeanDefinition(); beanDefinition1.setBeanClass(User.class); registry.registerBeanDefinition("user", beanDefinition1); } } // 配置文件 @Import(MyImportBeanDefinitionRegistrar.class) public class IOCJavaConfig { }