Spring入门--概述、Log4j2框架和IoC容器

本文章适用于第12周spring课程内容的补充学习。
主要内容来源于视频课程的笔记:https://www.bilibili.com/video/BV1kR4y1b7Qc/?p=22&share_source=copy_web&vd_source=97444cc205d3d3ef369bafb60cf90ca0

一、概述

1.Spring是什么?

Spring是一款主流的Java EE轻量级开源框架,Spring的用途不仅局限于服务器端的开发。从简单性,可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring框架除了自己提供功能外还能提供整合其他技术和框架的能力。

这次学习的Spring版本为Spring6

2.Spring的狭义与广义

广义上的Spring: Spring技术栈
广义上的Spring泛指以Spring Framework为核心的Spring技术栈。Spring现在已经不是一个单纯的应用框架,而是而是逐渐发展为一个由多个不同子模块组成的成熟技术,例如Spring Framework, Spring MVC, Spring Boot, Spring Cloud, Spring Data等,其中Spring Framework是其他子项目的基础。

狭义上的Spring: Spring Framework
狭义上的spring特指Spring Framework, 通常我们将它称为Spring框架。
Spring框架是一个分层的、面向切面的Java应用程序的一站式轻量级解决方案,它是Spring技术栈 的核心与基础,是为了解决企业级应用开发的复杂性而创建的。
Spring有两个最核心的模块:IoC与AOP

IoC: Inverse of Control的缩写,意为“控制反转”,指把创建对象的过程交给Spring进行管理

AOP: Aspect Oriented Programming的简写,意为 "面向切面编程"。AOP用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外, AOP还解决一些系统层面上的问题,比如日志,事务,权限等。

3.Spring Framework的特点

(1)非侵入式:使用Spring Framework开发应用程序时,Spring对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能型组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构。,反而能将组件结构进一步简化。这就使得基于Spring Framework开发应用程序时结构清晰、简洁优雅。

(2)控制反转:Ioc -- Inversion of Control,反转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。

(3)面向切面编程:AOP -- Aspect Oriented Programming,在不修改源代码的基础上增强功能。

(4)容器:Spring IOC是一个容器,因为他包含并管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大地降低了使用门槛,提高了开发效率。

(5)组件化:Spring实现了使用简单的配件组合成一个复杂的应用。在Spring中可以使用XML和Java注解组合这些对象。这使我们可以基于一个一个功能明确、边界清晰的组件有条不紊的搭建超大型应用系统。

(6)一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在Spring Framework的基础上全部使用Spring来实现。

4.Spring模块组成

这些是对各模块的简单介绍,在后续的学习中会逐步了解它们。 

(1)Spring Core: 核心容器

  • spring-core: IOC和DI的基本实现
  • spring-beans: BeanFactory和Bean的装配管理
  • spring-context:Spring context上下文,即IOC容器
  • spring-expression:Spring表达式语言

(2)Spring AOP:

  • spring-aop:面向切面编程的应用模块,整合ASM, CGlib, JDK Proxy
  • spring-aspects:集成Aspectctj,AOP应用框架
  • spring-instrucment:动态Class Loading模块

(3)Spring Data Access:

  • spring-jdbc: Spring对JDBC的封装,用于简化jdbc操作
  • spring-orm: java对象与数据库数据的映射框架
  • spring-oxm: 对象与xml文件的映射框架
  • spring-jms: spring对java Message Service(Java消息服务)的封装,用于服务之间的通信

(4)Spring Web:

  • spring-web:最基础的web支持,建立于spring-context之上,通过Servlet或listener来初始化IOC容器
  • spring-webmvc: 实现web mvc
  • spring-websocket:与前端的全双工通信
  • spring-webflux: spring5.0提供的,用于取代传统java servlet,非阻塞式Reactive Web框架,异步,非阻塞,事件驱动的服务。

(5)Spring Message:

  • spring-messaging: spring4.0提供的,为spring集成一些基础的报文传送服务。

