首页 > 其他分享 >Spring-IoC容器

Spring-IoC容器

时间:2025-01-12 16:15:29浏览次数:1  
标签:容器 String Spring void public bean logger IoC class

Spring-IoC容器

Spring IoC容器又称为Spring容器,是Spring架构的核心组件,它负责管理应用程序中对象(通常称为bean,bean是IoC容器中的对象实例)的创建、配置、生命周期和依赖关系。

IoC容器的基本概念

IoC容器又称为容器,是一种设计模式,用于管理应用程序中对象的创建、配置、生命周期和依赖关系。容器是实施控制反转(IoC)原则的一种方式,它将对象的管理责任从应用程序代码转移到容器中。

控制反转(IoC)

控制反转是一种思想。传统上,对象的控制权是在程序员手中。在IoC中,这个控制权被反转,将对象的创建权利、对象和对象之间关系的维护权利均交给容器管理,从而实现解耦和灵活配置。

依赖注入(DI)

容器通过依赖注入机制将对象依赖的属性通过配置进行注入。依赖注入可以是构造器注入、属性注入、方法注入。依赖注入实现了控制反转的思想。

bean

bean是指由容器创建、配置、组装、管理的对象。这些对象的生命周期和依赖关系有容器负责管理,从而实现了控制反转和依赖注入。

容器在Spring中的实现

Spring中的容器就是IoC思想的一个实现。IoC容器中创建、配置、组装、管理的对象称为bean。在创建bean之前,首先需要创建容器,下面介绍一下Spring 容器的接口。

BeanFactory

BeanFactory是Spring Ioc容器的最基本接口,提供了基础的依赖注入和Bean管理功能。

这个接口是由包含很多bean定义的对象实现的,每个bean定义由一个String名称唯一标识。根据bean定义,工厂将返回包含对象的独立实例(Prototype设计模式)或单个共享实例。返回哪种类型实例取决于bean factory的配置。

自Spring 2.0开始,使用BeanFactory的子接口ApplicationContext可以使用更多的功能(推荐使用ApplicationContext)。

ApplicationContext

ApplicationContext是BeanFactory的子接口,除了拥有BeanFactory的所有功能外,还提供了更多的功能。

ApplicationContext提供了:

  • bean工厂方法用于访问应用程序组件-继承自ListableBeanFactory接口
  • 以通用方式加载文件资源的能力-继承自ResourceLoader接口
  • 向已注册的监听器发布时间的能力-继承自ApplicationEvntPublisher接口
  • 解析消息的能力,支持国际化-继承自MessageSource接口

常见的ApplicationContext实现类

  • ClassPathXmlApplicationContext
从类路径下的XML配置文件中加载bean的定义。
  • AnnotationConfigApplicationContext
用于基于注解的Java配置(如@Configuration注解)来加载Spring配置类。
  • GenericWebApplicationContext
用于Web应用程序的容器,适用于基于Spring Web的应用。

加载ApplicationContext(初始化Spring 容器)的示例

Spring提供了不同的方式加载ApplicationContext,最常见的是通过XML配置、Java配置类或注解来初始化Spring容器,这里先简单演示一下这两种方式,后面会具体讲如何使用

通过XML配置文件加载ApplicationContext

java类:

//将此类定义为spring bean
@Component
public class MyBean {
}

xml文件:

<!-- applicationContext.xml -->
<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="myBean" class="com.example.MyBean"/>
</beans>

java加载文件:

// 使用 ClassPathXmlApplicationContext 来加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过ApplicationContext的方法获取到bean对象
MyBean myBean = (MyBean) context.getBean("myBean");

通过Java配置类加载ApplicationContext

java类:

//添加注解,将此类定义为spring bean
@Component
public class MyBean {
}

java配置类:

@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}

java加载文件:

// 使用 AnnotationConfigApplicationContext 来加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);

基于XML管理bean

引入依赖

pom文件:

        <!--spring context依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.11</version>
        </dependency>
        <!--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>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!-- 数据源 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.15</version>
        </dependency>

log4j2日志配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                TRACE < DEBUG < INFO < WARN < ERROR < FATAL
                trace:追踪,是最低的日志级别,相当于追踪程序的执行
                debug:调试,一般在开发中,都将其设置为最低的日志级别
                info:信息,输出重要的信息,使用较多
                warn:警告,输出警告的信息
                error:错误,输出错误信息
                fatal:严重错误
        -->
        <root level="DEBUG">
            <appender-ref ref="console"/>
            <appender-ref ref="rolling-file"/>
        </root>
    </loggers>

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

        <!-- 这个会打印出所有的信息,
            每次大小超过size,
            则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
            作为存档-->
        <RollingFile name="rolling-file" fileName="/opt/logs/spring6-demo/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="10"/>
        </RollingFile>
    </appenders>
</configuration>

获取bean的三种方式

  1. 根据id获取
  2. 根据类型获取
  3. 根据id和类型获取
注: 如果是根据类型获取bean时,指定类型的bean有且仅有一个,否则会报错(报根据类型找到多个)。

获取bean的示例

用于被创建实例的类

public class UserEntity {
    private final static Logger logger = LoggerFactory.getLogger(UserEntity.class);
    private String id;
    private String name;

    public void printHello() {
        logger.info("hello");
    }
}

用于定义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.xsd">

    <!-- 获取bean演示 UserEntity对象创建-->
    <bean id="userEntity" class="com.shen.springiocxml.entity.UserEntity"></bean>
    <!-- 当两个相同类型的bean时,通过类型获取bean会报异常-->
<!--    <bean id="UserEntity1" class="com.shen.springiocxml.entity.UserEntity"></bean>-->

</beans>

测试类

public class TestUserEntity {
    private final static Logger logger = LoggerFactory.getLogger(TestUserEntity.class);

    public static void main(String[] args) {
        //创建spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //1.根据id获取bean
        logger.info("根据id获取bean");
        UserEntity userEntity1 = (UserEntity) context.getBean("userEntity");
        userEntity1.printHello();
        logger.info("获取bean的hashcode:{}", userEntity1.hashCode());
        //2.根据类型获取bean
        logger.info("根据类型获取bean");
        UserEntity userEntity2 = context.getBean(UserEntity.class);
        userEntity2.printHello();
        logger.info("获取bean的hashcode:{}", userEntity2.hashCode());
        //3.根据id和类型获取bean
        logger.info("根据id和类型获取bean");
        UserEntity userEntity3 = context.getBean("userEntity", UserEntity.class);
        userEntity3.printHello();
        logger.info("获取bean的hashcode:{}", userEntity3.hashCode());
    }
}

