首页 > 其他 > 详细

IOC容器

时间:2021-01-22 19:29:11      阅读:41      评论:0      收藏:0      [点我收藏+]

IOC底层原理

什么是 IOC

即控制反转,是面向对象的一种设计原则,用来降低代码耦合度。
其中最常见的方式叫做依赖注入(DI),还有一种方式叫依赖查找。
Spring 是常见的一种 IOC 容器。创建对象和对象之间的调用过程,都由 Spring 来管理。
本质是 XML 解析+工厂模式+反射。

底层原理解析示例

<!-- XML配置 -->
<bean id="dao" class="pack.UserDao"></bean>
class UserFactory {
    public static userDao getDao() {
        //通过XML解析获取全类名
        String className = "pack.UserDao";
        //通过反射创建类
        Class clazz = Class.forName(className);
        return (UserDao) clazz.newInstance();
    }
}

IOC 的实现

方式一:BeanFactory
@Test
public void test() {
    //加载 Spring 配置文件
    BeanFactory factory = new ClassPathXmlApplicationContext("bean.xml");
    //获取配置创建的对象
    User user = factory.getBean("user", User.class);
    user.test();
}
方式二:ApplicationContext
@Test
public void test() {
	//加载 Spring 配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    //获取配置创建的对象
    User user = context.getBean("user", User.class);
    user.test();
}
两者的区别
BeanFactory 在加载配置文件时不创建对象,在获取对象时才创建,而 ApplicationContext 在加载配置文件时就创建对象。
在实际开发中,一般采用 ApplicationContext,而只有在系统资源较少时,才考虑使用 BeanFactory。
在 Web 项目中,ApplicationContext 容器的实例化工作会交由 Web 服务器完成。

XML 方式Bean 管理

创建对象

<!-- 在 Spring 配置文件中配置对象创建 -->
<bean id="user" class="pack.User"></bean>
id 属性是唯一标识。
class 属性是类的全路径。
创建对象时,默认执行的是无参构造方法。

依赖注入(DI)

通过 setXxx 方法注入属性
//必须声明 set 方法
class User{
    private String userName;
    private String userAge;
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public void setUserAge(String userAge) {
        this.userAge = userAge;
    }
}
<!-- 通过 property 标签注入属性 -->
<bean id="user" class="pack.User">
    <property name="userName" value="ZhangSan"></property>
    <property name="userAge" value="77"></property>
</bean>
通过有参构造方法注入属性
//必须声明有参构造方法
class User{
    private String userName;
    private String userAge;
    public User(String userName, String userAge){
        this.userName = userName;
        this.userAge = userAge;
    }
}
<!-- 通过 constructor-arg 标签注入属性 -->
<bean id="user" class="pack.User">
    <constructor-arg name="userName" value="ZhangSan"></constructor-arg>
    <constructor-arg name="userAge" value="77"></constructor-arg>
</bean>
通过p名称空间注入
<!-- 在配置文件中添加 p 名称空间 -->
<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">
 
    <!-- 使用 p 名称空间注入属性 -->
    <bean id="user" class="pack.User" p:userName="ZhangSan" p:userAge="77"></bean>
    
</beans>
注入空值和特殊符号
<!--注入 null -->
<property name="userName">
    <null/>
</property>


<!-- 注入特殊符号,例如注入 "<ZhangSan>" -->
<property name="userName">
    <value> 
        <!-- 法一:使用&lt;或&gt;转义符号进行转义 -->
        &lt;< ZhangSan &lt;>
    </value>
</property>
    
<property name="userName">
    <value>
        <!-- 法二:使用 CDATA -->
        <![CDATA[
			<ZhangSan>
		]]>
    </value>
</property>
注入外部 Bean
class UserService {
    private UserDaoImpl impl;
    public void setUserDaoImpl(UserDaoImpl impl) {
        this.impl = impl;
    }
    ...
}
<!-- 通过 ref 标签注入外部 Bean -->
<bean id="service" class="pack.service.UserService">
    <property name="impl" ref="dao"></property>
