01【高内聚低耦合、Spring概述、IOC容器、Bean的配置方式】

01【高内聚低耦合、Spring概述、IOC】

一、高内聚低耦合

1.1 程序架构设计

高内聚低耦合,是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。目的是使程序模块的可重用性、移植性大大增强。通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低

  • 内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事,它描述的是模块内的功能联系;

  • 耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。

Tips:元素可以是方法、对象(类),也可以指系统、子系统、模块、服务等。

  • 1)高内聚低耦合是软件架构的一种概念、思想;
  • 2)内聚与耦合想相对的,内聚程度越高那么耦合度就低,内聚程度越低往往耦合度就高;
  • 3)我们在软件设计时,应该尽量做到高内聚、低耦合;

1.2 低耦合

1.2.1 耦合概念

耦合,简单的来说就是元素(类)与元素(类)之间的关系;我们在设计程序时应该降低元素与元素之间的直接关系;降低元素与元素之间的耦合性;

假如一个元素A去调用元素B,或者通过自己的方法可以感知B,当B不存在的时候就不能正常工作,那么就说元素A与元素B耦合。耦合带来的问题是,当元素B发生变更或不存在时,都将影响元素A的正常工作,影响系统的可维护性和易变更性。同时元素A只能工作于元素B存在的环境中,这也降低了元素A的可复用性。

1.2.2 如何降低耦合

简单的来说就是元素A不能过度依赖元素B;

  • 1)定制合理的职责划分,让系统中的对象各司其职,不仅是提高内聚的要求,同时也可以有效地降低耦合;

  • 2)使用接口而不是继承:我们不难发现。继承就是一种耦合,假如子类A继承了父类B,父类B修改了任何功能将直接影响到子类,而接口则是将功能延迟到了子类中来实现;

1.3 高内聚

架构设计时的内聚高低是指,设计某个模块时,模块内部的一系列相关功能的相关程度的高低。相关程度越高,我们称之为高内聚,反之低内聚;内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。

很明显,程序的内聚性越高,代表功能的相关性也就越高,从一定程度上来说,这个功能就应该属性这个元素;因此耦合性就降低了;

程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。

1.4 不能完全低耦合

高内聚低耦合只是一个度的问题,我们并不能做到完全的高内聚低耦合;有时候太过严格反而会适得其反

我们可以举几个代码上的例子:

1)我们的UserServlet中,出现了UserService和DeptService,从一定程度上来讲,Dept的业务应该是不属于我User管辖的

在这里插入图片描述

如果要严格按照"高内聚,低耦合"的要求来,那么只能把DeptService的业务写在DeptServlet中,这样做虽然是降低了UserServlet与DeptService的联系,提高了UserServlet的内聚,但很显然,UserService变得非常臃肿(高耦合、低内聚)

2)我们再"严格"一点,ArticleServlet的功能应该由ArticleServlet来提供,而不需要借助其他类,否则ArticleServlet就与ArticleService产生耦合关系:

在这里插入图片描述

那么为了进一步降低耦合性,只能把ArticleService的代码编写到ArticleServlet中,那么很显然,ArticleSerlvet的内聚性就非常低了,因为很多不是属于ArticleServlet的代码也编写到ArticleServlet中了;

因此"高内聚低耦合只是一个度的问题",但是实际的设计开发过程中,总会发生这样那样的问题与情况,真正做到高内聚、低耦合是很难的,很多时候未必一定要这样, 更多的时候“最适合”的才是最好的, 不过,理解思想,审时度势地使用, 融会贯通,灵活运用,才是设计的王道。

比如说我们设计一个类可以不与JDK耦合,这可能吗?除非你不是设计的Java程序。再比如我设计了一个类,它不与我的系统中的任何类发生耦合。如果有这样一个类,那么它必然是低内聚,而且是非常低的内聚(因为所有的功能都在一个类的,耦合非常高,内聚非常低)。耦合与内聚常常是一个矛盾的两个方面。最佳的方案就是寻找一个合适的中间点。

  • 高内聚:一个的元素中的功能应该都是相关程度非常高的,不要提供与本身无关的功能

  • 低耦合:尽量与其他元素不要产生关系;

