通过一个简单的案例来认识什么是控制反转,为什么要使用SpringIOC容器
业务逻辑层Dao,定义一个UserDao接口
package com.ctb.dao;
public interface UserDao {
// 获取用户
void getUser();
}
接下来写每个不同的UserDao的具体实现
普通方式:
package com.ctb.dao.impl;
import com.ctb.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("普通方式获取用户");
}
}
使用Mysql方式:
package com.ctb.dao.impl;
import com.ctb.dao.UserDao;
public class UserDaoMysqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("使用Mysql获取数据");
}
}
使用Oracle方式:
package com.ctb.dao.impl;
import com.ctb.dao.UserDao;
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("使用Oracle获取数据");
}
}
接下来写的是Service层接口以及具体实现
package com.ctb.service;
public interface UserService {
public void getUser();
}
package com.ctb.service.impl;
import com.ctb.dao.UserDao;
import com.ctb.service.UserService;
public class UserServiceImpl implements UserService {
// private UserDao userDao = new UserDaoImpl();
// private UserDao userDao = new UserDaoMysqlImpl();
private UserDao userDao = new UserDaoOracleImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
最后定义测试类:
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
结论:可以看到每次想调用不同的dao的时候都需要在service中进行修改,可以说是非常复杂的,如果说提供了用户可能用到的dao,当用户想调用哪个自己说明,就可以直接调用,那不就不用再让我们进行修改了吗,以下的方式就是说明控制反转
重新修改Service的实现类:
package com.ctb.service.impl;
import com.ctb.dao.UserDao;
import com.ctb.service.UserService;
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
重新完成测试类,测试类把需要的方式告诉Service,再通过Service进行调用:
package com.ctb.test;
import com.ctb.dao.impl.UserDaoImpl;
import com.ctb.dao.impl.UserDaoMysqlImpl;
import com.ctb.dao.impl.UserDaoOracleImpl;
import com.ctb.service.impl.UserServiceImpl;
public class MyTest {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserDaoImpl userDaoImpl = new UserDaoImpl();
userService.setUserDao(userDaoImpl);
userService.getUser();
System.out.println("--------------");
UserDaoMysqlImpl userDaoMysql = new UserDaoMysqlImpl();
userService.setUserDao(userDaoMysql);
userService.getUser();
System.out.println("--------------");
UserDaoOracleImpl userDaoOracle = new UserDaoOracleImpl();
userService.setUserDao(userDaoOracle);
userService.getUser();
}
}
结论:springIOC指的是控制反转,所谓的控制反转就是指一开始我来创建自己所需要的对象,后面反转为由别人来控制并给予我所需要的对象
控制反转的作用
- 管理对象的创建和依赖关系的维护
- 托管了类的产生过程
- 解耦,在面向对象的软件系统中,底层实现可能有多个对象组成如图所示
一旦某一个对象出现了问题,那么其他对象肯定回有所影响,这就是耦合性太高的缘故,但是对象的耦合关系是无法避免的,也是必要的。随着应用程序越来越庞大,对象的耦合关系可能越来越复杂,经常需要多重依赖关系,因此,无论是架构师还是程序员,在面临这样的场景的时候,都需要减少这些对象的耦合性。
SpringIOC的使用
下面介绍了SpringIOC的各种使用方式,给出案例进行说明,在下面每个案例操作之前,需要创建maven项目,并导入spring配置
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
- 通过bean的id来获取IOC容器中的对象
定义一个Animal的bean,给它创建3个属性(名字,颜色,腿的数量),给它创建set,get,toString方法
package com.ctb.bean;
public class Animal {
private String name;
private String color;
private int legsNum;
public Animal() {
}
public Animal(String name, String color, int legsNum) {
this.name = name;
this.color = color;
this.legsNum = legsNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getLegsNum() {
return legsNum;
}
public void setLegsNum(int legsNum) {
this.legsNum = legsNum;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", legsNum=" + legsNum +
'}';
}
}
配置ioc2.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="animal" class="com.ctb.bean.Animal">
<property name="name" value="二哈"></property>
<property name="color" value="黑白"></property>
<property name="legsNum" value="4"></property>
</bean>
</beans>
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Animal animal = (Animal)context.getBean("animal"); //在ioc2.xml中给animals的id定义为animal,此行通过id来获取bean
System.out.println(animal);
}
}
测试结果:
2. 通过bean的类型来获取IOC容器中的对象
同样是上面的配置,只用修改测试类
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Animal animal = (Animal)context.getBean(Animal.class);
System.out.println(animal);
}
}
注意:因为在ioc2.xml中创建的是只有一个Animal类型的bean对象,所以可以使用这种方式进行获取,如果在ioc2.xml中创建多个bean对象,需要修改方式如下:
配置文件如下:
<bean id="animal" class="com.ctb.bean.Animal">
<property name="name" value="二哈"></property>
<property name="color" value="黑白"></property>
<property name="legsNum" value="4"></property>
</bean>
<bean id="animal2" class="com.ctb.bean.Animal">
<property name="name" value="阿拉斯加"></property>
<property name="color" value="棕白"></property>
<property name="legsNum" value="4"></property>
</bean>
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Animal animal = (Animal)context.getBean("animal",Animal.class);
Animal animal2 = (Animal)context.getBean("animal2",Animal.class);
System.out.println(animal);
System.out.println(animal2);
}
}
- 通过构造器给bean对象赋值
使用该方式的时候,需要先给bean类补充构造方法
public Animal(String name, String color, int legsNum) {
this.name = name;
this.color = color;
this.legsNum = legsNum;
}
<bean id="animal3" class="com.ctb.bean.Animal">
<constructor-arg name="name" value="金毛"></constructor-arg>
<constructor-arg name="color" value="金色"></constructor-arg>
<constructor-arg name="legsNum" value="4"></constructor-arg>
</bean>
<!-- 使用构造器创建bean的时候可以省略name属性,但是要按照构造方法中参数的顺序来赋值 -->
<bean id="animal4" class="com.ctb.bean.Animal">
<constructor-arg value="柴犬"></constructor-arg>
<constructor-arg value="棕白"></constructor-arg>
<constructor-arg value="4"></constructor-arg>
</bean>
<!-- 或者使用index来决定每个值的位置 -->
<bean id="animal5" class="com.ctb.bean.Animal">
<constructor-arg value="黑色" index="1"></constructor-arg>
<constructor-arg value="4" index="2"></constructor-arg>
<constructor-arg value="藏獒" index="0"></constructor-arg>
</bean>
同时也会存在构造方法参数个数相同的情况,如果参数相同但是也不是用指定的name,那么可以使用以下方式
添加两个构造方法:
public Animal(String name, String color) {
this.name = name;
this.color = color;
}
public Animal(String name, int legsNum) {
this.name = name;
this.legsNum = legsNum;
}
<!-- 通过type来指定传入的参数类型-->
<bean id="animal6" class="com.ctb.bean.Animal">
<constructor-arg value="羊驼"></constructor-arg>
<constructor-arg value="白色" type="java.lang.String"></constructor-arg>
</bean>
<!-- 注意使用int的使用,在type中找不到包,所以可以通过以下方式 -->
<bean id="animal7" class="com.ctb.bean.Animal">
<constructor-arg value="公鸡"></constructor-arg>
<constructor-arg value="2" type="int" index="1"></constructor-arg>
</bean>
- 通过命名空间为bean赋值,简化配置文件中属性声明的写法
使用p-namespace进行更简洁的XML配置
在使用之前先要导入配置
xmlns:p="http://www.springframework.org/schema/p"
<bean id="animal8" class="com.ctb.bean.Animal" p:name="鸭子" p:color="白色" p:legsNum="2"></bean>
- 为复杂类型进行赋值操作
创建一个食物类:
public class Food {
String name;
public Food() {
}
public Food(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
'}';
}
}
创建一个place的类:
public class Place {
private String name;
private int size;
public Place() {
//System.out.println("创建Place的bean");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
return "Place{" +
"name='" + name + '\'' +
", size=" + size +
'}';
}
}
重新创建animal类:
package com.ctb.bean;
import java.util.*;
public class Animal {
private String name;
private String[] color;
private int legsNum;
private Food food;
private List<Place> placeList;
private Set<Integer> size;
private Map<String,Object> maps;
private Properties properties;
public Animal() {
}
public Animal(String name, String[] color, int legsNum, Food food, List<Place> placeList, Set<Integer> size, Map<String, Object> maps, Properties properties) {
this.name = name;
this.color = color;
this.legsNum = legsNum;
this.food = food;
this.placeList = placeList;
this.size = size;
this.maps = maps;
this.properties = properties;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", color=" + Arrays.toString(color) +
", legsNum=" + legsNum +
", food=" + food +
", placeList=" + placeList +
", size=" + size +
", maps=" + maps +
", properties=" + properties +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String[] getColor() {
return color;
}
public void setColor(String[] color) {
this.color = color;
}
public int getLegsNum() {
return legsNum;
}
public void setLegsNum(int legsNum) {
this.legsNum = legsNum;
}
public Food getFood() {
return food;
}
public void setFood(Food food) {
this.food = food;
}
public List<Place> getPlaceList() {
return placeList;
}
public void setPlaceList(List<Place> placeList) {
this.placeList = placeList;
}
public Set<Integer> getSize() {
return size;
}
public void setSize(Set<Integer> size) {
this.size = size;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
通过ioc2.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: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="food" class="com.ctb.bean.Food">
<property name="name" value="meat"></property>
</bean>
<bean id="place1" class="com.ctb.bean.Place">
<property name="name" value="Africa"></property>
</bean>
<!--给复杂类型的赋值都在property标签内进行-->
<bean id="animal" class="com.ctb.bean.Animal">
<property name="name">
<!--赋空值-->
<null></null>
</property>
<!--给数组赋值-->
<property name="color">
<array>
<value>黄色</value>
<value>白色</value>
</array>
</property>
<property name="legsNum" value="4"></property>
<!--通过ref引用其他对象,引用外部bean-->
<property name="food" ref="food"></property>
<!--引用内部bean-->
<!-- <property name="address">
<bean id="food" class="com.ctb.bean.Food">
<property name="name" value="肉"></property>
</bean>
</property>-->
<!--为list赋值-->
<property name="placeList">
<list>
<!--内部bean-->
<bean id="place2" class="com.ctb.bean.Place">
<property name="name" value="China"></property>
</bean>
<!--外部bean-->
<ref bean="place1"></ref>
</list>
</property>
<!--给set赋值-->
<property name="size">
<set>
<value>100</value>
<value>200</value>
</set>
</property>
<!--给map赋值-->
<property name="maps">
<map>
<entry key="appearance" value="cute"></entry>
<entry key="place" value-ref="place1"></entry>
<entry key="food3">
<bean class="com.ctb.bean.Food">
<property name="name" value="apple"></property>
</bean>
</entry>
</map>
</property>
<!--给property赋值-->
<property name="properties">
<props>
<prop key="aaa">aaa</prop>
<prop key="bbb">222</prop>
</props>
</property>
</bean>
</beans>
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Animal animal16 = context.getBean("animal",Animal.class);
System.out.println(animal16);
}
}
测试结果:
- 继承关系bean的配置
<bean id="animal" class="com.ctb.bean.Animal">
<property name="name" value="二哈"></property>
<property name="color" value="黑白"></property>
<property name="legsNum" value="4"></property>
</bean>
<!-- 我家的二哈出去沾花惹草了,生出了只杂狗,使用parent来指定bean的配置信息继承哪个bean -->
<bean id="animal9" class="com.ctb.bean.Animal" parent="animal">
<property name="name" value="杂狗"></property>
</bean>
测试类:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Animal animal9 = (Animal)context.getBean("animal9",Animal.class);
System.out.println(animal9);
}
7. bean对象创建的依赖关系
删除之前所有创建的bean,只用创建两个bean
<bean id="animal10" class="com.ctb.bean.Animal"></bean>
<bean id="place" class="com.ctb.bean.Place"></bean>
在两个类的无参构造方法中填入打印字段
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
}
}
测试结果:
当我修改配置为:
<bean id="animal10" class="com.ctb.bean.Animal" depends-on="place"></bean>
<bean id="place" class="com.ctb.bean.Place"></bean>
测试结果:
可以通过depends-on来决定创建bean的顺序
- bean的作用域控制,是否是单例
<bean id="animal11" class="com.ctb.bean.Animal" scope="singleton">
</bean>
<bean id="animal12" class="com.ctb.bean.Animal" scope="prototype">
</bean>
Animal animal11 = (Animal)context.getBean("animal11",Animal.class);
Animal animal11_1 = (Animal)context.getBean("animal11",Animal.class);
System.out.println(animal11 == animal11_1);
Animal animal12 = (Animal)context.getBean("animal11",Animal.class);
Animal animal12_1 = (Animal)context.getBean("animal11",Animal.class);
System.out.println(animal12 == animal12_1);
测试结果:
- 利用工厂模式创建bean对象
除了上述的提供反射得到对应的bean创建方式,还可以通过工厂模式进行对象的创建
静态工厂:工厂本身不需要创建对象,只是通过调用类的静态方法来创建
创建动物类的工厂
public class AnimalStaticFactory {
public static Animal createAnimal(String name){
Animal animal = new Animal();
animal.setName(name);
return animal;
}
}
<!-- 通过factory-method来指定工厂方法 -->
<bean id="animal13" class="com.ctb.factory.AnimalStaticFactory" factory-method="createAnimal">
<!--constructor-arg:可以为方法指定参数-->
<constructor-arg value="加菲猫"></constructor-arg>
</bean>
实例工厂:工厂本身需要创建对象
public class AnimalFactory {
public Animal createAnimal(String name){
Animal animal = new Animal();
animal.setName(name);
return animal;
}
}
<!--创建实例工厂类-->
<bean id="AnimalFactory" class="com.ctb.factory.AnimalFactory"></bean>
<!-- factory-bean:指定使用哪个工厂实例 factory-method:指定使用哪个工厂实例的方法 -->
<bean id="animal14" class="com.ctb.bean.Animal" factory-bean="AnimalFactory" factory-method="createAnimal">
<constructor-arg value="马"></constructor-arg>
</bean>
- 实现FactoryBean接口来创建对象
FactoryBean是Spring规定的一个接口,当前接口的实现类,Spring都会将其作为一个工厂,但是在ioc容器启动的时候不会创建实例,只有在使用的时候才会创建对象
public class MyFactoryBean implements FactoryBean<Animal> {
// 返回创建的对象
public Animal getObject() throws Exception {
Animal animal = new Animal();
animal.setName("ctb");
return animal;
}
// 返回创建的类型
public Class<?> getObjectType() {
return Animal.class;
}
// 返回创建对象是否单例
public boolean isSingleton() {
return false;
}
}
<bean id="myFactoryBean" class="com.ctb.factory.MyFactoryBean"></bean>
- bean对象的初始化和销毁方法
在Animal类中加入init,destory方法
如果bean是单例,容器在启动的时候会创建好,关闭的时候会销毁创建的bean,如果bean是多例,获取的时候创建对象,销毁的时候不会有任何的调用,destory在ApplicationContext关闭的时候才会调用
public void init(){
System.out.println("对象被初始化");
}
public void destory(){
System.out.println("对象被销毁");
}
<bean id="address" class="com.ctb.bean.Animal" init-method="init" destroy-method="destory"></bean>
测试代码:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc2.xml");
Animal animal15 = context.getBean("animal15",Animal.class);
System.out.println(animal15);
//applicationContext没有close方法,需要使用具体的子类
((ClassPathXmlApplicationContext)context).close();
}
}
测试结果:
12. 配置bean对象初始化方法的前后处理方法
如果想在bean对象初始化方法的前后进行相关操作,那么需要实现BeanPostProcessor接口
public class MyBeanPostProcessor implements BeanPostProcessor {
//在初始化方法调用之前执行
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization:"+beanName+"调用初始化前置方法");
return bean;
}
//在初始化方法调用之后执行
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization:"+beanName+"调用初始化后缀方法");
return bean;
}
}
<bean id="myBeanPostProcessor" class="com.ctb.factory.MyBeanPostProcessor"></bean>