接口实现类根据接口类型获取bean的实例

接口实现类可以根据接口类型获取bean,如果是根据类型获取bean时,同样需要指定接口类型的bean唯一

接口类

public interface UserService {
    public void printRun();
}

接口实现类

public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Override
    public void printRun() {
        logger.info("run====================");
    }
}

定义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.xsd">
    <!-- 一个接口实现类获取过程-->
    <bean id="userServiceImpl" class="com.shen.springiocxml.service.impl.UserServiceImpl"></bean>
</beans>

测试类

public class TestUserServiceImpl {
    private static final Logger logger = LoggerFactory.getLogger(TestUserServiceImpl.class);

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //根据类型获取接口对应的bean
        UserService userService = context.getBean(UserService.class);
        logger.info("userService instanceof UserServiceImpl result:{}", userService instanceof UserServiceImpl);
        logger.info("输出得到的对象:{}", userService);
        userService.printRun();
    }
}

依赖注入

依赖注入(Dependency Injection DI)是一种通过构造器参数、工厂方法参数(构造方法的依赖注入)或者在对象实例构造后、工厂方法返回后设置对象的属性(基于setter的依赖注入),来定义对象的依赖关系(即该对象所依赖的其他对象)的过程。容器在创建bean时会注入这些依赖。这个过程与正常代码创建一个对象相反,bean本身通过类的构造器或者Service Locator模式来控制其依赖项的实例化(因此得名为控制反转-Inversion of Control)

使用DI原则的代码更加简洁,当对象通过依赖注入获取依赖时,解耦效果更为显著。对象不再查找其依赖项,也不知道依赖项的类或位置,因此,当依赖项时接口或抽象类时,你的类会变得更容易进行单元测试,因为可以在单元测试中使用存根(stub)或模拟(muck)。

依赖注入有两种方式:

  1. 基于构造方法的依赖注入
  2. 基于setter的依赖注入

依赖注入的示例

用于创建实例的类

public class BookEntity {
    private String name;
    private String author;

    public BookEntity() {
    }

    //全参构造方法
    public BookEntity(String name, String author) {
        this.name = name;
        this.author = author;
    }

