Spring IoC容器与Bean管理

Spring IoC容器与Bean管理

Spring快速入门

IoC控制反转

  • IoC控制反转, 全称Inversion of Control, 是一种设计理念
  • 由代理人来创建与管理对象, 消费者通过代理人来获取对象
  • IoC的目的是降低对象之间的直接耦合
  • 加入IoC容器将对象统一管理, 让对象关联变为弱耦合

DI依赖注入

  • IoC是设计理念, 是现代程序设计遵循的标准, 是宏观目标
  • DI(Dependency Injection) 是具体技术实现, 是微观实现
  • DI在Java中利用反射技术实现对象注入(Injection)

传统开发方式

  • 对象之间存在彼此直接引用导致对象硬性关联, 程序难以扩展维护

  • 比如Service调用Dao是直接new的话, 如果这个Dao不在适合当前应用, 要换其他Dao类的话, 就要修改Service类的源代码, 来new其他Dao类, 而修改源代码后, 就要重新编译、测试、发布、上线, 这个过程是非常繁琐的.

Spring IoC容器

  • IoC容器是Spring生态的地基, 用于统一创建与管理对象依赖

Spring IoC容器职责

  • 对象的控制权交由第三方统一管理(IoC控制反转)
  • 利用Java反射技术实现运行时对象创建与关联(DI依赖注入)
  • 基于配置提高应用程序的可维护性与扩展性

Spring XML配置管理对象(Bean)

三种配置方式

  • 基于XML配置Bean
    • 基于构造方法实例化(常用)
    • 基于静态工厂实例化(了解)
    • 基于工厂实例方法实例化(了解)
  • 基于注解配置Bean
  • 基于Java代码配置Bean

基于XML配置Bean

核心配置文件名(约定俗成): applicationContext.xml

<bean id="sweetApple" class="com.zk.spring.ioc.entity.Apple">
    <property name="title" value="红富士"/>
    <property name="origin" value="欧洲"/>
    <property name="color" value="红色"/>
</bean>

XML方式创建IoC容器(加载xml配置文件)

// 创建Spring IoC容器, 并根据配置文件在容器中实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

从IoC容器获取Bean

bean 标签 id 与 name属性相同点
  • bean id 与 name 都是设置对象在IoC容器中唯一标识
  • 两者在同一个配置文件中都不允许出现重复
  • 两者允许在多个配置文件中出现重复, 新对象覆盖旧对象
bean 标签 id 与 name属性区别
  • id要求更为严格, 一次只能定义一个对象标识(推荐)
  • name更为宽松, 一次允许定义多个对象标识
  • tips: id与name的命名要求有意义, 按小驼峰命名书写

路径匹配表达式

加载单个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

classpath:applicationContext.xml路径表达式是指:

从类路径中寻找applicationContext.xml文件, Maven工程加载的时候的类路径target/classes这一级目录

加载多配置文件
String[] configLocations = new String[]{"classpath:applicationContext.xml", "classpath:applicationContext-1.xml"};
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
路径表达式
表达式实例说明
classpath:config.xml扫描classpath根路径(不包含jar)的config.xml
classpath:com/zk/config.xml扫描classpath下(不包含jar)com.zk包中的config.xml
classpath*:com/zk/config.xml扫描classpath下(包含jar)com.zk包中的config.xml
classpath:config-*.xml扫描classpath根路径下所有的以config-开头的XML文件
classpath:com/**/config.xml扫描com包下(包含任何子包)的config.xml(了解)
file:/linux/home/config.xml扫描/linux/home根路径config.xml(了解)

对象依赖注入

  • 依赖注入是指运行时将容器内对象利用反射赋给其他对象的操作
  • 基于setter方法注入对象(常用)
  • 基于构造方法注入对象

利用setter实现对象依赖注入

<bean id="bookDao" class="com.zk.spring.ioc.bookshop.dao.BookDaoOracleImpl">

</bean>
<bean id="bookService" class="com.zk.spring.ioc.bookshop.service.BookService">
    <property name="bookDao" ref="bookDao"/>
</bean>

利用构造方法实现对象依赖注入

<bean id="sweetApple" class="com.zk.spring.ioc.entity.Apple">
    <!-- IoC容器自动利用反射机制在运行时调用setXXX方法为属性赋值 -->
    <property name="title" value="红富士"/>
    <property name="origin" value="欧洲"/>
    <property name="color" value="红色"/>