(6)Spring Test:

  • spring-test:集成测试支持,主要是对Junit的封装。

二、Spring开发入门

现在创建一个maven项目,进行Spring入门案例的测试
Spring: 6.0.2
JDK: 17
IDEA: 2023 社区版 

1.入门案例开发步骤 

这部分讲解如何创建并使用bean

文档结构:(这是一个Maven项目)

(1)引入Spring相关依赖

pom.xml配置文件中添加依赖:        

    <dependencies>        
        <!--context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.2</version>
        </dependency>

        <!--junit-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.6.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

(2)创建类,定义属性和方法

创建User类,这个类用来测试Bean,它的对象将由Spring进行创建。

package com.test1.demo;

public class User {
    public void add(){
        System.out.println("add");
    }
}

(3)按照Spring的要求来创建配置文件(.xml)

resource文件夹下创建配置文件bean.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-3.0.xsd">

    <!--完成User对象创建
    bean标签
        id属性:唯一标识
        class:创建对象所在类的全路径 (包名称+类名称)-->
    <bean id="user" class="com.test1.demo.User"> </bean>
</beans>

解析:
每个<bean>标签都应该由一个唯一的id,对其进行标识。
class属性值为创建对象所在类的全路径,即包名称+类名称,用来指定一个类。


(4)在xml文件中配置相关信息

相关信息即为<bean>标签所指定的信息(类),每一个bean标签对应着一个类。
除了<bean>标签外,我们以后还将会配置其他标签及其属性,实现不同的功能。


(5)进行最终测试

在主包中创建testUser类进行测试:

package com.test1.demo;

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

public class TestUser {
    @Test
    public void testUserObject(){
        //加载spring的配置文件,进行对象的创建
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //获取创建的对象
        User user = (User) context.getBean("user");
        System.out.println(user);
        //使用对象调用方法进行测试
        user.add();
    }
}

解析:
ApplicationContext对象context加载spring的配置文件,来创建文件中指定的类的对象。
调用context的getBean(String id)方法,获取它加载的类对象。这个方法的参数是在<bean>标签中设置好的id属性,它能够标识一个被记录的类。

测试结果:

可以看到,Spring通过反射创建了一个User对象,并能够调用它的add()方法。

2.如何使用反射创建对象?

下面的内容讲解,为什么我们通过调用contextgetBean()方法,就可以得到指定的类对象

(1) 类对象被创建的过程

  • 加载bean.xml文件
  • 对xml文件进行解析操作
  • 获取xml文件中bean标签中的属性值 -- id属性和class属性
  • 使用反射根据类的全路径创建对象(原理)
     
    Class clazz = Class.forName(class属性值)
    ​​​​​​​User user = (User)clazz.getDeclaredConstructor().newInstance();

可以发现,创建对象context时,它的构造函数通过反射技术,对xml文件中指定的类一股脑地创建出了它们的对象,让我们得以通过getBeans()获取到这些对象。

(2)创建的对象放在哪里?

Map<String, BeanDefinition> beanDefinitionMap变量中,对这些对象进行存储。
Key:唯一标识(id)
Value:类的定义(描述信息)

DefaultListableBeanFactory.class中有这个变量的定义:(源码)

类的描述信息(Value)有哪些呢?
打开BeanDefiniton,可以看到:

其中,beanClassName, Scope等都是类的描述信息。
我们通常把BeanDefinition接口叫为:Bean的定义对象。

三、整合Log4j2日记框架

在之前的学习中,我们大致了解了如何构建一个Spring项目。同时在spring的广义定义上,我们知道,它还可以整合其他框架,实现更复杂的功能。在接下来的学习中,我们将使用spring整合Log4j2日记框架进行学习。
 

1.Log4j2日志概述

在项目开发中,日志非常重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态的时候能够及时提醒我们处理,同时在系统产生问题的时候,能够帮助我们快速地定位、诊断并解决问题。

Apache Log4j2是一个开源的日志记录组件,使用非常广泛。在工程中易用方法替代了System.out等打印语句。