    //setter方法
    public void setName(String name) {
        this.name = name;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "BookEntity{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}

用于定义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.xsd">

    <!--1.set方式注入-->
    <bean id="bookEntity3" class="com.shen.springiocxml.entity.BookEntity">
        <!-- property标签:通过组件类的setXxx()方法给组件对象设置属性 -->
        <property name="name" value="英语"></property>
        <property name="author" value="李华"></property>
    </bean>

    <!--2.全参构造器方式注入-->
    <bean id="bookEntity4" class="com.shen.springiocxml.entity.BookEntity">
        <!-- constructor-arg标签:通过组件类的构造方法给组件对象设置属性 -->
        <!--name属性:指定参数名-->
        <constructor-arg name="name" value="物理"></constructor-arg>
        <!--index属性:指定参数所在位置的索引(从0开始)-->
<!--        <constructor-arg index="0" value="物理"></constructor-arg>-->
        <constructor-arg name="author" value="小王"></constructor-arg>
<!--        <constructor-arg index="1" value="小王"></constructor-arg>-->
    </bean>
</beans>

测试类

public class TestBookEntity {
    private static final Logger logger = LoggerFactory.getLogger(TestBookEntity.class);

    public static void main(String[] args) {
        //常规方式注入的两种方式(set和全参构造器)
        BookEntity bookEntity1 = new BookEntity();
        bookEntity1.setName("语文");
        bookEntity1.setAuthor("小明");
        logger.info("常规方式注入-set方式,对象打印:{}", bookEntity1);
        BookEntity bookEntity2 = new BookEntity("数学", "小李");
        logger.info("常规方式注入-全参构造器方式,对象打印:{}", bookEntity2);

        ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
        //spring基于xml方式 set注入方式
        BookEntity bookEntity3 = context.getBean("bookEntity3", BookEntity.class);
        logger.info("ioc注入-set方式,对象打印:{}", bookEntity3);
        //spring基于xml方式 全参构造器注入方式
        BookEntity bookEntity4 = context.getBean("bookEntity4", BookEntity.class);
        logger.info("ioc注入-全参构造器方式,对象打印:{}", bookEntity4);
    }
}

依赖注入-特殊值处理

常规值

不存在转义,就是数据本身的值

<property name="name" value="小明"></property>

null值

<property name="author">
    <null/>
</property>

xml实体

由于在xml配置文件中‘<’和'>'符号是定义标签的,如果想赋给属性需要使用转义符号
&lt; 表示'<'符号
&gt; 表示'>'符号
<!--给name属性赋值为<hello>-->
<property name="author" value="&lt;hello&gt;"></property>

CDTA

CDTA(Character Data)表示字符数据,用于包含不应由XML解析器解析的文本数据

格式为: <![CDATA[文本内容]]>

在CDTA部分中的所有内容都会被XML解析器视为普通文本,不会进行任何解析或转义
<!--给author属性赋值为a < b-->
<property name="author"> <value><![CDATA[a < b]]></value></property>

依赖注入-不同类型属性的赋值

对象类型属性的赋值

对象类型属性的赋值有三种方式:

  1. 引用外部的bean
  2. 内部bean
  3. 级联属性赋值(不常用)
对象类型属性赋值示例

被引用的类

public class DeptEntity {
    private String dName;

    @Override
    public String toString() {
        return "DeptEntity{" +
                "dName='" + dName + '\'' +
                '}';
    }

    public String getdName() {
        return dName;
    }

    public void setdName(String dName) {
        this.dName = dName;
    }
}

包含对象类型属性的类

public class EmpEntity {
    private String eName;
    private String age;
    private DeptEntity deptEntity;

    @Override
    public String toString() {
        return "EmpEntity{" +
                "eName='" + eName + '\'' +
                ", age='" + age + '\'' +
                ", deptEntity=" + deptEntity +
                '}';
    }

    public DeptEntity getDeptEntity() {
        return deptEntity;
    }

    public void setDeptEntity(DeptEntity deptEntity) {
        this.deptEntity = deptEntity;
    }

    public String geteName() {
        return eName;
    }

    public void seteName(String eName) {
        this.eName = eName;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

定义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.xsd">


    <!--    方法1-->
    <bean id="deptEntityOne" class="com.shen.springiocxml.entity.DeptEntity">
        <property name="dName" value="财务部"></property>
    </bean>

    <bean id="empEntityOne" class="com.shen.springiocxml.entity.EmpEntity">
        <property name="eName" value="小王"></property>
        <property name="age" value="23"></property>
        <!--        引入外部bean ref的值为对应bean的id-->
        <property name="deptEntity" ref="deptEntityOne"></property>
    </bean>

    <!--    方法2-->
    <bean id="empEntityTwo" class="com.shen.springiocxml.entity.EmpEntity">
        <property name="eName" value="小刘"></property>
        <property name="age" value="35"></property>
        <!--        内部bean-->
        <property name="deptEntity">
            <bean id="deptEntityTwo" class="com.shen.springiocxml.entity.DeptEntity">
                <property name="dName" value="研发部"></property>
            </bean>
        </property>
    </bean>

    <!--    方法3(不常用)-->
    <bean id="deptEntityThree" class="com.shen.springiocxml.entity.DeptEntity">
        <property name="dName" value="销售部"></property>
    </bean>

    <bean id="empEntityThree" class="com.shen.springiocxml.entity.EmpEntity">
        <property name="eName" value="小张"></property>
        <property name="age" value="35"></property>
        <!--        级联赋值-->
        <property name="deptEntity" ref="deptEntityThree"></property>
        <property name="deptEntity.dName" value="测试部"></property>
    </bean>
</beans>

测试类

public class TestEmpEntity {
    public static final Logger logger = LoggerFactory.getLogger(TestEmpEntity.class);

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-object-di.xml");
        EmpEntity empEntityOne = context.getBean("empEntityOne", EmpEntity.class);
        logger.info("emlEntityOne :{}", empEntityOne);
        EmpEntity empEntityTwo = context.getBean("empEntityTwo", EmpEntity.class);
        logger.info("emlEntityTwo :{}", empEntityTwo);
        EmpEntity empEntityThree = context.getBean("empEntityThree", EmpEntity.class);
        logger.info("empEntityThree :{}", empEntityThree);
    }
}

数组类型属性的赋值

当属性为数组类型时,需要使用array标签表示该属性为数组,在value标签中添加实际数组中元素的值

数组类型属性的赋值示例
<?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="deptEntity" class="com.shen.springiocxml.entity.DeptEntity">
        <property name="dName" value="财务部"></property>
    </bean>

    <bean id="empEntity" class="com.shen.springiocxml.entity.EmpEntity">
        <property name="hobby">
            <array>
                <value>抽烟</value>
                <value>喝酒</value>
                <value>烫头</value>
            </array>
        </property>
        <property name="eName" value="小王"></property>
        <property name="age" value="23"></property>
        <!--        引入外部bean ref的值为对应bean的id-->
        <property name="deptEntity" ref="deptEntity"></property>
    </bean>
</beans>

集合类型属性的赋值

当属性为集合类型时,需要使用list、set、map标签表示该属性为集合

list集合示例

list和set类似区别在于标签名不同

被引用的类

public class EmpEntity {
    public static final Logger logger = LoggerFactory.getLogger(EmpEntity.class);
    private String eName;
    private String age;
    private DeptEntity deptEntity;

    private String[] hobby;

    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    public void printHobby() {
        for (String s : this.hobby) {
            logger.info("{}", s);
        }
    }

    @Override
    public String toString() {
        return "EmpEntity{" +
                "eName='" + eName + '\'' +
                ", age='" + age + '\'' +
                ", deptEntity=" + deptEntity +
                '}';
    }

    public DeptEntity getDeptEntity() {
        return deptEntity;
    }

    public void setDeptEntity(DeptEntity deptEntity) {
        this.deptEntity = deptEntity;
    }

    public String geteName() {
        return eName;
    }

    public void seteName(String eName) {
        this.eName = eName;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

包含集合属性的类

public class DeptEntity {
    private static final Logger logger = LoggerFactory.getLogger(DeptEntity.class);
    private String dName;

    private List<EmpEntity> empList;

    public List<EmpEntity> getEmpList() {
        return empList;
    }

    public void setEmpList(List<EmpEntity> empList) {
        this.empList = empList;
    }

    @Override
    public String toString() {
        return "DeptEntity{" +
                "dName='" + dName + '\'' +
                '}';
    }

    public String getdName() {
        return dName;
    }

    public void setdName(String dName) {
        this.dName = dName;
    }

    public void printEmpList() {
        if (this.empList == null) {
            return;
        }
        for (EmpEntity empEntity : this.empList) {
            logger.info("{}", empEntity);
        }
    }
}

定义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.xsd">

    <bean id="empEntityOne" class="com.shen.springiocxml.entity.EmpEntity">
        <property name="eName" value="小王"></property>
        <property name="age" value="23"></property>
    </bean>

    <bean id="empEntityTwo" class="com.shen.springiocxml.entity.EmpEntity">
        <property name="eName" value="小刘"></property>
        <property name="age" value="35"></property>
    </bean>

    <bean id="deptEntityOne" class="com.shen.springiocxml.entity.DeptEntity">
        <property name="dName" value="财务部"></property>
        <property name="empList">
            <list>
                <!--                如果list中的元素时基本类型,可以用value标签表示-->
                <!--                <value>s</value>-->
                <ref bean="empEntityOne"></ref>
                <ref bean="empEntityTwo"></ref>
            </list>
        </property>
    </bean>
</beans>

测试类

public class TestDeptEntity {
    private static final Logger logger = LoggerFactory.getLogger(TestDeptEntity.class);

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-collection-di.xml");
        DeptEntity deptEntityOne = context.getBean("deptEntityOne", DeptEntity.class);
        deptEntityOne.printEmpList();
    }
}
map集合示例

被引用的类

public class TeacherEntity {
    private String tid;
    private String tname;

    public String getTid() {
        return tid;
    }

    public void setTid(String tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    public void setTname(String tname) {
        this.tname = tname;
    }

    @Override
    public String toString() {
        return "TeacherEntity{" +
                "tid='" + tid + '\'' +
                ", tname='" + tname + '\'' +
                '}';
    }
}

包含集合属性的类

public class StudentEntity {
    private Logger logger = LoggerFactory.getLogger(StudentEntity.class);
    private int sid;
    private String sname;
    private Map<String, TeacherEntity> teacherMap;

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public Map<String, TeacherEntity> getTeacherMap() {
        return teacherMap;
    }

    public void setTeacherMap(Map<String, TeacherEntity> teacherMap) {
        this.teacherMap = teacherMap;
    }

    public void printStudentInfo() {
        logger.info("学生id:[{}],学生姓名:[{}]", this.sid, this.sname);
        if (this.teacherMap == null) {
            return;
        }
        for (Map.Entry<String, TeacherEntity> entityEntry : this.teacherMap.entrySet()) {
            logger.info("{}课程老师信息:", entityEntry.getKey());
            logger.info("{}", entityEntry.getValue());
        }
    }

}

定义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.xsd">

    <bean id="teacherEntityOne" class="com.shen.springiocxml.entity.TeacherEntity">
        <property name="tid" value="1"></property>
        <property name="tname" value="张老师"></property>
    </bean>

    <bean id="teacherEntityTwo" class="com.shen.springiocxml.entity.TeacherEntity">
        <property name="tid" value="2"></property>
        <property name="tname" value="李老师"></property>
    </bean>

    <bean id="studentEntityOne" class="com.shen.springiocxml.entity.StudentEntity">
        <property name="sid" value="1"></property>
        <property name="sname" value="小王"></property>
        <property name="teacherMap">
            <map>
                <entry>
                    <key>
                        <value>语文</value>
                    </key>
                    <!--                    如果是基本类型使用value即可-->
                    <!--                    <value></value>-->
                    <ref bean="teacherEntityOne"></ref>
                </entry>

                <entry>
                    <key>
                        <value>数学</value>
                    </key>
                    <ref bean="teacherEntityOne"></ref>
                </entry>
            </map>
        </property>
    </bean>
</beans>

测试类

public class TestStudentEntity {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-collection-map-di.xml");
        StudentEntity studentEntityOne = context.getBean("studentEntityOne", StudentEntity.class);
        studentEntityOne.printStudentInfo();
    }
}

p命名空间

在Spring框架中,p命名空间是一个用于简化XML配置文件的特性,特别是在进行依赖注入时。p命名空间允许你以属性的方式直接配置bean的属性,而不是使用嵌套的property标签。这种配置方式更加简洁。

p命名空间只能用于设置属性的值,不能用于设置复杂的属性,如集合、映射等。

当使用p:属性名-ref时,则表示引用其他的bean。

p命名空间简化了配置,但不会影响Spring容器的功能,底层仍然是通过setter方式完成依赖注入的。

XML配置文件中引入p命名空间

<?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:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>  

使用p命名空间进行属性注入

<?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="deptEntityOne" class="com.shen.springiocxml.entity.DeptEntity"
          p:dName="财务部">
    </bean>

    <bean id="empEntityOne" class="com.shen.springiocxml.entity.EmpEntity"
          p:eName="小王"
          p:age="23"
          p:deptEntity-ref="deptEntityOne"
    >
    </bean>
</beans>

引入外部属性文件

如果想要引入外部属性值,则需要引入context命名空间,使用context命名空间声明外部属性文件的路径

属性文件

jdbc.user=root
jdbc.password=123456
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver

引入context命名空间的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: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
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--    需要引入context的命名空间,才能使用该标签-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="druidDataSourceOne" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
</beans>

测试类

public class TestDruidDataSource {
    private static final Logger logger = LoggerFactory.getLogger(TestDruidDataSource.class);

    public static void main(String[] args) {
        //常规方式生成Druid数据源对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        logger.info("常规方式生成DruidDataSource对象的url: {}", druidDataSource.getUrl());
        //spring方式生成Druid数据源对象
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml");
        DruidDataSource druidDataSource1 = context.getBean("druidDataSourceOne", DruidDataSource.class);
        logger.info("spring方式生成DruidDataSource对象的url: {}", druidDataSource1.getUrl());
    }
}

bean的作用域

在Spring框架中,bean的作用域定义了bean的生命周期和可见性。Spring支持多种作用域,以下是常见的bean作用域

1.单例模式(singleton)

默认的作用域。在Spring容器中,一个bean定义只生成一个实例。这个实例在spring容器启动时被创建,并在整个应用程序生命周期中重复使用。
优点: 性能高,不需要每次请求时都创建新实例。
缺点:不支持并发场景中的线程安全。
<!--写法-->
<bean id="mySingletonBean" class="com.example.MyClass" scope="singleton"/>

2.原型模式(prototype)

每次请求bean时,spring容器都会创建一个新的实例。
优点:支持并发场景,每个线程都可以拥有自己的bean实例。
缺点:性能较多,因为需要频繁创建和销毁实例。
<!--写法-->
<bean id="myPrototypeBean" class="com.example.MyClass" scope="prototype"/>

3.请求模式(request)

每次HTTP请求都会创建一下新的bean实例。仅适用于web应用程序。
优点:适用于web应用,每个请求都有自己的bean状态。
缺点:不适用于非web应用。
<!--写法-->
<bean id="myRequestBean" class="com.example.MyClass" scope="request"/>

4.会话模式(session)

每个HTTP会话都会创建一个新的bean实例。仅适用于web应用程序。
优点:适用于需要维护会话状态的应用。
缺点:不适用于非web应用。
<!--写法-->
<bean id="mySessionBean" class="com.example.MyClass" scope="session"/>

5.应用模式(application)

每个servletContext都会创建一个新的bean实例。仅适用于web应用程序。
优点:适用于整个web应用共享的数据。
缺点:不适用于非web应用
<!--写法-->
<bean id="myApplicationBean" class="com.example.MyClass" scope="application"/>

bean作用域的示例

用于创建实例的类

public class OrderOne {
}
public class OrderTwo {
}

用于定义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.xsd">


    <!--    单实例-->
    <bean id="orderOne" class="com.shen.springiocxml.scope.OrderOne" scope="singleton"></bean>
    <!--    多实例-->
    <bean id="orderTwo" class="com.shen.springiocxml.scope.OrderTwo" scope="prototype"></bean>
</beans>

测试类

public class TestOrder {
    private static final Logger logger = LoggerFactory.getLogger(TestOrder.class);

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");
        //单实例在spring容器初始化时创建
        OrderOne orderOne1 = context.getBean("orderOne", OrderOne.class);
        logger.info("单实例创建orderOne1对象的地址:{}", orderOne1);
        OrderOne orderOne2 = context.getBean("orderOne", OrderOne.class);
        logger.info("单实例创建orderOne2对象的地址:{}", orderOne2);
        //多实例在获取bean时创建
        OrderTwo orderTwo1 = context.getBean("orderTwo", OrderTwo.class);
        logger.info("多实例创建orderTwo1对象的地址:{}", orderTwo1);
        OrderTwo orderTwo2 = context.getBean("orderTwo", OrderTwo.class);
        logger.info("多实例创建orderTwo2对象的地址:{}", orderTwo2);
    }
}

bean的生命周期

在spring框架中,bean的生命周期指的式一个bean从创建到销毁的整个过程。spring容器负责管理bean的生命周期。bean的生命周期主要有以下阶段。

1.实例化

spring容器根据bean定义创建bean的实例。可以通过构造器或者工厂方法进行实例化。

2.属性赋值

容器为bean的属性注入值(依赖注入),这些值可以通过xml配置、注解或java配置类指定。

3.初始化前

在bean属性赋值完成后,初始化前阶段允许执行一些自定义逻辑。
可以通过实现BeanPostProcess接口的postProcessBeforeInitialization方法来添加自定义逻辑。

4.初始化

容器调用bean的初始化方法。可以通过以下三种方式指定初始化方法:
1.实现InitializingBean接口的afterPropertiesSet方法。
2.在XML配置中使用init-method属性指定自定义初始化方法。
3.使用@PostConstruct注解标记的方法。

5.初始化后

在bean初始化完成后,初始化后阶段允许执行一些自定义逻辑。
可以通过实现BeanPostProcessor接口的postProcessAfterInitialization方法来添加自定义逻辑。

6.使用

bean现在可以被应用程序的其他部分使用。

7.销毁前

在bean销毁前,允许执行一些清理工作。
可以通过实现DestructionAwateBeanPostProcessor接口的postProcessBeforeDestruction方法来 添加自定义逻辑。

8.销毁

容器调用bean的销毁方法。可以通过以下三种方式指定销毁方法:
1.实现DisposableBean接口的destory方法。
2.在XML配置中使用destory-method属性指定自定义销毁方法。
3.使用@PreDestory注解标记的方法。

9.垃圾回收

bean最终被垃圾回收器回收,释放资源。

bean生命周期的示例

用于创建实例的类

public class UserDemo {
    private static final Logger logger = LoggerFactory.getLogger(UserDemo.class);
    private String name;

    public UserDemo() {
        logger.info("1.调用无参数构造,创建bean对象");
    }

    public void initMethod() {
        logger.info("4.bean对象初始化,调用指定的初始化");
    }

    public void destroyMethod() {
        logger.info("7.bean对象销毁,调用指定销毁的方法");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        logger.info("2.对bean对象设置属性值");
        this.name = name;
    }

    @Override
    public String toString() {
        return "UserDemo{" +
                "name='" + name + '\'' +
                '}';
    }
}

BeanPostProcessor接口的实现类

public class MyBeanPost implements BeanPostProcessor {
    private static final Logger logger = LoggerFactory.getLogger(MyBeanPost.class);

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        logger.info("3.bean后置处理器,初始化之前执行");
        logger.info("bean name:[{}],bean is [{}]", beanName, bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        logger.info("5.bean后置处理器,初始化之后执行");
        logger.info("bean name:[{}],bean is [{}]", beanName, bean);
        return bean;
    }

}

定义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.xsd">

    <bean id="userDemo" class="com.shen.springiocxml.life.UserDemo"
          scope="singleton" init-method="initMethod" destroy-method="destroyMethod">
        <property name="name" value="张三"></property>
    </bean>

    <!--    后置处理器放入ioc容器才能生效-->
    <bean id="myBeanPost" class="com.shen.springiocxml.life.MyBeanPost"></bean>
</beans>

测试类

public class TestUserDemo {
    private static final Logger logger = LoggerFactory.getLogger(TestUserDemo.class);

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml");
        UserDemo userDemo = context.getBean("userDemo", UserDemo.class);
        logger.info("6.对象已创建,可以被使用");
        logger.info("{}", userDemo);
        context.close();
    }
}

Spring中的FactoryBean接口

在Spring框架中,FactoryBean是一个接口,用于实现复杂的初始化逻辑,它允许你创建一个可以生成其他对象的工厂。FactoryBean本身是一个bean,但它的主要目的是生产其他bean。这种设计模式在spring中很有用,尤其是在需要创建复杂对象或者需要控制对象创建过程的情况。

在整合Mybatis时,Spring就是通过FactoryBean机制来创建SqlSessionFactory对象的。

FactoryBean接口定义了三个方法:
1.T getObject() throws Exception;
这个方法返回由FactoryBean创建的实例。这是客户端代码通过Spring容器获取到的实际对象。

2.Class<?> getObjectType();
返回由getObject()方法返回的对象类型。

3.default boolean isSingleton() {
		return true;
	}
返回由getObject()方法返回的对象是否是单例,默认是单例返回true

使用FactoryBean的过程:
1.

使用FactoryBean的过程

  1. 定义FactoryBean实现:创建一个类实现FactoryBean接口
  2. 配置Spring容器: 在Spring配置文件中声明FactoryBean接口实现类
  3. 获取对象: 通过Spring容器获取FactoryBean接口实现类要生成的目标对象,获取FactoryBean实现类的bean时会自动调用getObject()方法,获取目标bean

FactoryBean的示例

FactoryBean接口实现类要生成的bean的类

public class UserDemoTwo {
}

FactoryBean接口实现类

public class MyFactoryBean implements FactoryBean<UserDemoTwo> {
    @Override
    public UserDemoTwo getObject() throws Exception {
        return new UserDemoTwo();
    }

    @Override
    public Class<?> getObjectType() {
        return UserDemoTwo.class;
    }
}

定义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.xsd">

    <bean id="userDemoTwo" class="com.shen.springiocxml.factorybean.MyFactoryBean"></bean>
</beans>

测试类

public class TestUserDemoTwo {
    private static final Logger logger = LoggerFactory.getLogger(TestUserDemoTwo.class);

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-factorybean.xml");
        UserDemoTwo testUserDemoTwo = (UserDemoTwo) context.getBean("userDemoTwo");
        logger.info("{}", testUserDemoTwo);
    }
}

基于XML的自动装配

自动装配的概念

Spring中的自动装配是指Spring容器能够自动识别对象之间的依赖关系,并自动建立这些依赖关系的过程。这种机制可以大大简化bean的配置,使得开发者不再需要显式(ref元素)地在配置文件中指定bean之间的依赖关系。

自动装配的方式

Spring提供了几种自动装配的方式,可以通过autowire属性来指定:
1.no:默认方式,不进行装配,依赖必须通过ref元素来定义。
2.byName:根据bean的名称进行自动装配。Spring会查找与依赖属性名相同的bean(id与属性名相同)进行注入。
3.byType: 根据bean的类型进行自动装配。Spring会查找与依赖属性类型相同的bean(class与属性类型相同)进行注入。
4.constructor:类似于byType,但它是针对构造函数的参数进行自动装配。Spring会查找与构造函数参数类型相同的bean进行注入。
5.autodetect:Spring会尝试通过构造函数自动装配(constructor),如果不可用,则会按类型自动装配(byType)

自动装配的优缺点

优点:

  • 减少了配置文件中的XML配置,使得配置更加简洁。
  • 提高开发效率,减少了手动配置依赖的工作量。

缺点:

  • 当容器中存在多个相同类型的bean时,可能会出现自动装配失败的情况。
  • 对于复杂依赖关系,自动装配不够灵活。

基于xml的自动装配示例

控制层接口类

/**
 * 用户控制层接口类
 */
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void invokeServiceMethod() {
        logger.info("控制层对象调用service方法");
        //常规调用方式
//        UserService userService = new UserServiceImpl();
//        userService.invokeDaoMethod();
        userService.invokeDaoMethod();


    }

业务接口与业务接口实现类

/**
 * 用户业务类
 */
public interface UserService {
    void invokeDaoMethod();
}
/**
 * 用户业务实现类
 */
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void invokeDaoMethod() {
        logger.info("业务层对象调用Dao方法");
        //常规调用方式
//        UserDao userDao = new UserDaoImpl();
//        userDao.printHello();

        userDao.printHello();
    }
}

数据传输接口与数据传输接口实现类

public interface UserDao {
    void printHello();
}
/**
 * 数据传输实现类
 */
public class UserDaoImpl implements UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);

    @Override
    public void printHello() {
        logger.info("数据传输对象打印hello");
        logger.info("hello");
    }
}