二、Spring概述

2.1 Spring 是什么

Spring是一个JavaEE轻量级的一站式开发框架。

JavaEE: 就是用于开发B/S的程序。(企业级)

轻量级:使用最少代码启动框架,然后根据你的需求选择,选择你喜欢的模块使用。

一站式:提供了表示层,服务层,持久层的所有支持。

2.2 Spring出现的背景

在世界第一套有Java官方Sun公司推出的企业级开发框架EJB出现后,瞬间风靡全球。被各大公司所应用。Spring之父,Rod Jonhson是一个音乐博士,在Sun公司的大力推广下,也成为EJB框架的使用者。

在深入研究完EJB框架(由Sun主导开发的一个JavaEE开发框架),无法接收这么一个框架被吹成世界第一,其中突出被他吐槽最厉害的一个点就EJB的重量级,就是只要使用EJB里面任何一个组件。都要将所有EJB的jar导进去。

于是他就提供了一个他的解决方案:**轻量级的一站式企业开发框架。**那么什么是轻量级呢?就是除内核模块(4个jar),其他模块由开发者自由选择使用,同时支持整合其他框架。也可以称为就是可插拔式开发框架,像插头和插座一样,插上就用。这就是Spring框架核心理念。

那么什么是一站式呢?

就是Spring框架提供涵盖了JavaEE开发的表示层,服务层,持久层的所有组件功能。也就是说,原则上,学完一套Spring框架,不用其他框架就可以完成网站一条流程的开发。

在这里插入图片描述

2.3 Spring包详解

Spring提供的功能模块特别多,我们需要什么功能就导入对应的jar包即可,但是Spring环境有4个核心包必须导入;

包名说明
spring-aop-5.2.9.RELEASE.jar实现了AOP的支持
spring-aspects-5.2.9.RELEASE.jarAOP框架aspects支持包
spring-beans-5.2.9.RELEASE.jar内核支撑包,实现了处理基于xml对象存取
spring-context-5.2.9.RELEASE.jar内核支撑包,实现了Spring对象容器
spring-context-support-5.2.9.RELEASE.jar容器操作扩展包,扩展了一些常用的容器对象的设置功能
spring-core-5.2.9.RELEASE.jar内核支撑包,Spring的内核
spring-expression-5.2.9.RELEASE.jar内核支撑包,实现了xml对Spring表达式的支持
spring-instrument-5.2.9.RELEASE.jar提供了一些类加载的的工具类
spring-instrument-tomcat-5.2.9.RELEASE.jar提供了一些tomcat类加载的的工具类,实现对应Tomcat服务的调用
spring-jdbc-5.2.9.RELEASE.jarSpringJDBC实现包,一个操作数据库持久层的子框架
spring-jms-5.2.9.RELEASE.jar集成jms的支持,jms:Java消息服务。
spring-messaging-5.2.9.RELEASE.jar集成messaging api和消息协议提供支持
spring-orm-5.2.9.RELEASE.jarORM框架集成包,实现了Hibernate,IBatis,JDO的集成。
spring-oxm-5.2.9.RELEASE.jarSpring OXM对主流O/X Mapping框架做了一个统一的抽象和封装。就是对应XML读写框架的支持
spring-test-5.2.9.RELEASE.jarSpring集成JUnit测试
spring-tx-5.2.9.RELEASE.jar事务代理的支持
spring-web-5.2.9.RELEASE.jarSpringWeb通用模块
spring-webmvc-5.2.9.RELEASE.jarSpringMVC子框架
spring-webmvc-portlet-5.2.9.RELEASE.jarSpring对门户技术(portlet)的支持
spring-websocket-5.2.9.RELEASE.jarSpring对websocket的支持

Tips:我们本次采用的是Spring最新版本5.0

三、Spring快速入门

3.1 搭建Spring环境

搭建Spring环境最少需要4个核心包,分别是context、core、expression、beans,在Maven中,我们直接导入context的坐标依赖即可

  • pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dfbz</groupId>
    <artifactId>01_Spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.12.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