</bean>
<bean id="dao" class="pack.dao.UserDaoImpl"></bean>
注入内部 Bean 和级联赋值
class UserService {
    private UserDaoImpl impl;
    public void setUserDaoImpl(UserDaoImpl impl) {
        this.impl = impl;
    }
    //get方法
    public UserDaoImpl getUserDaoImpl(UserDaoImpl impl) {
        return this.impl;
    }
    ...
}

class UserDaoImpl {
    private String value;
    ...
}
<!-- 法一:如注入外部 Bean -->
<bean id="service" class="pack.service.UserService">
    <property name="impl" ref="dao"></property>
</bean>
<bean id="dao" class="pack.dao.UserDaoImpl">
    <property name="value" value="value in impl"></property>
</bean>

<!-- 法二:需要写出 UserService中该属性的 get 方法-->
<bean id="service" class="pack.service.UserService">
    <property name="impl" ref="dao"></property>
    <!-- 使用 impl.value -->
    <property name="impl.value" value="value in impl">
</bean>
<bean id="dao" class="pack.dao.UserDaoImpl">
</bean>
    
<!-- 法三:级联赋值 -->
<bean id="service" class="pack.service.UserService">
    <property name="impl">
        <bean id="dao" class="pack.dao.UserDapImpl">
            <property name="value" value="value in impl"></property>
        </bean>
    </property>
</bean>
注入集合类型属性
public class Student {
    private String[] courses;
    private List<String> lists;
    private Map<String,String> maps;
    private Set<String> sets;
    //对象类型
    private List<Course> stuLists;
    
    public void setCourses(String[] courses) { this.courses = courses; }
    public void setLists(List<String> lists) { this.lists = lists; }
    public void setMaps(Map<String, String> maps) { this.maps = maps; }
    public void setSets(Set<String> sets) { this.sets = sets; }
    public void setStuLists(List<Course> stuLists) { this.stuLists = stuLists; }
}
<bean id="student" class="pack.Student">
    <!-- Array -->
    <property name="courses">
        <array>
            <value>Java</value>
            <value>MySQL</value>
        </array>
    </property>
    <!-- List -->
    <property name="lists">
        <list>
            <value>Python</value>
        </list>
    </property>
    <!-- Map -->
    <property name="maps">
        <map>
            <entry key="01" value="Ad"></entry>
        </map>
    </property>
    <!-- Set -->
    <property name="sets">
        <set>
            <value>Oracle</value>
        </set>
    </property>
	<!-- List & Class -->
    <property name="stuLists">
        <list>
            <ref bean="cour1"></ref>
            <ref bean="cour2"></ref>
        </list>
    </property>
</bean>

<bean id="cour1" class="pack.Course">
    <property name="cName" value="Java"></property>
</bean>
<bean id="cour2" class="pack.Course">
    <property name="cName" value="Go"></property>
</bean>
提取集合注入部分
public class Book {
    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入名称空间 util -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!--提取list集合属性注入-->
    <util:list id="bookList">
        <value>val-00</value>
        <value>val-01</value>
        <value>val-02</value>
    </util:list>
    <!--提取list集合属性注入使用-->
    <bean id="book" class="pack.Book">
        <property name="list" ref="bookList"></property>
    </bean>
</beans>

FactoryBean

Spring 中有两种 Bean,普通 Bean 和 FactoryBean。
区别:
	普通 Bean:配置文件中定义的类型就是返回类型。
	FactoryBean:返回类型可以和配置文件中定义的类型不一样。
<!--FactoryBean-->
<bean id="facBean" class="pack.UserA"></bean>
<!--NormalBean-->
<bean id="norBean" class="pack.UserB"></bean>
public class UserA implements FactoryBean {