定义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.xsd">

    <!--    根据类型自动装配-->
    <!--    <bean id="userController" class="com.shen.springiocxml.auto.controller.UserController" autowire="byType"></bean>-->

    <!--    <bean id="userService" class="com.shen.springiocxml.auto.service.impl.UserServiceImpl" autowire="byType"></bean>-->

    <!--    <bean id="userDao" class="com.shen.springiocxml.auto.dao.impl.UserDaoImpl"></bean>-->

    <!--    根据名称自动装配,需要保证id名与实例名一致-->

    <bean id="userController" class="com.shen.springiocxml.auto.controller.UserController" autowire="byName"></bean>

    <bean id="userService" class="com.shen.springiocxml.auto.service.impl.UserServiceImpl" autowire="byName"></bean>

    <bean id="userDao" class="com.shen.springiocxml.auto.dao.impl.UserDaoImpl"></bean>

</beans>

测试类

/**
 * 测试类
 */
public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml");
        UserController userController = context.getBean("userController", UserController.class);
        userController.invokeServiceMethod();
    }
}

基于注解管理bean

Java注解和Spring注解

Java注解

Java注解的概念
Java注解是使用@interface关键字定义的。
注解可以包含元素(类似于方法),这些元素可以有默认值。
注解可以用于修饰类、方法、字段、参数等。
Java中常见的内置注解
  • @Override: 表示当前方法覆盖了父类的方法。
  • @Deprecated:表示当前元素已过时,不推荐使用。
  • @SuppressWarnings:抑制编译器警告。