</bean>
<bean id="andy" class="com.zk.spring.ioc.entity.Child">
    <constructor-arg name="name" value="andy"/>
    <constructor-arg name="apple" ref="sweetApple"/>
</bean>

注入集合对象

List, Set, Map, Properties

<bean id="company" class="com.zk.spring.ioc.entity.Company">
    <property name="rooms">
        <list>
            <value>2001-总裁办</value>
            <value>2003-总经理办公室</value>
            <value>2010-研发部会议室</value>
            <value>2010-研发部会议室</value>
        </list>
    </property>
    <property name="computers">
        <map>
            <entry key="dev-88172" value-ref="c1"/>
            <entry key="dev-88173">
                <bean class="com.zk.spring.ioc.entity.Computer">
                    <constructor-arg name="brand" value="联想"/>
                    <constructor-arg name="type" value="台式机"/>
                    <constructor-arg name="sn" value="84324324324"/>
                    <constructor-arg name="price" value="3085"/>
                </bean>
            </entry>
        </map>
    </property>
    <property name="info">
        <props>
            <prop key="phone">0101023-123123</prop>
            <prop key="address">北京市xxx</prop>
            <prop key="website">https://www.xxx.com</prop>
        </props>
    </property>
</bean>

查看容器内对象

// 获取容器内所有beanId数组
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName : beanNames) {
    System.out.println(beanName);
    System.out.println("类型:" + context.getBean(beanName).getClass().getName());
    System.out.println("内容:" + context.getBean(beanName));
}

bean scope属性

  • bean scope属性用于决定对象何时被创建与作用范围
  • bean scope配置将影响容器内对象的数量
  • bean scope默认值singleton(单例), 指全局共享同一个对象实例
scope属性说明
singleton单例(默认值), 每一个容器有且只有唯一的实例, 实例被全局共享
prototype多例, 每次使用时都是创建一个实例
requestweb环境下, 每一次独立请求存在唯一实例(生存范围: 请求), 了解
sessionweb环境下, 每一个session存在有唯一实例, 了解
applicationweb环境下, ServletContext存在唯一实例, 了解
websocket每一次WebSocket连接中存在唯一实例, 了解

singleton在容器是单例多线程执行, 存在线程安全风险

prototype在容器中多实例, 占用更多资源, 不存在线程安全问题

singleton与prototype对比
singletonprototype
对象数量全局唯一存在多个
实例化时机IoC容器启动时getBean()或对象注入时
线程安全问题存在不存在
执行效率

tips: 因为Service中的Dao类是稳定的(恒定不变的), 所以设置为单例没问题

如果一个类中某个属性是会改变的, 要设置成多例

bean的生命周期

image-202104211526539dsf00

<bean id="order1" class="com.zk.spring.ioc.entity.Order" init-method="init" destroy-method="destroy">
    <property name="price" value="19.8"/>
    <property name="quantity" value="1000"/>
</bean>
public class Order {
    private Float price;
    private Integer quantity;
    private Float total;

    public void init() {
        total = price * quantity;
    }
    public void destroy() {
        System.out.println("释放与订单对象相关的资源");
    }
((ClassPathXmlApplicationContext) context).registerShutdownHook(); // IoC容器开始进行销毁, 会调用对象的destory-method所声明的方法, 来完成对象的释放

实现极简IoC容器

public class ClassPathXmlApplicationContext implements ApplicationContext {
    private Map<String, Object> iocContainer = new HashMap<>();