在依赖空白处右键:Maven–>Show Dependencies

在这里插入图片描述

或者直接按住快捷键Ctrl+Shift+Alt+U

查看Maven依赖图:

在这里插入图片描述

编写功能类:

package com.dfbz.service;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class HelloService {
    public void sayHello(){
        System.out.println("hello spring!");
    }
}

3.2 编写Spring配置文件

在这里插入图片描述

<?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">

    <!--
        往SpringIOC容器里面添加一个类
        id:类的名称
        class:类的全路径
    -->
    <bean id="helloService" class="com.dfbz.service.HelloService"></bean>
</beans>

3.3 测试类

package com.dfbz.test;

import com.dfbz.service.HelloService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {

    @Test
    public void test1() {

        // 获取SpringIOC容器
        ApplicationContext app =
                new ClassPathXmlApplicationContext("application.xml");

        // 根据id获取
        HelloService helloService = (HelloService) app.getBean("helloService");

        // 根据字节码对象获取
//        HelloService helloService = app.getBean(HelloService.class);

        helloService.sayHello();
    }
}

通过Spring,我们可以不用new就可以创建对象;对象交给Spring帮我们管理,想要的时候直接去Spring的IOC容器里面获取;

四、IOC容器

4.1 为什么要IOC容器?

4.1.1 没有IOC容器的情况

我们来使用三层架构模拟一段保存user的逻辑:

  • 定义一个UserDao接口:
package com.dfbz.dao;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public interface UserDao {
    void save();
}
  • 定义实现类-01:
package com.dfbz.dao.impl;

import com.dfbz.dao.UserDao;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class UserDaoImplMySQL implements UserDao {
    @Override
    public void save() {
        System.out.println("使用MySQL保存到数据库...");
    }
}

  • 定义实现类-02:
package com.dfbz.dao.impl;

import com.dfbz.dao.UserDao;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class UserDaoImplOracle implements UserDao {
    @Override
    public void save() {
        System.out.println("使用Oracle保存到数据库..");
    }
}
  • 定义UserService:
package com.dfbz.service;

import com.dfbz.dao.UserDao;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }
}
  • 定义UserController:
package com.dfbz.controller;

import com.dfbz.service.UserService;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void save() {
        userService.save();
    }

}
  • 测试类:

我们会发现,要保存一个用户必须手动的创建一个个对象,并将对象移除组装起来,最后组装成一个UserController,然后才可以调用;

@Test
public void test2() {

    UserDao userDao = new UserDao();
    UserService userService = new UserService(userDao);
    UserController userController = new UserController(userService);

    userController.save();
}

存在的问题:

  • 1)UserController的过程非常复杂且需要我们来负责整个的创建过程,我们必须要对UserController的结构非常清晰才可以创建出来,实际开发中,UserController可能都不是我编写的;甚至整个User的业务我都没有参加,我只是想用一下UserController的某个功能;

  • 2)因为是直接通过new生成的具体对象,这是一种硬编码的方式,违反了面向接口编程的原则,程序间存在严重的耦合,如果有一天我们从MyBatis的Dao切换到Hiberante的Dao那么将会需要改动源代码;

  • 3)每一处使用UserController都需要这么几个步骤,频繁的创建对象,浪费资源;

4.1.2 使用IOC容器的情况

@Test
public void test4() {

    // 获取SpringIOC容器
    ApplicationContext app =
            new ClassPathXmlApplicationContext("spring.xml");

    // 从IOC容器中获取一个UserController,具体UserController是如何创建出来的,我不管
    UserController userController = (UserController) app.getBean("userController");

    userController.save();
}

使用IOC之后,我们只管向容器索取所需的Bean即可。IOC便解决了以下的问题:

  • 1)Bean之间的解耦,Bean不需要我来创建了,我们也不必对Bean的内部非常了解,只管像IOC容器获取即可;

  • 2)当dao类需要跟换时,只需要在spring的配置文件中更换dao的实现类即可,原来的代码都不行做任何的改动;

  • 3)IOC容器天生支持单例;