Java中的自定义注解
开发者可以自定义注解,用于特定场景或目的。
自定义注解可以包含元注解,如@Retention(保留策略)、@Target(目标元素类型)等。
示例
public @interface MyAnnotation {
    String value() default "default";
}

@MyAnnotation(value = "example")
public class MyClass {
    // ...
}
Java注解的应用场景
  • 提供元数据,如作者、版本等。
  • 编译时检查,如@Override。
  • 运行时处理,如通过反射读取注解信息。

Spring注解

Spring注解的概念
Spring注解时Spring框架提供的,基于Java注解实现的。
这些注解用于简化Spring应用的配置,如依赖注入,组件扫描,事务管理,切面编程等。
常见的Spring注解
  • 依赖注入:@Autowired,@Value
  • 组件扫描:@ComponentScan
  • 定义bean:@Component,@Service,@Repository,@Controller
  • 事务管理:@Transactional
  • 切面编程:@Aspect,@Pointcut,@Before,@After,@AfterReturning,@AfterThrowing,@Around。
应用场景
  • 简化Spring配置,减少XML配置文件的使用。
  • 提供声明式事务管理。
  • 实现面向切面编程(AOP)。

基于注解的自动装配

通过注解实现自动装配的步骤

  1. 引入Spring相关依赖
  2. 开启组件扫描
  3. 使用注解定义bean
  4. 依赖注入(两种注解注入方式)

引入Spring相关依赖

        <!--spring context依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.11</version>
        </dependency>
        <!--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>
        <!--        @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【**如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。**】-->
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>

开启组件扫描

Spring默认不使用注解装配bean,因此需要在XML配置文件中引入context命名空间,通过context:component-scan元素开启bean的自动扫描功能。开启之后,Spring会自动从指定的包中扫描,对使用定义bean注解(@Component,@Service,@Repository,@Controller)的类自动注册为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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--    开启组件扫描-->
    <context:component-scan base-package="com.shen.springiocannotation"></context:component-scan>

</beans>

使用注解定义bean

Spring提供了多个注解,这些注解功能相同,均作用于类上,将该类自动注册为bean,只是应用的场景不同。

注解 说明
@Component 通用注解,适用于任何层次,用于标记一个类为spring容器中的bean
@Controller 作用于控制层的注解,用于标记一个控制层的类为spring容器中的bean
@Service 作用于业务层的注解,用于标记一个业务层的类为spring容器中的bean
@Repository 作用于数据访问层的注解,用于标记一个数据访问层的类为spring容器中的bean

依赖注入

注解形式的依赖分为两种方式:使用Spring注解@Autowired或者使用Java注解@Resource。

@Autowired注解与@Resource注解的区别
  1. 来源和规范
@Autowired: 是Spring框架自带的注解,遵循Spring的依赖注入规范。
@Resource: 是Java JSR-250规范中的注解,由Java官方提供,是一种通用的依赖注入方式。
  1. 注入方式
@Autowired: 默认按照类型进行自动注入,如果找到多个相同类型的bean,会根据名称进行匹配。可以使用@Qualifier注解来指定要注入的bean的名称。
@Resource: 默认按照名称进行注入,名称由name属性,如果未指定name,则使用字段名或方法名作为默认名称。如果找不到匹配的名称,则按照类型进行注入。
  1. 属性支持
@Autowired: 可以用于构造方法、属性、方法参数、配置类中的@Bean方法参数。
@Resource: 通常用于属性和方法的注入,不常用于构造函数注入。
  1. 循环依赖
@Autowired: Spring支持通过构造函数的循环依赖,因为它在创建bean的早期阶段就解决了依赖关系
@Resource: 由于它是通过JNDI(Java Naming and Directory Interface)查找来实现注入的,所以不支持构造函数的循环依赖。
使用Spring注解@Autowired进行依赖注入