Log4j2由几个重要的组件构成:

(1)日志信息的优先级:下面的信息优先级由低到高依次排列

TRACE: 追踪,是最低的日志级别,相当于追踪程序的执行
DEBUG: 调试,一般在开发中,都将其设置为最低的日志级别
INFO: 信息,输出重要的信息,使用较多
WARN: 警告,输出警告的信息
ERROR: 错误,输出错误信息
FATAL: 严重错误

这些级别分别用来指定这条信息的重要程度,级别高的信息会自动屏蔽级别低的日志信息。

(2)日志信息的输出目的地:日志信息的输出目的地指定了将日志打印到控制台还是文件中。

(3)日志信息的输出格式:日志信息的输出格式控制了日志信息的显示内容。

2.整合日志的操作

 文档结构:

(1)引入Log4j2的依赖

pom.xml中,引入对Log4j2框架的依赖(类似于springFrame的依赖)

        <!--Log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>

(2)加入日志配置文件

resource包中,创建Log4j2.xml配置文件,用来控制日志的输出

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <loggers>
<!--        TRACE: 追踪,是最低的日志级别,相当于追踪程序的执行-->
<!--        DEBUG: 调试,一般在开发中,都将其设置为最低的日志级别-->
<!--        INFO: 信息,输出重要的信息,使用较多-->
<!--        WARN: 警告,输出警告的信息-->
<!--        ERROR: 错误,输出错误信息-->
<!--        FATAL: 严重错误-->

        <!--日志信息的级别-->
        <root level="DEBUG">
            <!--日志输出的信息,对应着下面三个name-->
            <appender-ref ref="spring6log"/>
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="log"/>
        </root>
    </loggers>

    <appenders>
        <!--输出日志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序都会自动清空,由append属性决定,适合-->
        <File name="log" fileName="d:/C_Java/testSpring/Spring6/info/test.log" append="false">
            <PatternLayout pattern="%d{HH:mm:ss SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!--这个会打印出所有信息,
        每次大小超过size,
        这个大小为size的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
        作为存档-->
        <RollingFile name="RollingFile" fileName="d:/C_Java/testSpring/Spring6/info/app.log"
                     filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="50MB"/>
            <!--DefaultRolloverStrategy属性如不设置
            则默认最多同一文件夹下有7个文件,这里设置了20-->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </appenders>
</configuration>

解析:
log4j2.xml被标签分为两个部分,一是<loggers>标签,另一个是<appenders>标签。

<loggers>标签中的内容:
<root>:每个配置都应该具有这个标签,它的属性level表示日志信息的级别。
<appender-ref>: 是root的子节点,用来指定该日志输出到哪个appender。

<appenders>标签中的内容:
<console>:用于定义输出到控制台的Appender。
<File>: 用于定义输出到指定位置的文件的Appender。
<RollingFile>: 定义指定方式触发新的Appender。

对于这些标签的使用及它们的属性,可以参考博客:
log4j2的使用及配置详解_log4j2 additivity-CSDN博客
 

(3)对日志输出进行测试

 依旧使用TestUser类进行测试:

可以看到,新增了三条控制台输出。同时,在你之前设置的文件夹下,可以看到新增了test.log和app.log两篇日志。它们分别对应着三条日志信息的输出。
 

3.补充说明

(1)Appender的使用方法

Log4j2使用Appender将日志数据写到各种目标位置(控制台、文件、多种数据库API、远程套接字服务器等)。

Appender可以通过特定的Appender插件名或appender元素来配置,每个Appender都要有一个name属性,用来指定区别于其他appender的唯一标识。这个标识的值在Logger的appender-ref中被引用(见上面的loggers标签),从而将Appender配置到该Logger中。

(2)常用的Appender--ConsoleAppender

ConsoleAppender会将输出写入System.out或者System.err中。必须提供一个Layout来格式化LogEvent。简单地来说,这个标签(console)将日志信息输出到控制台。

(3)常用的Appender--RollingFileAppender