    //重写返回类型
    @Override
    public UserB getObject() throws Exception {
        return new UserB();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
@Test
public void testFacBean() {
   ApplicationContext context
        = new ClassPathXmlApplicationContext("bean.xml");
   //返回的不是UserA类型,而是UserB类型
   UserB user = context.getBean("facBean", UserB.class);
   System.out.println(user);
}

Bean的作用域

在 Spring 中设置 Bean 实例是单实例还是多实例。默认是单实例对象。 
//单实例是指多次获取相同的 Bean,返回的是同一个实例。
<!-- 通过 scope 标签来设置单实例 singleton 或多实例 prototype -->
<bean id="user" class="pack.User" scope="prototype"></bean>
单实例 singleton 和多实例 prototype 的区别:
	设置为 singleton 时,在加载配置文件的时候就会创建单实例对象。
	设置为 prototype 时,在调用 getBean() 时创建多实例对象。

Bean的生命周期

从 Bean 实例创建到销毁的过程:
1. 通过构造器创建 Bean 实例
2. 依赖注入
3. 调用 Bean 的初始化方法
4. 创建完成,对象可以使用
5. 当容器关闭时,调用 Bean 的销毁方法
public class Demo {
    private String name;

    public Demo() { System.out.println("执行无参构造方法"); }
    
    public void setName(String name) {
        this.name = name;
        System.out.println("调用 set 方法实现依赖注入");
    }
    
    public void init() { System.out.println("执行定义的初始方法"); }
    
    public void destory() { System.out.println("执行定义的销毁方法"); }
    
    @Override
    public String toString() { 
        return "调用创建对象的 toString 方法"; 
    }
}
<!-- 需要配置初始方法和销毁方法 -->
<bean id="demo" class="pack.Demo" init-method="init" destroy-method="destory">
    <property name="name" value="my name"></property>
</bean>
@Test
public void testBeanLife() {
    ApplicationContext context
        = new ClassPathXmlApplicationContext("beanlife.xml");
    Demo demo = context.getBean("demo", Demo.class);
    System.out.println(demo);
    //需要手动关闭容器
    ((ClassPathXmlApplicationContext)context).close();
}
输出结果:
D:\Tools\JDK\bin\java.exe...
执行无参构造方法
调用set方法实现依赖注入
执行定义的初始方法
调用创建对象的toString方法
执行定义的销毁方法

Process finished with exit code 0
加入 Bean 的后置处理器后的生命周期
1. 通过构造器创建 Bean 实例
2. 依赖注入
3. 把实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法
4. 调用 Bean 的初始化方法
5. 把实例传递给 Bean 后置处理器的 postProcessAfterInitialization 方法 
6. 创建完成,对象可以使用
7. 当容器关闭时,调用 Bean 的销毁方法
//创建一个后置处理器
public class Post implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("调用配置处理器的 Before 方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("调用配置处理器的 After 方法");
        return bean;
    }
}
<!-- 后置处理器对同一配置文件中的其他 bean 都起作用 -->
<bean id="post" class="pack.Post"></bean>
添加后置处理器后的运行结果:
D:\Tools\JDK\bin\java.exe...
执行无参构造方法
调用set方法实现依赖注入
调用配置处理器的 Before 方法
执行定义的初始方法
调用配置处理器的 After 方法
调用创建对象的 toString 方法
执行定义的销毁方法

Process finished with exit code 0

自动装配

根据制定的装配规则(属性名称或属性类型),Spring 自动将匹配的属性值进行注入。
public class User {
    private PassWord pwd;
    public void setPassWord(Password pwd) {
        this.pwd = pwd;
    }
}
<!-- 
	通过 autowire 标签来实现自动装配
	byName 方式根据 id 与属性名来装入,要求装入的 bean 的 id 与属性名相同
	byType 方式根据属性的类型来装入,要求指定类型的 bean 只能定义一个
-->
<bean id="user" class="pack.User" autowire="byName"></bean>
<bean id="pwd" class="pack.Password"></bean>

外部属性文件

例:配置 druid 数据库的连接池
<!-- 直接配置连接池方式 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value ="jdbc:mysql://localhost:3306/userDb"></property>
	<property name="username" value="root"></ property>
	<property name="password" value="root"></ property>
</bean>
#外部 properties 文件
prop.driverClass = com.mysql.jdbc.Driver
prop.url = jdbc:mysql://localhost:3306/userDb
prop.userName = root
prop.password = root
<!-- 引入外部属性文件方式 -->
<!-- step01 引入 context 名称空间 -->
...

<!-- step02 在配置文件中使用标签引入外部文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
                                          
<!-- step03 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${prop.driverClass}"></property>
    <property name="url" value="${prop.url}"></ property>
    <property name="username" value="${prop.userName}"></ property>
	<property name="password" value="${prop.password}"></ property>
</bean>

注解方式 Bean 管理

创建对象

Spring 针对 Bean 管理中创建对象提供的注解,功能一样,用在不同层
1) @Component
2) @Service
3) @Controller
4) @Repository
使用注解方式创建对象的步骤:
1. 引入额外的外部依赖 spring-aop-5.6.2.RELEASE.jar
2. 开启组件扫描
	2.1 引入名称空间 context
	2.2 使用 component-scan 标签开启组件扫描