@Autowired默认根据类型进行装配。

@Autowired注解源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}
@Autowired注解进行依赖注入的方式
  1. 构造方法上注入
  2. set方法上注入
  3. 形参上注入
  4. 属性上注入
  5. 类中只有一个构造方法时不需要注解
  6. 与@Qualifier注解结合通过名称进行注入
示例

控制层

@Controller
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    //注入Service
    //第一种方式 属性注入-根据属性类型找到对应对象,完成注入 不用构建set方法
//    @Autowired
//    private UserService userService;

//    //第二种方式set方法注入
//    private UserService userService;
//
//    @Autowired
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }

////    第三种方式 构造方法注入
//    private UserService userService;
//
//    @Autowired
//    public UserController(UserService userService) {
//        this.userService = userService;
//    }

//    //第四种方式 形参上注入
//    private UserService userService;
//
//    public UserController(@Autowired UserService userService) {
//        this.userService = userService;
//    }

    //    第五种方式 只有一个构造方法,不需要注解
    private UserService userService;

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

    public void printHello() {
        logger.info("控制层调用打印方法");
        userService.printHello();
    }
}

业务层

public interface UserService {
    void printHello();
}
@Service
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

//    @Autowired
//    private UserDao userDao;

    //第六种方式 @Autowired和@Qualifier注解 根据名称自动装配
    @Autowired
    @Qualifier(value = "userOneDaoImpl")
    private UserDao userDao;

    @Override
    public void printHello() {
        logger.info("业务层调用打印方法");
        userDao.printHello();
    }
}

数据访问层

public interface UserDao {
    void printHello();
}
@Repository
public class UserOneDaoImpl implements UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserOneDaoImpl.class);

    @Override
    public void printHello() {
        logger.info("one");
    }
}
@Repository
public class UserDaoImpl implements UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);

    @Override
    public void printHello() {
        logger.info("数据传输层打印hello");
        logger.info("hello");
    }
}

开启组件扫描的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: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
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--    开启组件扫描-->
    <context:component-scan base-package="com.shen.springiocannotation"></context:component-scan>

</beans>

测试类

public class TestUserController {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserController userController = context.getBean(UserController.class);
        userController.printHello();
    }
}
使用Java注解@Resource进行依赖注入

@Resource默认根据名称进行装配,当注解没有指定name时会把属性名当作name,如果根据name找不到则会根据类型装配。

@Resource注解源码
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
    /**
     * The JNDI name of the resource.  For field annotations,
     * the default is the field name.  For method annotations,
     * the default is the JavaBeans property name corresponding
     * to the method.  For class annotations, there is no default
     * and this must be specified.
     */
    String name() default "";

    String lookup() default "";

    Class<?> type() default java.lang.Object.class;

    enum AuthenticationType {
	    CONTAINER,
	    APPLICATION
    }
    
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;

    boolean shareable() default true;

    String mappedName() default "";

    String description() default "";
}
示例

控制层

@Controller("myUserController")
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

//    //根据名称进行注入
//    @Resource(name = "myUserService")
//    private UserService userService;

    //既没有指定名字,属性名也不一致的情况下通过类型进行注入
    @Resource
    private UserService userService;



    public void printHello() {
        logger.info("控制层调用打印方法");
        userService.printHello();
    }
}

业务层

public interface UserService {
    void printHello();
}
@Service("myUserService")
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    //没有指定名称根据属性名进行注入
    @Resource
    private UserDao myUserDao;


    @Override
    public void printHello() {
        logger.info("业务层调用打印方法");
        myUserDao.printHello();
    }
}

数据访问层

public interface UserDao {
    void printHello();
}
@Repository("myUserDao")
public class UserDaoImpl implements UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);

    @Override
    public void printHello() {
        logger.info("数据传输层打印hello");
        logger.info("hello");
    }
}

开启组件扫描的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: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
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--    开启组件扫描-->
    <context:component-scan base-package="com.shen.springiocannotation"></context:component-scan>

</beans>

测试类

public class TestMyUserController {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserController userController = context.getBean(UserController.class);
        userController.printHello();
    }
}

替代XML配置文件的配置类

示例

原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: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
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--    开启组件扫描-->
    <context:component-scan base-package="com.shen.springiocannotation"></context:component-scan>

</beans>
替代的配置类
@Configuration
@ComponentScan("com.shen.springiocannotation")
public class Spring6Config {
}
启动类加载Spring容器
public class TestUserControllerByAnnotation {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);
        UserController userController = context.getBean(UserController.class);
        userController.printHello();
    }
}

手写Spring容器

Spring框架中的容器是基于Java反射机制实现的。

Java反射

java反射机制可以参考我之前写的博客。

Java反射 - 柯南。道尔 - 博客园

Spring容器实现过程

  1. 创建测试类:新增业务层和数据访问层的类用于创建相应类的对象。
  2. 自定义注解: 新增@Bean注解用于创建对象,@Di注解用于属性注入。
  3. 创建bean容器接口,定义方法,用于返回对象。
  4. 实现bean容器接口方法:根据包加载bean,返回对象(扫描包中的类信息,如果类中有@Bean注解,则将该类实例化)。

示例

自定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

业务层

public interface UserService {
    void printService();
}
@Bean
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Di
    private UserDao userDao;

    @Override
    public void printService() {
        logger.info("print service ==============");
        userDao.printDao();
    }
}

数据访问层

public interface UserDao {
    void printDao();
}
@Bean
public class UserDaoImpl implements UserDao {
    private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);

    @Override
    public void printDao() {
        logger.info("dao print ==========");
    }
}

bean容器

public interface ApplicationContext {
    Object getBean(Class<?> cls);
}
public class AnnoationApplicationContext implements ApplicationContext {

    private final static Logger logger = LoggerFactory.getLogger(AnnoationApplicationContext.class);

    //创建map集合,存入bean对象
    private final Map<Class<?>, Object> beanFactory = new HashMap<>();
    private String rootPath;

    @Override
    public Object getBean(Class<?> cls) {
        return beanFactory.get(cls);
    }