RollingFileAppender会自动输出到filename参数指定的文件中,并且需要指定TriggeringPolicyRolloverStrategy。其中TriggeringPolicy决定是否生成新的日志文件,RolloverStrategy决定如何生成新的日志文件。

4.手动书写日志

在程序中,我们也可以直接手动书写日志,而不使用xml文件进行配置。
接下来在testUser测试类中进行手动书写日志的测试。

    //创建Logger对象
    private Logger logger = LoggerFactory.getLogger(TestUser.class);

    @Test
    public void testUserObject(){
        //加载spring的配置文件,进行对象的创建
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //获取创建的对象
        User user = (User) context.getBean("user");
        System.out.println(user);
        //使用对象调用方法进行测试
        user.add();

        //手动写日志
        logger.info("###执行调用成功");
    }

输出:

 

四、容器 IoC

1.概述

IoC是Inversion of Control的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们设计出松耦合、更优良的程序。

Spring通过IoC容器来管理所有Java对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由IoC容器管理的Java对象称为Spring Bean,它与使用关键字new创建的Java对象没有任何区别。

IoC容器是Spring框架中最重要的核心组件之一,它贯穿了Spring从诞生到成长的整个过程。
 

(1)控制反转

控制反转是一种思想,目的是降低程序耦合度,提高程序扩展力。
控制反转,反转的是什么?将对象的创建权力交出去,交给第三方容器负责;将对象和对象之间的维护权交出去,交给第三方容器负责。
控制反转这种思想如何实现?DI(Dependency Injection): 依赖注入。
 

(2)依赖注入

依赖注入实现了控制反转的思想。
指Spring创建对象的过程中,将对象依赖属性通过配置进行注入。

依赖注入常见的实现方式:
set注入
构造注入

Bean管理:Bean对象的创建,以及Bean对象中属性的赋值(或者叫Bean对象之间关系的维护)
 

(3)IoC容器在Spring的实现

Spring的IoC容器就是IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做bean。在创建bean之前,首先要创建IoC容器。Spring提供了IoC容器的两种创建方式:

BeanFactory
这是IoC容器的基本实现,使用Spring内部使用的接口。面向Spring本身,不提供给开发者使用。

ApplicationContext (主要)
BeanFactory的子接口,提供了更多的高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。

Application的主要实现类:

ClassPathXmlApplicationContext通过读取类路径下的xml格式的配置文件创建IoC容器对象
FileSystemXmlApplicationContext通过文件系统路径读取xml格式的配置文件创建IoC容器对象
ConfigurableApplicationContext

ApplicationContext的子接口,包含一些扩展方法refresh()和close(),让Application具有活动,关闭和刷新上下文功能

WebApplicationContext专门为web应用准备,基于web环境创建IoC容器对象,并将对象引入存入ServletContext域中

(4)IoC容器如何创建Bean

xml配置文件
        |  Bean定义信息
        |  BeanDefinition
       V
     抽象   BeanDefinitionReader
        |
        |
       V        BeanFactory工厂+反射
IoC容器  | Bean定义信息--> 实例化        |
               |                             初始化        |
               |                             最终对象     |

类中:context.getBean("类名") 获取最终的类对象

2.基于XML管理Bean

在上面的学习中,我们对bean的创建和获取方法有了一定认识:
创建实体类--->在xml文件中将这个类设置为bean--->在要调用这个类时,使用getBean()方法

(1)搭建子模块test-xml

项目结构:

这个模块用来测试使用xml管理bean,为了方便,可以将依赖添加到父模块的pom.xml中。

  • 创建User类(将通过这个类进行创建bean的测试)
package com.test.demo1;

public class User {
    private String name;
    private Integer age;

    public void run(){
        System.out.println("run");
    }
}

(2)实验一:获取bean

方式1:根据 id获取
由于id属性指定了bean的唯一标识,所以根据bean标签的id属性可以精确地获取到一个组件对象。上个实验中我们使用的就是这种方式。
将上个实验中的bean.xml和resource.xml配置文件复制到这个项目中,再次进行测试。