4.2 IOC概念

控制反转(Inversion of Control,缩写为IOC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

反转意味着我们把对象创建的控制权交出去,交给谁呢?交给IOC容器,由IOC容器负责创建特定对象,并填充到各个声明需要特定对象的地方。

4.3 IOC的实例化

在Spring 容器读取Bean配置创建Bean实例之前,必须对它进行实例化。只有在容器实例化后,才可以从容器里获取Bean实例并使用。

Spring提供了多种类型的容器实现。

  • BeanFactory:容器的基本实现。

  • ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFMTTbrX-1669719985321)(media/94.png)]

BeanFactory是Spring 框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory 。

无论使用何种方式,配置文件是相同的。

  • ConfigurableApplicationContext扩展于 ApplicationContext,新增加两个主要方法:refresh() 和 close() ,让ApplicationContext具有启动、刷新和关闭上下文的能力。

  • ClassPathXmlApplicationContext:通过classpath路径直接获得加载的xml文件(推荐使用)

ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
  • FileSystemXmlApplicationContext:通过文件路径来获得加载的xml文件。
ApplicationContext app = new FileSystemXmlApplicationContext("D:/spring.xml");
  • AnnotationConfigApplicationContext:基于配置类注解来创建IOC容器
ApplicationContext app=new AnnotationConfigApplicationContext(SpringConfig.class);

五、Bean的配置

5.1 创建Bean的方式

我们的Bean都交给了SpringIOC容器来进行管理,意味着Bean的创建由IOC容器来进行,SpringIOC容器提供了多种不同的方式来创建Bean;

  • 准备实体类:
@Data
public class Book {
    private String id;
    private String name;
    private Double price;

    public Book() {
        System.out.println("book初始化了");
    }
}

5.1.1 普通创建方式

普通创建方式就是之前我们案例中的创建方式

  • spring.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>标签:用于声明一个类,在启动Spring框架的时候根据该配置的类创建对象到容器里面
         name:设置对象名(唯一标识符)
         id:设置对象名(唯一标识符,功能和name一样)
         class:用于指定对象对应的类名
      -->
    <bean id="book01" name="book01" class="com.dfbz.entity.Book"></bean>
</beans>

5.1.2 工厂方式

  • BookFactory:
package com.dfbz.factory.factory;

import com.dfbz.entity.Book;

/**
 * @author lscl
 * @version 1.0
 * @intro: 图书工厂类
 */
public class BookFactory {
    public Book createBook(){
        return new Book();
    }
}
  • spring.xml:
<!--先把BookFactory放入到IOC容器中-->
<bean id="bookFactory" class="com.dfbz.factory.BookFactory"></bean>

<!--
    factory-bean:工厂类的名称
    factory-method: 创建bean的方法名称
-->
<bean id="book02" factory-bean="bookFactory" factory-method="createBook" ></bean>

5.1.3 静态工厂

  • BookStaticFactory:
package com.dfbz.factory;

import com.dfbz.entity.Book;

/**
 * @author lscl
 * @version 1.0
 * @intro: 图书工厂类(静态工厂)
 */
public class BookStaticFactory {
    public static Book createBook(){
        return new Book();
    }
}
  • spring.xml:
<!--
    class: 静态工厂类的全路径
    factory-method: 创建bean的方法名称
-->
<bean id="book03" class="com.dfbz.factory.BookStaticFactory" factory-method="createBook" ></bean>

5.1.4 FactoryBean方式

Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。

工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。

在这里插入图片描述

  • BookFactoryBean:
package com.dfbz.factory;