    //创建有参构造,传参为包路径,设置包扫描规则
    //当前包以及其子包,哪个类有@Bean注解,把这个类通过反射实例化
    public AnnoationApplicationContext(String basePackagePath) {
        try {
            //1.把.替换成\
            String packagePath = basePackagePath.replaceAll("\\.", "\\\\");

            //2.获取包的绝对路径
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                loadBean(new File(filePath));
                logger.info("完成{}包扫描", basePackagePath);
            }
        } catch (Exception e) {
            logger.info("扫描包异常:", e);
        }
    }

    //包扫描方法
    private void loadBean(File file) throws Exception {
        //1. 判断当前是否为文件夹
        if (file.isDirectory()) {
            //2. 获取文件夹中所有内容
            File[] childFiles = file.listFiles();
            //3. 判断文件夹里面是否为空,不为空则遍历所有内容
            if (childFiles == null || childFiles.length == 0) {
                return;
            }
            for (File child : childFiles) {
                //4.1 遍历得到每个File对象,如果文件对象是文件夹则递归遍历
                if (child.isDirectory()) {
                    //递归
                    loadBean(child);
                } else {
                    //4.2 如果File对象是文件,则获取文件的绝对路径
                    String filePath = child.getAbsolutePath();
                    //4.3 判断当前文件路径是否带有.class
                    if (filePath.contains(".class")) {
                        //4.4 如果带有class则获取全类名
                        String pathWithClass = filePath.substring(rootPath.length() - 1).replaceAll("\\\\", ".");
                        String className = pathWithClass.replace(".class", "");
                        //4.5 通过全类名获取Class类对象
                        Class<?> cls = Class.forName(className);
                        ///4.6 判断是否为接口
                        if (!cls.isInterface()) {
                            Bean annotation = cls.getAnnotation(Bean.class);
                            //4.7 判断是否有Bean注解
                            if (annotation != null) {
                                Constructor<?> constructor = cls.getConstructor();
                                //4.8 实例化对象
                                Object object = constructor.newInstance();
                                //4.9 如果该类有接口,则取该类继承的一个接口作为key存入map集合中,反之则将该类作为key存入map集合中,value为实例
                                if (cls.getInterfaces().length > 0) {
                                    beanFactory.put(cls.getInterfaces()[0], object);
                                } else {
                                    beanFactory.put(cls, object);
                                }
                            }
                        }
                    }

                }
            }
        }
        //属性注入
        loadDi();
    }

    private void loadDi() throws IllegalAccessException {
        //实例化对象在beanFactory中
        //1. 遍历beanFactory的map集合
        Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class<?>, Object> entry : entries) {
            //2. 获取map集合中每个对象,并且获取每个对象的所有属性
            Object object = entry.getValue();
            //获取Class类对象
            Class<?> cls = object.getClass();
            //获取所有属性
            Field[] fields = cls.getDeclaredFields();
            //3. 遍历属性数组
            for (Field field : fields) {
                Di annotation = field.getAnnotation(Di.class);
                //4. 判断属性上是否有@Di注解
                if (annotation != null) {
                    //暴破,允许对私有属性赋值
                    field.setAccessible(true);
                    //5. 如果有@Di注解,把此属性的对象进行注入
                    field.set(object, beanFactory.get(field.getType()));
                }
            }
        }

    }
}

测试类

public class TestUser {
    private static final Logger logger = LoggerFactory.getLogger(TestUser.class);

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnoationApplicationContext("com.shen.shouxieioc");
        UserService userService = (UserService) applicationContext.getBean(UserService.class);
        userService.printService();
    }
}

参考资料

https://docs.spring.io/spring-framework/reference/core/beans.html

https://www.bilibili.com/video/BV1kR4y1b7Qc/

标签:容器,String,Spring,void,public,bean,logger,IoC,class
From: https://www.cnblogs.com/shenStudy/p/18666989

相关文章

  • 《JAVA基于SpringBoot的在线拍卖系统》毕业设计项目
    大家好我是蓝天,混迹在java圈的辛苦码农。今天要和大家聊的是一款《JAVA基于SpringBoot的在线拍卖系统》毕业设计项目。项目源码以及部署相关请联系蓝天,文末附上联系信息。......
  • 第三十三章 Spring之梦开始的地方——MVC
    Spring源码阅读目录第一部分——IOC篇第一章Spring之最熟悉的陌生人——IOC第二章Spring之假如让你来写IOC容器——加载资源篇第三章Spring之假如让你来写IOC容器——解析配置文件篇第四章Spring之假如让你来写IOC容器——XML配置文件篇第五章Spring之假如让你来......
  • springboot整合EasyExcel导出excel表格
    文章目录什么是EasyExcel?EasyExcel的特点使用EasyExcel导出excel1.添加EasyExcel依赖2.定义converter3.定义导出实体类型4.查询数据库数据并导出5.导出样式什么是EasyExcel?  EasyExcel是阿里巴巴开源的一款高性能、简洁易用的Excel读写工具库,基于Java开......
  • 基于Spring Boot的景区服务平台【代码实现+数据库设计+实现文档】
    ......
  • [免费]微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端)【论文+源码+SQL脚本
    大家好,我是java1234_小锋老师,看到一个不错的微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端),分享下哈。项目视频演示【免费】微信小程序(高校就业)招聘系统(Springboot后端+Vue管理端)Java毕业设计_哔哩哔哩_bilibili项目介绍随着越来越多的用户借助于移动手机......
  • 基于springboot的老年人体检管理系统
      博主介绍:java高级开发,从事互联网行业多年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有实实在在的写点程序。......
  • 基于Springboot的图书电子商务系统【附源码】
    基于Springboot的图书电子商务系统效果如下:系统主页面图书信息管理页面图书资讯页面系统登陆页面个人中心页面我的地址页面图书分类管理页面在线客服页面图书信息页面研究背景随着互联网技术的快速发展,尤其是移动互联网的普及,电子商务已成为人们日常生......
  • 【开题报告+论文+源码】基于SpringBoot+Vue的大学生创新创业申报平台系统
    背景与意义在全球创新创业浪潮的推动下,高等教育机构愈发重视培养大学生的创新精神和创业能力,积极构建有利于学生发挥创新能力、实现创业梦想的良好环境。然而,在实际操作中,项目申报流程繁琐、审核效率低下、权限分配不清晰等问题成为了制约大学生创新创业活动发展的重要瓶颈。......
  • springboot“伊牛”养牛场管理平台源码毕设+论文
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着农业现代化的不断推进,畜牧业作为农业的重要组成部分,其管理模式也逐渐向智能化、信息化方向发展。传统的养牛场管理往往依赖于人工记录和口头传达,......
  • SpringBoot面试题(2025)
    什么是SpringBoot?多年来,随着新功能的增加,spring变得越来越复杂。只需访问https://spring.io/projects页面,我们就会看到可以在我们的应用程序中使用的所有Spring项目的不同功能。如果必须启动一个新的Spring项目,我们必须添加构建路径或添加Maven依赖关系,配置应......