方式2:根据类型获取 (见2)
方式3:根据id和类型获取 (见3)

public class TestUser {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

        //1.根据id获取bean
        User user = (User) context.getBean("user");
        System.out.println("根据id获取bean: " + user);

        //2.根据类型获取bean
        User user2 =context.getBean(User.class);
        System.out.println("根据类型获取bean: " + user2);

        //3.根据id+类型获取bean
        User user3 = context.getBean("user", User.class);
        System.out.println("根据id和类型获取bean: " + user3);
    }
}

注意:当根据类型获取bean的时候,要求IoC容器中指定类型的bean有且只能有一个。
当IoC容器中一共配置了两个bean:

    <bean id="user1" class="com.test.demo1.User"> </bean>
    <bean id="user2" class="com.test.demo1.User"> </bean>

则根据类型获取时会抛出异常:
No qualifying bean of type 'com.test.demo1.User' available: expected single matching bean but found 2: user,user2

扩展知识:
如果组件类实现了接口,根据接口类型可以获取bean吗?
可以,前提是bean唯一

如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗
不行,因为bean不唯一

如图,UserDao接口有两个实现,且这两个实现类均配置了bean:

    <bean id="userDaoImpl" class="com.test.demo1.bean.UserDaoImpl"> </bean>
    <bean id="userDaoImpl" class="com.test.demo1.bean.UserDaoImpl2"> </bean>

这时,再通过接口类型获取bean,则会报错。

        UserDao userDao = context.getBean(UserDao.class);
        System.out.println("根据接口类型获取Bean: " + userDao);
        userDao.run();

结论:
根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:对象inatanceof指定的类型 的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

(3)实验二:依赖注入之setter注入

  • 首先创建一个实体类Book来测试setter注入,这个类有两个成员变量bname和author。
public class Book {
    private String bname;
    private String author;

    public void setBname(String bname) {
        this.bname = bname;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
    //设置toString方法
    @Override
    public String toString() {
        return "Book{" +
                "bname='" + bname + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}
  • 接下来编写配置文件bean-di.xml:
    <!--set方法注入-->
    <bean id="book" class="com.test.demo1.di.Book">
        <property name="bname" value="Java"></property>
        <property name="author" value="hwh"></property>
    </bean>

现在,读取配置文件时,加载<bean "book">时,就会调用set方法,对context中的对象进行赋值

  • 创建测试类TestBook
public class TestBook {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
        Book book = context.getBean("book", Book.class);
        System.out.println(book);
    }
}

运行结果:
 可以看到,context中的book对象已经被赋值。

(4)实验三:依赖注入之构造器注入

区别:类中要有有参数的构造函数。

  • 在配置文件bean-di.xml中添加构造器注入的方式:
    <!--构造器注入-->
    <bean id="book2" class="com.test.demo1.di.Book">
        <constructor-arg name="bname" value="Spring"> </constructor-arg>
        <constructor-arg name="author" value="hwh"> </constructor-arg>
    </bean>

其中,constructor-arg表示类的有参构造的参数,name为参数名称,value为参数值。

注意:

如果你只在Book类中设置了有参构造,而没有添加无参构造,那么当你创建ApplicationContext对象context = new ClassPathXmlApplication("xxx.xml"); 时,会报错
原因我们之前学习过,context解析xml文件,创建类对象并将它们存入map中。所以此时所有在xml文件中被标记的类都将被创建出对象。而之前设置的类对象"book"是无参构造出的对象,此时Book类中没有无参构造函数,创建这个对象时就会报错。

构造器注入和setter注入最直观的区别:标签的区别
setter注入在bean中使用<property>来指定变量名和值。
构造器注入在bean中使用<constructor-arg>来指定变量名和值。

(5)实验四:特殊值处理

  • 字面量赋值

什么是字面量?
eg. int  a = 10;
声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a时,实际上拿到的值是10。
如果a是带引号的'a',那么它就不是一个变量,而是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的数据本身(不代表变量)。         