import com.dfbz.entity.Book;
import org.springframework.beans.factory.FactoryBean;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class BookFactoryBean implements FactoryBean<Book> {
    /**
     * SpringIOC容器创建bean时调用的方法
     * @return : 创建的JavaBean
     * @throws Exception
     */
    @Override
    public Book getObject() throws Exception {
        return new Book();
    }

    /**
     * 要创建的JavaBean的类型
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Book.class;
    }
}
  • spring.xml:
<!--
    通过Spring提供的FactoryBean来创建对象
    class: FactoryBean的实现类的全路径
-->
<bean id="book04" class="com.dfbz.factory.BookFactoryBean"></bean>

5.2 给Bean中属性赋值

我们前面创建的JavaBean,其属性值默认都是为空的;我们可以通过Spring的方式来给容器中的bean赋值;

5.2.1 通过构造器

通过Bean提供的构造方法来给Bean设置值

  • Book的构造函数:
public Book(String id, String name, Double price) {
    System.out.println("有参构造执行了..");
    this.id = id;
    this.name = name;
    this.price = price;
}

// 参数顺序不一致
public Book(Double price, String id, String name) {
    System.out.println("有参构造执行了..");
    this.id = id;
    this.name = name;
    this.price = price;
}
  • spring.xml
<!--通过构造函数的参数名进行属性的赋值-->
<bean id="book05" class="com.dfbz.entity.Book">
    <constructor-arg name="id" value="001"/>
    <constructor-arg name="name" value="《大学》"/>
    <constructor-arg name="price" value="28.9"/>
</bean>

<!-- 通过构造函数的索引进行属性的赋值 -->
<bean id="book06" class="com.dfbz.entity.Book">
    <constructor-arg index="0" value="002"/>
    <constructor-arg index="1" value="《中庸》"/>
    <constructor-arg index="2" value="38.9"/>
</bean>

<!-- 通过类型不同区分重载的构造器 -->
<bean id="book07" class="com.dfbz.entity.Book">
    <constructor-arg index="0" value="45.9" type="java.lang.Double"/>
    <constructor-arg index="1" value="003" type="java.lang.String"/>
    <constructor-arg index="2" value="《论语》"  type="java.lang.String"/>
</bean>

5.2.2 通过set方法

  • spring.xml:
<!-- 通过set方法进行属性的设置 -->
<bean id="book08" class="com.dfbz.entity.Book">
    <property name="id" value="004"></property>
    <property name="name" value="《孟子》"></property>
    <property name="price" value="29.8"></property>
</bean>

5.2.3 p命名空间

p名称空间:为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。

Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性。使用p命名空间后,基于XML的配置方式将进一步简化。

  • spring.xml:
<!-- 使用p名称设置属性值 -->
<bean id="book09" class="com.dfbz.entity.Book"
      p:id="005" p:name="《说岳全传》" p:price="88.8"/>

5.2.4 c命名空间

与带有p命名空间的XML快捷方式类似,Spring 3.1中引入的c命名空间允许内联属性来配置构造函数参数

<!-- 使用p名称设置属性值 -->
<bean id="book10" class="com.dfbz.entity.Book"
      c:id="006" c:name="《三国演义》" c:price="28.8"/>

5.3.5 级联赋值

级联赋值:有时候我们bean中的属性的类型并不是一个基本数据类型,而是一个对象,那么如何给这个对象中的某个属性进行赋值呢?

  • Emp:
@Data
public class Emp {
    private String id;
    private String name;
    
    // 所属部门
    private Dept dept = new Dept();
}
  • Dept:
@Data
public class Dept {

    private String id;
    private String name;
    private String location;
}
  • spring.xml:
<bean id="emp01" class="com.dfbz.entity.Emp">
    <property name="id" value="001"></property>
    <property name="name" value="张三"></property>

    <!--属性的级联设置-->
    <property name="dept.id" value="001"></property>
    <property name="dept.name" value="研发部"></property>
    <property name="dept.location" value="泰国"></property>
</bean>


<!--首先定义一个部门-->
<bean id="dept" class="com.dfbz.entity.Dept">
    <property name="id" value="002"></property>
    <property name="name" value="销售部"></property>
    <property name="location" value="老挝"></property>
</bean>

<bean id="emp02" class="com.dfbz.entity.Emp">
    <property name="id" value="002"></property>
    <property name="name" value="李四"></property>

    <!--使用ref属性进行引用数据类型的注入-->
    <property name="dept" ref="dept"></property>
</bean>

5.2.6 null值的处理

  • null值的处理:
<bean id="emp03" class="com.dfbz.entity.Emp">
    <property name="id" value="003"></property>
    <property name="name" value="王五"></property>

    <!--报错,会把null当做字符串-->
<!--        <property name="dept" value="null"></property>-->
    <property name="dept">
        <!--赋值null的正确写法-->
        <null />
    </property>
</bean>
  • 引用数据类型的处理:
<bean id="emp04" class="com.dfbz.entity.Emp">
    <property name="id" value="003"></property>
    <property name="name" value="王五"></property>

    <!--可以使用ref属性引用其他地方的dept-->
    <!--    <property name="dept" ref="dept" />-->
    <property name="dept">
        <!--可以使用<ref>标签引用其他地方的dept-->
        <!--        <ref bean="dept"></ref>-->

        <!--使用bean标签再定义一个-->
        <bean id="dept02" class="com.dfbz.entity.Dept">
            <property name="id" value="004"></property>
            <property name="name" value="市场部"></property>
            <property name="location" value="越南"></property>
        </bean>
    </property>
</bean>

5.3 赋值复杂类型

5.3.1 数组和集合

在Spring中可以通过一组内置的XML标签来配置集合属性

数组和List配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签可以通过指定简单的常量值,通过<ref>指定对其他Bean的引用。通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其<value>他集合。

数组的定义和List一样,都使用<list>元素。

配置java.util.Set需要使用<set>标签,定义的方法与List一样。

  • 修改dept(需要提供get/set方法):
// 部门员工
private List<Emp> empList = new ArrayList<>();

// 曾用名
private List<String> usedName=new ArrayList<>();
  • spring.xml:
<bean id="dept03" class="com.dfbz.entity.Dept">
    <property name="id" value="009"></property>
    <property name="name" value="研发部"></property>
    <property name="location" value="柬埔寨"></property>
    
    <property name="usedName">
        <list>
            <!--基本数据类型和String类型-->
            <value>技术部</value>
            <value>开发部</value>
        </list>
    </property>
    <property name="empList">
        <list>
            <!--引用数据类型-->
            <ref bean="emp01"></ref>
            <ref bean="emp02"></ref>
        </list>
    </property>
</bean>

5.3.2 properties和Map

  • 提供一个Pojo类:
@Data
public class Pojo {
    private Map map=new HashMap();
    private Properties props=new Properties();
}
  • spring.xml:
<bean id="pojo01" class="com.dfbz.entity.Pojo">

    <!--map类型-->
    <property name="maps">
        <map>
            <entry key="id" value="001"></entry>
            <entry key="name" value="《诗经》"></entry>
            <entry key="price" value="38.8"></entry>
        </map>
    </property>

    <property name="prop">
        <props>
            <prop key="id">001</prop>
            <prop key="name">《尚书》</prop>
            <prop key="price">39.8</prop>
        </props>
    </property>
</bean>

5.4 Bean的生命周期

Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。

Spring IOC容器对bean的生命周期进行管理的过程:

  • 1)默认情况下,在容器启动时,Spring会将所有的bean都初始化

  • 2)为bean的属性设置值(通过构造方法、set方法等)

  • 3)调用bean的初始化方法init

  • 4)bean可以使用了

  • 5)默认情况下,当容器关闭时,Spring会将所有的bean销毁

在配置bean时,通过bean标签的init-methoddestroy-method属性为bean指定初始化和销毁方法

  • 测试实体类:
@Data
public class Person {
    private String name;

    private Integer age;

    private String addr;

    public void init(){
        System.out.println("初始化了");
    }

    public void destroy(){
        System.out.println("销毁了");
    }
    public Person() {
        System.out.println("person创建了");
    }
}
  • spring.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">


    <!--
        init-method:bean初始化时调用的方法(构造方法执行之后)
        destroy-method:bean销毁时调用的方法
    -->
    <bean id="person" class="com.dfbz.entity.Person" init-method="init" destroy-method="destroy"></bean>

</beans>
  • 测试类型:
package com.dfbz.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    //获取SpringIOC容器
    ConfigurableApplicationContext app =
            new ClassPathXmlApplicationContext("spring2.xml");

    @Test
    public void test1() {

        Object person = app.getBean("person");
        System.out.println(person);

        app.close();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V2uC9wIX-1669719985322)(media/91.png)]

5.5 Bean的作用域

默认情况下,IOC容器中的bean都是单例的,而且是容器启动时,会创建所有的bean

@Test
public void test2() {

    Object person = app.getBean("person");
    Object person2 = app.getBean("person");
    
    System.out.println(person == person2);		// true,同一个bean只会初始化一份
}

可以通过lazy-init属性来调整加载的时机,等到用到的时候再加载:

<!--
    lazy-init: 懒加载配置
        true: 开启懒加载,等到用到的时候再加载bean
        false:立即加载,容器启动之前就创建好
-->
<bean id="person" class="com.dfbz.entity.Person" 
      init-method="init" destroy-method="destroy" lazy-init="true"></bean>

可以通过scpoe属性来调整bean的作用范围:

<!--
    scope: Bean的作用域
        singleton: 单例(默认值)
        prototype: 多例
        request: 在WEB环境中,一次请求创建一次
        session: 在WEB环境中,一次会话创建一次
        application: WEB环境中,创建了上下文对象时创建一次
        websocket: 在WEB环境中,创建了WebSocket时创建一次
-->
<bean id="person" class="com.dfbz.entity.Person" init-method="init" 
      destroy-method="destroy" lazy-init="true" scope="prototype"></bean>
  • 测试类:
@Test
public void test2() {
    Object person = app.getBean("person");
    Object person2 = app.getBean("person");

    System.out.println(person == person2);      //false

    app.close();        // 容器关闭时,多实例的bean不会随之销毁
}

注意:在多例中,容器创建时不会加载多实例的bean,而是等到具体用的时候再加载,每次获取的实例都是一个全新的实例,并且IOC容器销毁时,多实例的bean也不会随之销毁;而是将Bean的销毁交给了JVM的垃圾回收

5.6 后置处理器

Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理

bean后置处理器时需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor

  • 定义MyProcess:
package com.dfbz.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class MyProcess implements BeanPostProcessor {

    /**
     * 创建完对象后执行前置方法
     * @param bean: 要创建出来的bean
     * @param beanName: bean的名称
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【"+beanName+"】执行了前置方法,类型为: "+ bean.getClass());
        return bean;
    }

    /**
     * 对象的init方法执行完再执行后置方法
     * @param bean: 要创建出来的bean
     * @param beanName: bean的名称
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【"+beanName+"】执行了后置方法,类型为: "+ bean.getClass());
        return bean;
    }
}
  • spring.xml:
<!--注册后置处理器-->
<bean id="myProcess" class="com.dfbz.process.MyProcess"></bean>

<!--注册person对象-->
<bean id="person" class="com.dfbz.entity.Person"  init-method="init" destroy-method="destroy"></bean>

测试效果:

在这里插入图片描述

5.7 bean的其他属性

5.7.1 继承属性

<bean id="book" class="com.dfbz.entity.Book">
    <property name="id" value="001"></property>
    <property name="name" value="《礼记》"></property>
    <property name="price" value="36.9"></property>
</bean>

<!--
    代表继承person01的同名属性值
-->
<bean id="book01" class="com.dfbz.entity.Book" parent="book">
    <property name="id" value="002"></property>
    <property name="name" value="《春秋》"></property>
    <!-- price 也是36.9(被继承下来了)-->
</bean>

5.7.2 抽象属性

<!--设置abstract属性为true后,该类不能实例化-->
<bean id="book03" class="com.dfbz.entity.Book" abstract="true">
    <property name="id" value="003"></property>
    <property name="name" value="《中庸》"></property>
    <property name="price" value="38.9"></property>
</bean>

5.7.3 依赖属性

<?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">

    <!--
        depends-on: 创建当前对象时必须先创建depends-on指定的对象(多个对象以逗号隔开)
    -->
    <bean id="book" class="com.dfbz.entity.Book" depends-on="person,emp"></bean>
    <bean id="emp" class="com.dfbz.entity.Emp"></bean>

    <bean id="person" class="com.dfbz.entity.Person"></bean>
</beans>

5.8 引用外部属性文件

我们习惯性的把一些配置写在外部的xxx.properties文件中,Spring提供了context命名空间帮我们读取类路径下的配置文件

  • 引入mysql和druid连接池依赖:
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.1</version>
</dependency>
  • 准备一个配置文件:
jdbc.username=root
jdbc.password=admin
jdbc.url=jdbc:mysql://localhost:3306/db01
jdbc.driverClassName=com.mysql.jdbc.Driver
  • spring.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"
       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 https://www.springframework.org/schema/context/spring-context.xsd">
    <!--
        原来的写法
        <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="username" value="root"></property>
            <property name="password" value="admin"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/db01"></property>
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        </bean>
    -->
    <!--从类路径下加载jdbc.properties配置文件-->
    <context:property-placeholder location="jdbc.properties"/>
    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
    </bean>

</beans>

六、SpEL表达式

Spring表达式语言全称为:Spring Expression Language,简称SpEL,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。

和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样,SpEL根据JavaBean风格的getXxx()、setXxx()方法定义的属性访问对象图,完全符合我们熟悉的操作习惯。

6.1 使用普通字面量

整数:

<property name="count" value="#{5}"/>

小数:

<property name="price" value="#{89.7}"/>

科学计数法:

<property name="hits" value="#{1e4}"/>

String类型的字面量可以使用单引号或者双引号作为字符串的定界符号

<property name="name" value="#{'张三'}"/>

或者

<property name='name' value='#{"Chuck"}'/>

布尔类型:

<property name="enabled" value="#{false}"/>

引用其他bean:

<bean id="dept" class="com.dfbz.entity.Dept">
    <property name="id" value="001"></property>
    <property name="name" value="研发部"></property>
</bean>

<bean id="emp" class="com.dfbz.entity.Emp">
    <property name="id" value="#{'001'}" />
    <property name="name" value="#{'张三'}" />
    <property name="dept" value="#{dept}" />
</bean>

6.2 调用方法

Utils类:

package com.dfbz.util;

import java.util.Random;
import java.util.UUID;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Utils {

    /**
     * 使用普通方法生成id
     *
     * @return
     */
    public String getId() {
        return UUID.randomUUID().toString();
    }

    /**
     * 使用静态方法生成价格
     * @return
     */
    public static Double getPrice() {
        return Double.parseDouble((new Random().nextDouble() * 100 + "").substring(0, 4));
    }
}