    public ClassPathXmlApplicationContext() {
        try {
            String filePath = this.getClass().getResource("/applicationContext.xml").getPath();
            filePath = URLDecoder.decode(filePath, "UTF-8");
            SAXReader reader = new SAXReader();
            Document document = reader.read(new File(filePath));
            List<Node> beans = document.getRootElement().selectNodes("bean");
            for (Node bean : beans) {
                Element element = (Element) bean;
                String id = element.attributeValue("id");
                String className = element.attributeValue("class");
                Class<?> c = Class.forName(className);
                Object obj = c.newInstance();
                List<Node> properties = element.selectNodes("property");
                for (Node property : properties) {
                    Element ele = (Element) property;
                    String propName = ele.attributeValue("name");
                    String propValue = ele.attributeValue("value");
                    String setMethodName = "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
                    System.out.println("准备执行" + setMethodName + "方法注入数据");
                    Method setMethod = c.getMethod(setMethodName, String.class);
                    // 通过setter方法注入数据
                    setMethod.invoke(obj, propValue);
                }
                iocContainer.put(id, obj);
            }
            System.out.println("IoC容器初始化完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String beanId) {
        return iocContainer.get(beanId);
    }
}

注解与Java Config配置IoC容器

基于注解配置IoC容器

基于注解的优势

  • 摆脱繁琐的XML形式的bean与依赖注入配置
  • 基于"声明式"的原则, 更适合轻量级的现代企业应用
  • 让代码可读性变得更好, 研发人员拥有更好的开发体验

三类注解

  • 组件类型注解-声明当前类的功能与职责
  • 自动装配注解-根据属性特征自动注入对象
  • 元数据注解-更细化的辅助IoC容器管理对象的注解

四种组件类型注解

注解说明
@Component组件注解, 通用注解, 被该注解描述的类将被IoC容器管理并实例化
@Controller语义注解, 说明当前类是MVC应用中的控制器类
@Service语义注解, 说明当前类是Service业务服务类
@Repository语义注解, 说明当前类用于业务持久层, 通常描述对应Dao类

开启组件扫描

<!-- XML配置开启组件扫描, 才能使用注解, 在IoC容器初始化时自动扫描四种组件类型注解并完成实例化 -->
<context:component-scan base-package="com.zk">
    <!-- 排除某个包, 不进行IoC管理, 不常用 -->
	<context:exclude-filter type="regex" expression="com.zk.xx.*"/>
</context:component-scan>

两类自动装配注解

分类注解说明
按类型装配(不建议使用)@Autowired按容器内对象类型动态注入属性, 由Spring机构提供
@Inject基于JSR-330标准, 其他同@Autowired, 但不支持required属性
按名称装配@Named与@Inject配合使用, JSR-330规范, 按属性名自动装配属性
@Resource基于JSR-250规范, 优先按名称, 再按类型智能匹配(最常用)
  • 如果装配注解放在set方法上, 则自动按类型/名称对set方法参数进行注入
  • 如果装配注解放在属性上, Spring IoC容器会自动通过反射技术将属性private修饰符自动改为public, 直接进行赋值, 不再执行set方法

@Resource注解详解

  1. @Resource如果设置name属性, 则按name属性的值作为bean name在IoC容器中将bean注入
  2. @Resource未设置name属性
    1. 以属性名作为bean name在IoC容器中匹配bean, 如有匹配则注入
    2. 按属性名未匹配到, 则按类型进行匹配, 同@Autowired, 需加入@Primary注解解决类型冲突
  3. 使用建议: 在使用@Resource时推荐设置name或保证属性名与bean name一致

元数据注解

注解说明
@Primary按类型装配时出现多个相同类型对象, 拥有此注解对象优先被注入
@PostConstruct描述方法, 相当于XML中init-method配置的注解版本
@PreDestroy描述方法, 相当于XML中destroy-method配置的注解版本
@Scope设置bean的scope属性
@Value为属性注入静态数据

@Value读取properties文件属性值的用法

<!--  通知Spring IoC容器初始化时加载属性文件  -->
<context:property-placeholder location="classpath:config.properties"/>
@Value("${metaData}")
private String metaData;

基于Java Config配置IoC容器

  • 完全摆脱XML的束缚, 使用独立Java类管理对象与依赖
  • 注解配置相对分散, 利用Java Config可对配置集中管理
  • 可以在编译时进行依赖检查, 不容易出错

Java Config 核心注解

注解说明
@Configuration描述类, 说明当前类是Java Config配置类, 完全替代XML文件
@Bean描述方法, 方法返回的对象将被IoC容器管理, beanId默认为方法名
@ImportResource描述类, 加载静态配置文件, 可使用@Value注解获取
@ComponentScan描述类, 同XML的<context:component-scan>标签
// 基于Java Config配置IoC容器的初始化
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

Spring单元测试

Spring Test测试模块

  • Spring Test是Spring中用于测试的模块
  • Spring Test对JUnit单元测试框架有良好的整合
  • 通过Spring Test可在JUnit在单元测试时自动初始化IoC容器

Spring与JUnit4整合过程

  • Maven工程依赖spring-test和junit
  • 利用@RunWith与@ContextConfiguration注解描述测试用例类
    • @RunWith(SpringJUnit4ClassRunner.class) 将JUnit4的执行权交由Spring Test, 在测试用例执行前自动初始化IoC容器
    • @ContextConfiguration(location = {"classpath:applicationContext.xml"}) 在IoC容器初始化时指定要加载的配置文件
  • 测试用例类从容器获取对象完成测试用例的执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值