    <bean id="book2" class="com.test.demo1.di.Book">
        //此时,bname和Spring都不代表任何变量,只是表示字符串本身
        <constructor-arg name="bname" value="Spring"> </constructor-arg>
        <constructor-arg name="author" value="hwh"> </constructor-arg>
    </bean>
  • 空值

如果某个bean中的一个属性为空值,如何进行设置?不能写value = "null"。如图所示,在<constructir-arg>的属性中只指定name值,在标签中添加<null/>。

    <bean id="book3" class="com.test.demo1.di.Book">
        <constructor-arg name="bname" value="Mybatis"> </constructor-arg>
        <constructor-arg name="author">
            <null/>
        </constructor-arg>
    </bean>
  • xml实体

"<"小于号在XML文档中用来定义标签的开始,不能随意使用。
解决方案:使用XML实体代替。

    <bean id="book3" class="com.test.demo1.di.Book">
        <constructor-arg name="bname" value="Mybatis"> </constructor-arg>
        <!--&lt;表示<  &gt;表示> -->
        <constructor-arg name="author" value="&lt;&gt;"> </constructor-arg>
    </bean>
  • CDATA节(另一个解决方案)

CDATA表示纯文本数据,当XML解析器看到CDATA节就知道这里是纯文本,就不会当作XML标签属性来解析。

    <bean id="book3" class="com.test.demo1.di.Book">
        <constructor-arg name="bname" value="Mybatis"> </constructor-arg>
        <constructor-arg name="author">
            <!--  <![CDATA[]]>  在[]中书写你的文本-->
            <value><![CDATA[a < b]]></value>
        </constructor-arg>
    </bean>

(6)实验五:为对象类属性赋值

首先,先创建一个新的文件夹,添加两个类:员工类Emp和部门类Dept:

并完成这两个类属性的配置:

package com.test.demo1.ditest;

//部门类
public class Dept {
    private String Dname;

    //setter
    ......

    public void info(){
        System.out.println("部门名称: " + Dname);
    }
}
package com.test.demo1.ditest;

//员工类
public class Emp {
    private String Ename;
    private Integer age;

    //创建另一个类的对象
    Dept dept;

    //setter
    ......

    public void work(){
        dept.info();
        System.out.println("Emp is working");
    }
}

记得如果使用setter方式注入,要添加set方法。

新建test.xml来并进行配置:

方式一:引用外部bean
<?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-3.0.xsd">

<!--    第一种方式:引入外部bean
        1.创建两个类对象dept和emp
        2.在emp的bean标签里,使用property引入dept的bean-->

<!--部门类-->
    <bean id="dept" class="com.test.demo1.ditest.Dept">
        <property name="dname" value="安保部"> </property>
    </bean>
<!--员工类-->
    <bean id="emp" class="com.test.demo1.ditest.Emp">
        <!--普通属性注入-->
        <property name="ename" value="tom"> </property>
        <property name="age" value="40"> </property>
        <!--注入对象类型属性-->
        <property name="dept" ref="dept"> </property>
    </bean>

</beans>

如图,首先创建dept类的bean,再将其作为一个成员变量储存再emp对象中。
注意:如何区别deft这个值是对象还是普通的String值?使用ref=xxx来表示传入的值是类对象

接下来创建一个测试类Test5进行测试:

public class Test5 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
        //员工对象
        Emp emp = context.getBean("emp", Emp.class);
        emp.work();
    }
}

执行效果:

方法二:内部bean
    <!--第二种方式 内部bean注入-->
    <bean id="emp2" class="com.test.demo1.ditest.Emp">
        <property name="ename" value="jack"> </property>
        <property name="age" value="30"> </property>
        <property name="dept">
            <!--在emp2的dept属性值下,配置一个bean-->
            <bean id="dept2" class="com.test.demo1.ditest.Dept">
                <property name="dname" value="财务部"> </property>
            </bean>
        </property>
    </bean>