spring.xml:


<bean id="util" class="com.dfbz.entity.Utils"></bean>

<bean id="book" class="com.dfbz.entity.Book">
    <!--使用对象调用普通方法-->
    <property name="id" value="#{util.getId()}"></property>

    <!--
        调用静态方法
        注意:要写全路径
    -->
<!--        <property name="id" value="#{T(java.util.UUID).randomUUID().toString()}"></property>-->

    <!--可以使用类名来调用-->
<!--        <property name="price" value="#{T(com.dfbz.util.Utils).getPrice()}"></property>-->
    
    <!--也可以使用对象名调用-->
    <property name="price" value="#{util.getPrice()}"></property>
</bean>

6.3 运算符

  • 算术运算符:+、-、*、/、%、^
<property name="test" value="#{5+1}"/>
<property name="test" value="#{5*3}"/>
  • 字符串连接:+
<property name="test" value="#{'用户名: '+'张三'}"/>
  • 比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge
<property name="test" value="#{30>60}"/>
  • 逻辑运算符:and, or, not, |
<property name="test" value="#{30>60 | 30<80}"/>
  • 三目运算符:判断条件?判断结果为true时的取值:判断结果为false时的取值
<property name="test" value="#{30>60? '30大于60啊':'30小于60啊'}"/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

緑水長流*z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值