<!--
	开启组件扫描
	1. 多个包的路径用 , 隔开
  	2. 写需要扫描的包的上层目录也行
-->
<context:component-scan base-package="pack.dao, pack.service"></context:component-scan>
<!--
<context:component-scan base-package="pack"></context:component-scan>
-->
/**
 * 相当于 <bean id="userService" class="classPath"></>
 * value 可以不写,默认值是类名(驼峰命名,第一个首字母小写)
 * @Component 可以用 @Service、@Controller、@Repository 替代
 */
@Component(value = "userService")
public class UserService {
    //...
}

组件扫描配置

<!-- 配置一:只扫描 spring 包下的 @Component 注解 -->
<context:component-scan base-package="com.spring5" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
    
<!-- 配置二:扫描 spring 报下除了 @Component 的其他注解 -->
<context:component-scan base-package="com.spring5">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

基于注解方式实现属性注入

注入方式:
1) @Autowired:根据属性类型进行自动注入
2) @Qualifier:根据属性名称进行自动注入,要和 @Autowired 一起使用
3) @Resource:可以根据类型也可根据名称注入,是 javax.annotation.Resource 中的注解,不建议使用
4) @Value:注入基本类型属性

注入步骤:
1. 基于注释实现对象创建
2. 定义属性,并在属性上面实现注解。不需要定义 set 方法
@Service
public class UserService {
    //根据类型注入
    @Autowired
    private UserDao dao;
    
    //基本类型注入
    @Value(value="hello")
    private String name;
    
    /**
    *  当出现Use人Dao具有多个实现类的时候,配合 @Qualifier来使用
    *  @Autowired
    *  @Qualifier(value="myUserDaoImp1")
    *  private UserDao dao;
    */
    
    /**
    *  //@Resource //根据类型注入
    *  @Resource(name="myUserDaoImp1") //根据名称和类型注入
    *  private UserDao dao;
    */
    
    
}

@Repository(value="myUserDaoImpl")
public class UserDaoImpl implements UserDao{
    //...
}

完全注解开发

//实际开始中一般是基于 SpringBoot 实现
//创建测试类代替 XML 配置文件
@Configuration
@ComponentScan(basePackages = {"com.spring5"})
public class SpringConfig {
}

//获取 ApplicationContext 对象
@Test
public void testService() {
    ApplicationContext context 
        = new AnnotationConfigApplicationContext(SpringConfig.class);
    //...
}

IOC容器

原文:https://www.cnblogs.com/zhugaoxiang/p/14314798.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!