方法三:级联赋值
    <!--第三种方式 级联赋值-->
    <bean id="dept3" class="com.test.demo1.ditest.Dept"> </bean>

    <bean id="emp3" class="com.test.demo1.ditest.Emp">
        <property name="ename" value="jerry"> </property>
        <property name="age" value="34"> </property>
        <property name="dept" ref="dept3"> </property>
        <property name="dept.dname" value="技术研发部"> </property>
    </bean>

注意:使用这种方法配置时,应该在Emp类中创建getDept方法,否则Bean无法通过get方法获取到emp对象中的dept属性,自然也无法为这个属性进行赋值(级联赋值)。对于一般的属性(不是类对象的),Bean直接调用set方法就可以对它们进行赋值。

简单地说,如果对类赋值,可以进行级联赋值,但是要通过get方法先获取这个成员类的对象。

(7)实验六:数组属性赋值

在类中,属性可能是数组,拥有多个不同的值,接下来学习bean如何给这些属性赋值。

首先在Emp类中新增int[]类型的属性hobbies和set方法:

    private String[] hobbies;

    public void setHobbies(int[] hobbies) {
        this.hobbies = hobbies;
    }
    //print()方法用于观察配置情况
    public void pring(){
        for (String hobby : this.hobbies){
            System.out.println(hobby);
        }
    }

然后再配置bean-diarray.xml文件:

    <bean id="emp" class="com.test.demo1.ditest.Emp">
        <!--普通属性-->
        <property name="ename" value="张三"> </property>
        <property name="age" value="31"> </property>
        <!--对象类型属性-->
        <property name="dept">
            <!--内部bean-->
            <bean id="dept1" class="com.test.demo1.ditest.Dept">
                <property name="dname" value="技术部"> </property>
            </bean>
        </property>
        <!--数组类型属性-->
        <property name="hobbies">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
    </bean>

注意:配置数组的各个元素时,在property标签下设置array标签并分别赋值即可。

(8)实验七:为集合类型属性赋值

为List属性赋值

在Dept类中添加List<Emp>类型的属性,以及get和set方法,并设置输出的函数:

    private List<Emp> employees;

    //getter and setter
    ......

    public void info(){
        System.out.println("部门名称: " + Dname);
        for (Emp emp : employees){
            System.out.println(emp.toString());
        }
    }

<!--创建三个Emp类的bean-->
    <bean id="emp1" class="com.test.demo1.ditest.Emp">
        <property name="ename" value="张三"> </property>
        <property name="age" value="23"> </property>
    </bean>
    <bean id="emp2" class="com.test.demo1.ditest.Emp">
        <property name="ename" value="李四"> </property>
        <property name="age" value="33"> </property>
    </bean>
    <bean id="emp3" class="com.test.demo1.ditest.Emp">
        <property name="ename" value="王五"> </property>
        <property name="age" value="43"> </property>
    </bean>
    
    <!--数组类型属性-->
    <bean id="dept1" class="com.test.demo1.ditest.Dept">
        <property name="dname" value="技术部"> </property>
        <property name="employees">
            <list>
                <ref bean="emp1"/>
                <ref bean="emp2"/>
                <ref bean="emp3"/>
            </list>
        </property>
    </bean>

与数组的赋值很相似,List容器赋值所用的标签是<List>,值得注意的是,你为它赋的值是自定义类,索引<List>内部的标签应该使用<ref bean="xxx">而非<value="xxx">。

为Map属性赋值

现在假设我们拥有两个类:Student和Teacher,对应着师生关系,一门课程对应着一个老师。
创建Student类,其中拥有一个Map类型的属性。

package com.test.demo1.dimap;

import java.util.Map;

public class Student {
    private int sid;
    private String snmae;

    private Map<String, Teacher> teacherMap;

    public void run(){
        System.out.println("Student name: " + snmae + " Student id: " + sid);
        System.out.println(teacherMap);
    }
    //getter and setter
    .......
}

编写xml文件来创建Bean:

<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-3.0.xsd">
    <!--
    1.创建两个类的对象
    2.注入普通类型的属性
    3.在学生bean中注入Map类型的属性-->
    <bean id="teacher" class="com.test.demo1.dimap.Teacher">
        <!--注入普通类型的属性-->
        <property name="teacherId" value="100"> </property>
        <property name="teacherName" value="王老师"> </property>
    </bean>

    <bean id="student" class="com.test.demo1.dimap.Student">
        <!--注入普通类型的属性-->
        <property name="sid" value="001"> </property>
        <property name="sname" value="张三"> </property>
        <property name="teacherMap">
            <!--说明: 首先,使用<map>标签注入map类型的值
            然后在一个<entry>标签中设置一个键值对
            <key>中表示建的值,在<value>标签中为这个键进行赋值
            <key>外是value(值)的值,由于它是Teacher类型,使用<ref>标签赋值-->
            <map>
                <entry>
                    <key>
                        <value>10010</value>
                    </key>
                    <ref bean="teacher"/>
                </entry>
            </map>
        </property>
    </bean>
</beans>
引用集合类型bean

现在在Student类中新增一种属性List<Lesson>,以表示该学生要上的课,其中Lesson是自定义类

    private List<Lesson> lessonList;

配置xml文件:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd">
    <!--
    1.创建三个对象
    2.注入普通类型属性
    3.使用util:类型 定义
    4.在学生的bean引入util:类型定义bean,完成list、map类型注入-->

    <bean id="lesson" class="com.test.demo1.dimap.Lesson">
        <property name="lessonName" value="java开发"> </property>
    </bean>
    <bean id="lesson2" class="com.test.demo1.dimap.Lesson">
        <property name="lessonName" value="前端开发"> </property>
    </bean>
    <bean id="teacher" class="com.test.demo1.dimap.Teacher">
        <property name="teacherName" value="王老师"> </property>
        <property name="teacherId" value="00001"> </property>
    </bean>
    <bean id="teacher2" class="com.test.demo1.dimap.Teacher">
        <property name="teacherName" value="刘老师"> </property>
        <property name="teacherId" value="00002"> </property>
    </bean>
    <bean id="student" class="com.test.demo1.dimap.Student">
        <property name="sid" value="10000"> </property>
        <property name="sname" value="张三"> </property>
        <!--注入list、map类型-->
        <property name="lessonList" ref="lessonList"> </property>
        <property name="teacherMap" ref="teacherMap"> </property>
    </bean>

    <!--这是一个名为lessonList,储存两个Lesson类对象的List数组-->
    <util:list id="lessonList">
        <ref bean="lesson"/>
        <ref bean="lesson2"/>
    </util:list>

    <!--这是一个名为teacherMap,储存一个<String,Teacher>键值对的Map-->
    <util:map id="teacherMap">
        <entry>
            <key>
                <value>10010</value>
            </key>
            <ref bean="teacher"/>
        </entry>
    </util:map>

</beans>

3.值得注意的问题:

(1)在使用setter方式进行注入的时候,你通常使用<property>name属性来指定你要进行赋值的成员变量,Bean会调用相应的setter方法对其进行赋值。此时,需要注意:你的setter方法应该被命名为:set+变量名,否则这个方法将无法被识别。

(2)当你使用接口类型的值来创建bean时,应该确保它的实现类只有一个。

(3)Ioc容器如何创建和管理bean。

(4)使用<util>标签时应当在在<beans>标签中添加:
        xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation=""中添加:
        ​​​​​​​http://www.springframework.org/schema/util         http://www.springframework.org/schema/util/spring-util.xsd

在后续的内容中将继续介绍Ioc容器管理bean的方法,全注解开发以及AOP相关概念。

五、参考文章:

​​​​​​​Log4j2详解——XML配置详解 - 简书 (jianshu.com)

14.容器:IoC-基于XML管理Bean-环境搭建_哔哩哔哩_bilibili

  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值