Spring框架学习 --前言 + Spring的XML应用

【Spring框架】Spring概述及基本应用 Spring框架优点,构建及demo 阅读详情

一、前言

1.传统Javaweb开发的困惑

我们用一段常见的Javaweb开发代码为例:
在这里插入图片描述
这段代码在开发中有两个问题

1.1 问题一:层与层之间紧密耦合、接口与具体实现类紧密耦合。

在本方法中当我们在后期开发时如果需要切换接口实现则需要修改源码,不利于后期的开发与运维。
解决思路:不在程序中手动new对象,使用第三方来提供程序需要的bean对象。

程序
第三方
bean对象

bean对象:在java中我们将符合特定规范的java类称为bean。一般来说此种类用于封装数据、有默认的构造方法

1.2 问题二:在完成此方法本来的目的之前重复的操作较多。

在本方法中我们仅仅需要userDao.updateUserInfo(user)这段话来更改数据库内容,但是却添加了开启事务、关闭事务、记录异常等代码,这些代码在其他类似方法中也会以相似度极高的形式出现。
解决思路:不在程序中new对象,使用第三方根据程序需求生成对象并且根据程序的需求生成该Bean对象的代理对象(增强对象的方法)

程序
第三方
bean对象
bean对象的代理对象

代理(Proxy):在java中代理是一种设计模式。代理用于提供特定对象间接访问。
代理类可以在调用被代理类的基础上新加一些功能。
比如说我们有一个负责修改数据库的A类。A类中有一个查询数据的方法searchData()。现在我们需要在每次调用A类查询数据方法之前新加一段日志记录的功能。那么我们就可以新建一个A类的代理类B类,B类中新写一个方法此方法中完成日志记录后在调用A类中的searchData()方法。
实现方法如下:

 public Class B{
 	//被代理对象
 	A a;
 	public B(A a){
 		this.a = a;
 	}
 	public Object searchData(){
 		...
 		新加记录日志功能代码
 		...
 		//调用目标对象方法
 		a.searchData();
 		...
 	}
 }

2.IoC、DI和AOP思想的提出

程序
第三方
bean对象
bean对象
bean对象
bean对象的代理对象

IoC(Inversion of Control):控制反转,强调的是将原来程序中创建Bean以及销毁bean的权利反转给第三方。
DI(Dependency Injection):依赖注入,强调Bean之间的关系由第三方去设置。
AOP(Aspect Oriented Programming):面向切面编程,功能的横向抽取与扩展,主要实现的方式是代理(Proxy)。主要思想是对某一个bean进行增强。


二、Spring框架

Spring是一个轻量级的Java开发框架,可以简化我们的开发过程。针对与Javaweb开发中遇到的问题,Spring框架提供了IoC容器、AOP、数据访问、Web开发、集成等模块。
Spring官网 : https://spring.io/

1.BeanFactory快速入门

程序代码
第三方
配置清单
bean类
bean类

此处我们将要了解的BeanFactory便是控制生成与销毁Bean的第三方。

1.1 快速使用BeanFactory的案例

本案例主要模拟一个简单的Bean在BeanFactory中配置以及在程序中生成bean的过程。

  1. 创建一个普通的Maven项目
  2. 在pom文件中导入依赖
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.7</version>
        </dependency>
  1. 创建一个简单的bean
    创建一个UserService以及UserServiceImpl。
    UserService:
public interface UserService {
    public void fun();
}

Userserviceimpl:

public class UserServiceImpl implements UserService {
    @Override
    public void fun() {
        System.out.println("方法成功被调用...");
    }
}
  1. 创建Bean配置文件beans.xml,并且在beans.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.xsd">
<!--    bean的配置清单,在BeanFactory中配置bean-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
</beans>

我们可以看到,在bean标签中我们输入了id以及class两个属性,它们分别可以理解为bean的名字以及bean对应的类。

  1. 创建一个测试类,在测试类中生成bean并且测试是否可行
package com.itheima.test;

import com.itheima.service.UserService;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

public class BeanFactoryTest {
    public static void main(String[] args) {
//        创建BeanFactory对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//        创建一个XML读取器
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//        读取配置清单给BeanFactory
        reader.loadBeanDefinitions("beans.xml");
//        获取bean对象并且调用其方法查看是否创建成功
        UserService userService = (UserService) beanFactory.getBean("userService");
        if (userService != null){
            userService.fun();
        }
    }
}

运行结果:
在这里插入图片描述

我们可以看到我们在程序中使用BeanFactory有三步:
1.创建一个BeanFactory对象(DefaultListableBeanFactory)
2.创建一个配置读取对象(XmlBeanDefinitionReader),使用配置对象读取配置清单
3.获取Bean

1.2 BeanFactory中的依赖注入(DI)

我们都知道,在我们一般的程序开发中,Service层是需要调用Dao层,Service与Dao层是紧密链接的。因此我们可以在上述的案例中做一个小小的补充:模拟日常开发,创建一个Dao层的bean,在beans.xml中配置好这个bean之后将这个Dao层的bean‘注入’到userService的bean中。

  1. 创建一个Dao层的bean
    我们创建的类为UserDao以及UserDaoImpl

UserDao:

public interface UserDao {
    public void fun();
}

UserDaoImpl:

public class UserDaoImpl implements UserDao {
    @Override
    public void fun() {
        System.out.println("正在运行UserDao的方法...");
    }
}
  1. 在beans.xml中添加bean的配置,修改UserService中的代码以及将依赖导入
    UserService
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        System.out.println("BeanFactory调用此方法,将UserDao设置到此处...");
        this.userDao = userDao;
    }

    @Override
    public void fun() {
        System.out.println("UserService方法成功被调用...");
        userDao.fun();
    }
}
<!--    bean的配置清单,在BeanFactory中配置bean-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

    <bean id="userDao" class="com.itheima.Dao.impl.UserDaoImpl"></bean>

我们在配置UserDao的bean时也在UserService的bean中添加了一个<property>标签,在此标签中我们配置了UserDao,但是需要注意的是我们如此配置的前提是在UserService中编写了setUserDao()的方法。也就是说在BeanFactory中当我们给一个bean添加Property标签时,FactoryBean会自己根据标签中的name在bean中寻找对应的set方法,并且运行它。
3. 运行测试类进行测试
现在我们稍微修改测试类,添加日志显示,并且获取UserService的bin并且运行他的fun()方法:
BeanFactoryTest:

public class BeanFactoryTest {
    public static void main(String[] args) {
        System.out.println("BeanFactoryTest测试类开始测试...");
//        创建BeanFactory对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//        创建一个XML读取器
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
//        读取配置清单给BeanFactory
        reader.loadBeanDefinitions("beans.xml");
        System.out.println("开始获取UserService bean...");
//        获取bean对象并且调用其方法查看是否创建成功
        UserService userService = (UserService) beanFactory.getBean("userService");
        if (userService != null){
            userService.fun();
        }
    }
}

在这里插入图片描述


2.ApplicationContext快速入门

ApplicationContext称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更加强大。并且在ApplicationContext中对bean的操作大部分情况下都是调用的BeanFactory。
针对上一个案例中的测试类,我们可以将其中的BeanFactory替换为ApplicationContext进行编写:

public class ApplicationContextTest {
    public static void main(String[] args) {
        System.out.println("开始ApplicationContext对象的创建...");
//        创建ApplicationContext对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
//        获取Bean
        System.out.println("获取userService bean...");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.fun();
    }
}

我们可以看到ApplicationContext在创建的时候要比BeanFactory更加简单一些,可以直接将xml文件路劲作为构造参数创建,而不需要和BeanFactory一样创建再创建一个加载XML的类对象。
运行结果:
在这里插入图片描述


面试题:BeanFactory和ApplicationContext之间的关系

答:BeanFactory和ApplicationContext都是Spring框架中用于配置和管理bean的容器。
BeanFactory提供了基础的IoC功能,即用配置文件或者注解的形式对bean进行创建、销毁、组装和配置等过程。
ApplicationContext是BeanFactory的子接口,在BeanFactory的基础上提供了更多企业级的功能,相较于BeanFactory,ApplicationContext更加适用于提供应用程序级别的功能,例如:事件发布、资源加载、国际化等。另外ApplicationContext还支持自动注入、AOP代理、事务管理等高级功能,这些都是BeanFactory不具有的。
在对bean进行加载时,ApplicationContext会在容器启动时就将bean加载完毕,因此ApplicationContext可以更加快速的获取bean,BeanFactory会在程序获取bean的时候才将bean进行实例化,因此BeanFactory在容器启动时速度更快一些。
总的来时,BeanFacotry和ApplicationContext都是Spring框架中两个重要的容器接口,BeanFactory提供了基础的IoC功能,ApplicationContext在BeanFactory的基础上提供了更多企业级的功能。


3.基于xml的Spring应用

3.1 Spring配置详解

Spring bean的常用配置如下

xml配置方式功能描述
<bean id = “” class = “” >Bean的id以及对应的类
<bean name=“” >使用name定义bean的别名,我们也可以通过别名来获取bean实例
<bean scope=“” >Bean的作用范围,BeanFactory作为容器的时候,取值singleton和prototype
<bean lazy-init=“” >Bean的实例化时间,可以使bean延迟加载。但是在BeanFactory作为容器的时候无效。
<bean init-method=“” >Bean实例化时自动执行初始化方法,method对应方法名
<bean destory-method >Bean的销毁前执行的方法,method指定方法名
<bean autowire=“” >设置自动注入模式,常用的模式有 byType:按照类型、byName:按照方法
<bean factory-bean=“” factory-method=“” >指定哪个工程Bean的哪个方法完成bean的创建
3.1.1 bean的id和name配置

在xml中配置bean的时候,我们一般配置一个id就可以了,之后使用getBean方法根据id获取bean。

<bean id="userDao" class="com.itheima.Dao.impl.UserDaoImpl"></bean>

在bean标签中我们还可以配置name,可以使用getBean根据name获取,name可以配置多个,配置多个name的时候使用,分隔。

<bean id="userDao" name="dao,Userdao2" class="com.itheima.Dao.impl.UserDaoImpl"></bean>

另外我们也可以name和id都不配置,在这种情况下,我们直接使用getBean根据类名来获取bean。

<bean class="com.itheima.Dao.impl.UserDaoImpl"></bean>

这种情况下获取bean:

applicationContext.getBean("com.itheima.Dao.impl.UserDaoImpl");

3.1.2 bean的作用域范围

在基础的Spring框架中,bean配置的scope只有两个值:singleton(单例)和prototype(原型)。
singleton : 单例,只会在容器启动的时候创建一个,并且存储在容器当中,在获取bean时容器返回,每次返回的bean都是同一个。
prototype : 原型,不会在容器启动时创建,在获取bean的时候容器将实例化一个bean返回,每次返回的bean都不是同一个。
在一般情况下,我们不需要配置scope,使用默认值singleton即可。


3.1.3 bean的延迟加载

当lazy-init的值为true时,那么这个bean只有在被获取时才会被加载。可以用此方法节省资源。

<bean id="userDao" class="com.itheima.Dao.impl.UserDaoImpl" lazy-init="true"></bean>

3.1.4 bean的初始化和销毁方法

可以在bean中配置init-method和destory-method方法来指定创建以及销毁方法。
例如:

public class UserServiceImpl implements UserService {

    public void init(){
        System.out.println("UserServiceImpl初始化方法被调用...");
    }
    public void destory(){
        System.out.println("UserServiceImpl销毁方法被调用...");
    }
    @Override
    public void fun() {
        System.out.println("UserServiceImpl方法被调用...");
    }
}

beans.xml:

<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" init-method="init" destroy-method="destory"></bean>

编写测试方法:

public class BeanTest {
    public static void main(String[] args) {
        //ApplicationContext没有close方法,只能使用其子类ClassPathXmlApplicationContext
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.fun();
//        只有在程序中调用容器的销毁方法时,容器才会调用其中bean的销毁方法
        context.close();
    }
}

测试结果:
在这里插入图片描述
需要注意的是,如果我们没有调用容器的close方法,直接关闭程序,那么容器就来不及调用其中bean的销毁方法就‘挂掉’了。所以我们要分清楚bean的销毁以及bean的销毁方法的区别,bean的销毁是指销毁这个bean,而bean的销毁方法是指在销毁bean之前调用的方法。


3.1.5 InitializingBean接口

要实现bean的初始化方法,我们也可以把bean的类实现InitializingBean接口,实现接口的afterPropertiesSet方法,那么容器就会在bean实例化注入依赖之后调用此方法。


3.1.6 实现bean实例化的方法

我们常用的bean实例化的方法是由Spring容器直接生成bean的实例,但是我们也可以自己对bean实例化的方法进行定义。

1)生成有参构造的bean
在我们没有特定对bean进行配置时,Spring容器会默认调用bean的无参构造方法对bean进行实例化。我们也可以给bean配置有参构造方法,在bean标签中配置对应的参数即可。
例如,我们接下来创建一个简单的bean,他的有参构造方法中的参数为String类型的name以及int类型的age:

public class BeanCreateTestService {
    public BeanCreateTestService(String name , int age){
        System.out.println("BeanCreateTest获取到的参数" + name + " " + age);
    }
}

在Spring容器中进行配置

    <bean id="beanCreateTestService" class="com.itheima.BeanTest.BeanCreateTestService">
<!--       配置有参构造的参数-->
        <constructor-arg name="name" value="BugMaker"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
    </bean>

运行结果:
在这里插入图片描述
2)静态工厂实例化
以上实例化方法都是由Spring容器实例化,除此之外我们也可以自己编写bean的实例化方法,将bean实例化后存入Spring容器当中。
我们先自己编写一个将bean实例化的工厂类,起名为MyBeanFactorySt:

public class MyBeanFactorySt {
    public static BeanCreateTestService CreateBeanCreateTestService(String name,int age){
//        编写bean的实例化方法,我们可以在bean实例化之前编写一些业务逻辑
        System.out.println("静态工厂实例化之前的业务逻辑...");
        return new BeanCreateTestService(name,age);
    }
}

之后配置bean

<!--    静态工厂构造bean class:bean工厂的类|factory-method:bean工厂实例化bean的方法-->
    <bean id="beanCreateTestServiceStaticFactory" class="com.itheima.BeanTest.MyBeanFactorySt" factory-method="CreateBeanCreateTestService">
<!--        配置bean工厂中实例化方法中的参数-->
        <constructor-arg name="name" value="BugMakerStatic"></constructor-arg>
        <constructor-arg name="age" value="24"></constructor-arg>
    </bean>

运行结果:
在这里插入图片描述
需要注意的是,我们之前学习的’init-method’以及将类实现InitializingBean接口使用接口的afterPropertiesSet()方法这两个都是在bean实例化之后才执行的,而现在我们自定义bean的实例化方法可以在bean实例化之前执行相关业务逻辑。

bean实例化时,方法的执行顺序:

1.bean的构造方法
2.运行bean中属性的set方法
3.运行bean的afterPropertiesSet方法
4.运行bean设置的初始化方法

3)动态工厂实例化
我们出了使用静态的方法实例化之外,也可以使用动态的方法进行实例化,也就是说我们将一个bean作为Bean工厂,使用bean里面的方法实例化一个bean。相当于bean中嵌套了一个bean。
我们编写一个简单的动态bean工厂:

public class MyBeanFactoryDy {
    public  BeanCreateTestService CreateBeanCreateTestService(String name,int age){
//        编写bean的实例化方法,我们可以在bean实例化之前编写一些业务逻辑
        System.out.println("动态工厂实例化之前的业务逻辑...");
        return new BeanCreateTestService(name,age);
    }
}

配置动态bean工厂以及其生产的bean:

<!--    动态工厂实例化-->
    <bean id="DyBeanFactory" class="com.itheima.BeanTest.MyBeanFactoryDy"></bean>

    <bean id="beanCreateTestServiceDy" factory-bean="DyBeanFactory" factory-method="CreateBeanCreateTestService">
        <constructor-arg name="name" value="BugMakerStatic"></constructor-arg>
        <constructor-arg name="age" value="24"></constructor-arg>
    </bean>

在这里插入图片描述

其实这些实例化bean的方法更加强调了Spring容器的“容器”性质,我们可以直接把一个类交给Spring容器让它来管理这个类的bean实例化;也可以告诉Spring容器一个实例化对象的方法,让Spring容器调用此方法来实例化一个bean。

4)实现FactoryBean规范延迟实例化方法
我们在将bean实例化出了上述的将bean的实例方法交给Spring容器之外,我们也可以配置一个简单的工厂bean,Spring容器先加载简单的工厂bean,当我们获取这个bean的时候再使用工厂bean将bean实例化。
创建一个实现FactoryBean的方法:

public class FactoryBeanImpl implements FactoryBean<UserService> {
    @Override
    public UserService getObject() throws Exception {
        return new UserServiceImpl();
    }

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

配置bean:

<!--FactoryBean延迟加载-->
    <bean id="userServiceF" class="com.itheima.BeanTest.FactoryBeanImpl">
    </bean>

测试方法:

public class FactoryBeanTest {
   public static void main(String[] args) {
       ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
       UserService userServiceF = (UserService) applicationContext.getBean("userServiceF");
       userServiceF.fun();
   }
}

测试结果

在这里插入图片描述

继承FactoryBean与一般方法不同的是此方法在Spring容器创建的时候加载的类是FactoryBean的实现类而非我们需要实例化的类。
我们通过debug模式,在ApplicationContext刚刚创建之后查看ApplicationContext中的数据可以看出,在ApplicationContext->beanFactory->singletoneObjects中存放的键值对中userServiceF对应的是FactoryBean的实现类。

在这里插入图片描述
在我们getBean之后,在ApplicationContext中会生成对应bean的实例并且将bean存放在ApplicationContext->beanFactory->factoryBeanObjectCache中。
在这里插入图片描述


3.1.7 Bean的依赖注入配置

1)String注入

<property name="属性名称" value="String值"></property>

2)类注入

	<property name="属性名称" ref="注入类bean的id"></property>

或者:

	<property name="属性名称">
            <bean class="注入依赖的类"></bean>
	</property>

3)String List注入

 		<property name="属性名称">
            <list>
                <value>String值1</value>
                <value>String值2</value>
                <value>String值3</value>
            </list>
        </property>

4)类List注入

        <property name="属性名称">
            <list>
                <ref bean="注入的bean1"></ref>
                <ref bean="注入的bean2"></ref>
                <ref bean="注入的bean3"></ref>
            </list>
        </property>

5)String Set注入

<!--        string set-->
        <property name="属性名称">
            <set>
                <value>Sring1</value>
                <value>Sring2</value>
                <value>Sring3</value>
            </set>
        </property>

6)类Set注入

        <property name="属性名称">
            <set>
                <ref bean="注入的bean1"></ref>
                <ref bean="注入的bean2"></ref>
                <ref bean="注入的bean3"></ref>
            </set>
        </property>

7)Map类型注入

        <property name="属性名称">
            <map>
                <entry key="1" value-ref="注入的bean1"></entry>
                <entry key="2" value-ref="注入的bean2"></entry>
                <entry key="3" value-ref="注入的bean3"></entry>
            </map>
        </property>

8)Propertie注入

        <property name="属性名称">
            <props>
                <prop key="配置名称1">配置值1</prop>
                <prop key="配置名称2">配置值2</prop>
            </props>
        </property>

9)依赖自动注入
在bean标签中可以设置autowire的值。
autowire="byname"时,会自动在Spring容器中查找名字和bean中set方法名字相同的bean进行自动注入。
在这里插入图片描述
autowire="byType"时,会在Spring容器中查找bean类型和bean中属性相同的bean进行依赖注入,但是当bean中有两个以上类型相同的bean时,程序会错。
在这里插入图片描述
在这里插入图片描述


3.1.8 Spring的其他配置标签

beans标签
用于改变开发环境。嵌套在根标签内的beans标签中的bean只有在程序中设置对应的profiles才可以在程序中被引用。
在这里插入图片描述
import标签
导入其他配置文件。
在这里插入图片描述
alias标签
可以给bean一个别名。
在这里插入图片描述


3.2 Spring的getBean方法

方法定义返回值和参数
Object getBean(String beanName)根据beanName获取bean实例,返回的值为Object
T getBean(Class type)根据Bean的类型获取bean,要求容器中此类型的bean尽可有一个,返回值类型为Class类型的实例,无需强转
T getBean(String beanName,Class Type)根据beanName从容器中获取bean实例,返回值为Class类型的实例无需强转

3.3 Spring配置非自定义Bean

以上我们都介绍的是自己创建的bean在Spring中进行配置。在实际开发中我们有时会在Spring中配置非自定义的bean,例如在jar包中的类进行配置。
配置非自定义的bean需要注意:bean的实例化方法是什么(无参还是有参构造、静态还是动态工厂实例化);bean创建时需要注入哪些必要的属性。
我们举一个简单的例子:
在我们日常开发中将日期格式转化一般使用的代码为:

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2023-04-01 10:20:30";
Date parsedDate = formatter.parse(dateString);

那么我们如果将转换日期的Date作为一个bean就需要将SimpleDateFormat类作为一个bean工厂,调用其parse方法实例化一个Date的bean:

<!--    配置SimpleDateFormat 动态工厂-->
    <bean id="dateFormat" class="java.text.SimpleDateFormat">
        <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
    </bean>
<!--    使用动态工厂将Date实例化-->
    <bean id="date" factory-bean="dateFormat" factory-method="parse">
        <constructor-arg name="source" value="2023-04-01 10:20:30"></constructor-arg>
    </bean>

此外我们也可以将常用一些类在Spring容器中添加配置,比如:数据库source、数据库链接等。


3.4 Bean实例化的基本流程

bean的实例化流程如下:
1.Spring容器初始化时,加载xml配置文件,获取每一个bean的信息,将bean封装为BeanDefinition对象
2.将所有的BeanDefinition对象存储在Spring容器的BeanDefinitionMap<String,BeanDefinition>中
3.ApplicationContext(ApplicationContext会在创建之后立马将bean实例化)遍历BeanDefinitionMap,根据BeanDefinition中的信息生成bean的实例对象,将bean的实例对象存放在singletonObjects<String,Objects>的Map集合中
4.当程序获取Bean的实例化对象时,Spring容器会在singletonObjects中找到对应的实例对象并且返回
在这里插入图片描述


3.5 Spring的后处理器(Post Processor)

在这里插入图片描述
在这里插入图片描述

post processor:后处理器


3.5.1 BeanFactoryPostProcessor – 在bean实例化之前执行(对BeanFactory的后处理)

Spring会在加载xml配置之后会调用BeanFactoryPostProcessor接口的实现类。
因此我们可以在BeanFactoryPostProcessor的实现类中自己在BeanFactory添加bean。
1.创建一个类实现BeanFactoryPostProcessor接口

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//        自行注册一个BeanDefinition
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.itheima.service.impl.UserServiceImpl");
//        将自行创建的BeanDefinition加入BeanFactory当中
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        defaultListableBeanFactory.registerBeanDefinition("myUserServiceimpl",beanDefinition);

    }
}

2.在xml文件中配置这个类的bean

 <bean class="com.itheima.factory.MyBeanFactoryPostProcessor"></bean>

3.测试能否正常获取这个bean

public class BeanFactoryPostProcessorTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService myUserService = (UserService) applicationContext.getBean("myUserServiceimpl");
        System.out.println(myUserService);
    }
}

测试结果:
在这里插入图片描述
此外BeanFactoryPostProcessor还有一个子接口BeanDefinitionRegistryPostProcessor。在Spring容器启动的时候,当所有的BeanDefinition都已经加载到内存中,Spring容器会调用BeanDefinitionRegistryPostProcessor的postProcessorBeanDefinitionRegistry方法。
在这里插入图片描述


3.5.2 BeanPostProcessor – 在bean实例化之后执行(对Bean的后处理)

在这里插入图片描述
BeanPostProcessor是在Bean实例化之后存入SingletonObjects之前做出处理的,且处理针对的对象是实例化的Bean。
BeanPostProcessor接口:

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

@Nullable 注释是表明这个方法的返回值可以为空以防空指针报错
default 表明这个方法可以不被实现类实现

BeanPostProcessor接口有两个方法postProcessBeforeInitialization和postProcessAfterInitialization,这两个方法在类实例化的过程中调用顺序如下:
在这里插入图片描述


BeanPostProcessor案例 :对Bean的方法的时间日志功能增强

要求如下:在bean的方法开始以及结束时增加日志显示功能
1.编写一个简单的bean:

public class UserServiceImpl implements UserService {
    @Override
    public void fun() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("UserServiceImpl方法被调用...");
    }
}

2.编写一个BeanPostProcessor类,此类中postProcessAfterInitialization中会对实例化之后的bean进行动态代理,对其方法进行加强,在方法执行前后增加日志显示

public class BeanFuncationTimeLogPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//        生产bean的加强类
        Object beanProxy =  Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
//                      1.输出方法开始时间
                        System.out.println(beanName + "." + method.getName() + "---开始时间 : " +  simpleDateFormat.format(new Date()));
//                      2.执行方法
                        Object result = method.invoke(bean, args);
//                      3.输出方法结束时间
                        System.out.println(beanName + method.getName() + "---结束时间 : " +  simpleDateFormat.format(new Date()));
                        return result;
                    }
                }
        );

        return beanProxy;
    }
}

lambda表达式:lambda表达式可以理解为匿名内部类的一个更简洁的编写方式
例如文中动态代理的方法中我们需要一个InvocationHandler的内部类:

Proxy.newProxyInstance(
	ClassLoader,
	Interfaces,
	new InvocationHandler(){
		...
		内部类代码
		...
	}
)

我们可以使用lambda表达式写为

Proxy.newProxyInstance(
	ClassLoader,
	Interfaces,
	() -> {
		...
		内部类代码
		...
	}
)

3.测试结果
当bean方法被调用时:
在这里插入图片描述


bean实例化的过程

在这里插入图片描述


3.6 Spring Bean的生命周期

Spring生命周期是指Bean实例化之后到Bean存储到单例池的过程:
1.Bean的实例化阶段:Spring判断Bean的属性(是否是单例singleton、是否是FactoryBean),之后将普通单例通过反射实例化
2.Bean的初始化过程:Spring对Bean实例属性的填充,执行Aware接口方法、BeanPostProcessor、进行InitializingBean接口的初始化方法、执行自定义的init方法等一系列的初始化方法。
2.Bean的完成阶段:在Bean完成了初始化阶段,Bean成为了一个完整的Spring Bean,Spring容器会把它存放在单例池(SingletonObjects)之中。那么我们将Bean存放到单例池(SIngletonObjects)称为Bean完成了整个生命周期。
在这里插入图片描述
Spring在进行属性注入的时候我们分为以下三种情况:

1.注入普通属性

例如String、int等基本的存储类型的集合时,直接通过类的set方法反射设置进去。

2.注入单向对象引用属性:

单向对象是指当前的Bean仅对属性对象引用,而引入的对象不对当前Bean进行引用。
例如:

	Class A{
		...
	}
	Class B{
		A a;
		public void setA(A a){
			this.a = a;
		}
	}

在上述代码中仅有B单向的把A当为属性。
那么在这种情况下,在B的Bean属性注入的时候,Spring会使用getBean()获取A对应的bean,之后使用set方法设置进去,如果此时A在容器中没有实例化,那么容器会先实例化A(完成整个生命周期),之后进行注入操作。

3.注入双向对象引用属性****(重点)****

双向对象引用是指两个类互相为对方的属性。
例如:

	Class A{
		B b;
		public void setA(B b){
			this.b = b;
		}
	}
	Class B{
		A a;
		public void setA(A a){
			this.a = a;
		}
	}

上述代码中A既把b作为属性又是B的属性,B也同样如此。
了解这种情况我们需要先了解一下三级缓存机制

三级缓存
public class DefaultSingletonBeanRegistry ... {
	...
	//一级缓存,单例池,里面存放的是已经完成生命周期的Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    //二级缓存,早期单例池,缓存半成品对象,里面存放的对象已经被其他对象引用过
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
   	//三级缓存,单例Bean的工厂池,缓存半成品对象,里面存放的是没有被其他对象引用过的对象
   	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    ...
}

注意:三级缓存中Map的结构是<String , ObjectFactory>, ObjectFactory是一个函数式接口,其中仅有getObject()函数,Bean在刚被实例化之后,会“包进”ObjectFactory中,通过get方法获取。

当两个Bean相互为引用属性时,Spring容器创建Bean实例化的逻辑流程:
在这里插入图片描述
我们需要知道一个Bean的创建到存入单例池的流程是实例化->初始化方法->单例池。
当Spring执行getBean(“A”)的方法时,会先把Bean A实例化,此时创建的对象仅仅只是被创建出来并不成熟,不可以被直接引用,同时A的创建工厂会存入三级缓存。之后Spring会执行属性注入、初始化等一系列方法。当Bean A执行属性注入的时候如果发现在一二三级缓存中都没有Bean B的实例化对象,那么对于A的初始化工作会被“挂起”,Spring开始对于Bean B的创建。当Bean B创建到初始化阶段的属性注入时,会在三级缓存中找到创建Bean A的创建工厂,此时会根据Bean A是否被AOP代理创建代理对象或者普通对象交由Bean B(此时将Bean A放入二级缓存),使得Bean B可以完成一整个的创建流程。当Bean B被完全创建完成之后Spring会回到Bean A的创建工作继续Bean A的创建。

我们主要需要了解的是三级缓存的作用:
一级缓存(SIngletonObjects):存储的是已经“成熟的Bean”可以直接被用户使用。
二级缓存 (EarlySIngletonObjects):存储的Bean处于中间状态,Bean尚未完成依赖注入,但是已经被创建。
三级缓存(SIngletonFactories):是一个工厂,里面存放的是正在被创建的Bean,存放的是Lambda表达式,会根据Bean是否被AOP而创建Bean的代理对象或者普通对象。

*** 问题:为什么是三级缓存而不是二级缓存?

其实如果不考虑AOP的话。二级缓存是可以处理绝大多互相依赖的情况的。
但是当相互依赖的A、B两个bean中的A类是被AOP代理的话,Spring要求属性注入时Bean B需要注入的必须是Bean A的代理对象,但是AOP代理对象的生成是在Bean的初始化阶段才生成的。所以,我们必须借助可以直接生成AOP代理对象的三级缓存来处理相互依赖且有AOP代理的情况。

具体可以参考博客:Spring 的三级缓存机制

对于非代理Bean,singletonFactories中的工厂对象直接创建Bean的实例并将其放入一级缓存singletonObjects中。而对于需要被AOP代理的Bean,singletonFactories中的工厂对象会先创建一个原始的Bean实例,然后通过AOP代理来生成代理对象,并将代理对象放入一级缓存singletonObjects中。

3.7 Spring IoC整体流程总结

在这里插入图片描述
SpringBean生成的整个阶段可以总体分为两个大阶段:BeanDefinition阶段以及Bean的生命周期。
BD阶段中,Spring读取配置文件中Bean的配置信息,创建BD并且将它们存储在BDMap之中。
Bean的生命周期阶段中,Spring依次获取BDMap中的BD,根据BD的信息将Bean实例化(此时Bean不是完整的Bean),将Bean初始化(此时Bean变为完整),将Bean存入SingletonObjects中。


3.8 Spring xml方式整合第三方框架

3.8.1 Mybatis原始代码使用

1.配置Mybatis信息
创建Mybatis-config.xml文件,其内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 设置数据源类型。由于表格存储JDBC驱动需要主动关闭后才能让进程退出,请根据实际使用选择合适的数据源类型。-->
            <!-- 如果程序常驻执行,则您可以使用POOLED维护一个连接池;如果希望程序完成数据查询后退出,则只能使用UNPOOLED。-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_learn"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 设置为映射配置文件的路径。-->
        <package name="com.itheima.mapper"/>
    </mappers>
</configuration>

在MySQL是8.0以上的版本时,driver为com.mysql.cj.jdbc.Driver;在MySQL是5.0版本的时候driver是com.mysql.jdbc.driver。以及在MySQL是8.0以上的版本时URL有时是需要添加时区等参数的,否则无法建立和数据库的链接,此次测试不需要添加这些参数,原因未知,可能原因是mysql驱动或者mybatis版本是新的所以解决了这个问题。

2.创建实体类
根据数据库的结构创建一个实体类。
数据库的结构如图:
在这里插入图片描述
实体类:

package com.itheima.pojo;

public class User {
    private int id;
    private String username;
    private int age;
    private String address;

/*
	...
	getter和setter方法,toString方法
	...
*/
}

3.创建Mapper以及SQL语句
创建一个简单的Mapper接口:

public interface UserMapper {
    List<User> findAll();
}

根据Mapper接口创建一个mapper的xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace: mapper接口的以用路径-->
<mapper namespace="com.itheima.mapper.UserMapper">
<!--    id: mapper接口中使用的方法
        resultType: SQL返回的数据类型-->
    <select id="findAll" resultType="com.itheima.pojo.User">
        select * from USER_INFO
    </select>
</mapper>

4.编写测试类

package com.itheima.test;

import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
    public static void main(String[] args) throws IOException {
//        1.获取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//        2.根据mybatis配置文件创建sqlSessionFactory,并且获取一个sqlSession
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        3.根据sqlSession生成userMapper的对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//        4.执行userMapper中的方法并且将结果输出
        List<User> all = userMapper.findAll();
        for (User user : all){
            System.out.println(user.toString());
        }

    }
}

MyBaits原始的使用方法比较繁琐,并且一般情况下是不会这么使用的,所以不需要刻意记住。
执行结果:
在这里插入图片描述


3.8.2 Spring整合Mybatis

1.导入Mybatis整合Spring的相关坐标

       <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>

2.编写Mapper和Mapper.xml
上个案例中已经写了对应的mapper以及mapper.xml,可以去上个案例查看。
3.配置SqlSessionFactoryBean和MapperScannerConfigurer

<!--    数据源-->
    <bean name="DataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_learn"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
<!-- SqlSessionFactory bean-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="DataSource"></property>
    </bean>
<!--    MapperScannerConfigurer 扫描指定包,自动生成Mapper对象存入Spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.itheima.mapper"></property>
    </bean>

SqlSessionFactoryBean需要数据源作为参数才可以创建。
4.编写测试代码
我们编写一个简单的UserService接口,它有一个简单的fun()方法,之后编写一个实现类UserServiceImpl实现这个接口。
UserServiceImpl中设置一个简单的Mapper属性,fun方法中使用Mapper中的方法实现数据库的查询功能。

public interface UserService {
    public void fun();
}
public class UserServiceImpl implements UserService {

    private UserMapper userMapper;

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void fun() {
        List<User> all = userMapper.findAll();
//        lambda表达式实现遍历
        all.forEach(
                (user) -> {
                    System.out.println(user.toString());
                }
        );
    }
}

之后在Spring的配置文件中添加对于这个Bean的配置信息:

    <bean name="userService" class="com.itheima.service.impl.UserServiceImpl">
        <property name="userMapper" ref="userMapper"></property>
    </bean>

我们在从头到尾的过程中都没有配置UserMapper相关的Bean,但是在对UserService进行属性创建的时候却可以将userMapper作为bean传入,这是因为MapperScannerConfigurer会自己遍历对应路径下的mapper类并且生成bean存入Spring容器之中。

编写测试方法,调用userService的bean:

public class MybatisSpringTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.fun();
    }
}

测试结果:
在这里插入图片描述


3.8.3 Spring整合MyBatis的原理解析

整合包里提供了SqlSessionFactoryBean和一个扫描Mapper的配置对象。SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并且通过动态代理产生Mapper的实现类存储到Spring容器之中。其中主要相关的包括一下四个类:
SqlSessionFactoryBean:需要配置,用于提供SqlSessionFactory;
MapperScannerConfigurer:需要进行配置,用于扫描指定的mapper注册BeanDefiniftion;
MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法(因为mybatis中mapper均是接口形式,想要生成对应的对象就要使用FactoryBean)
ClassPathMapperScanner:会将扫描到的Mapper Bean的注入状态是自动注入状态definition.setAutowireMode(2),所以MapperFactoryBean中的SqlSessionFactory会自动注入进去。

SqlSessionFactoryBean
SqlSessionFactoryBean在初始化阶段调用afterPropertiesSet()方法,创建SqlSessionFactory并且设置在属性中,创建的方法和原始的MyBatis代码相同,都是先创建SqlSessionFactoryBuilder之后使用SqlSessionFactoryBuilder创建SqlSessionFactory。

MapperScannerConfigurer的原理解析

Mapper扫描配置器。
我们在看Spring整合MyBatis时难免会有一个疑问:Mapper接口都是接口,不可以直接生成对象Bean,Spring容器是怎么生成的MapperBean呢?
答: 当Spring容器扫描到了Mapper文件,并且根据Mapper文件创建了BeanDefinition之后,会统一将Mapper的BeanDefinition文件中的BeanClass设置为MapperFactoryBean,并且将注入模式设置设置为自动注入。MapperFactoryBean是FactoryBean的子类,专门负责生成Mapper Bean。MapperFactoryBean将sqlSessionFactory作为属性,这个类是MyBatis原代码生成Mapper对象的工厂,之后Spring容器会自动将创建好的sqlSessionFactory Bean作为属性注入其中。之后在Spring容器的初始化阶段时,根据BeanDefinition创建实例时,会根据BeanDefinition将对应Mapper接口的Class传入作为构建参数,创建出这个Mapper专用的MapperFactoryBean,之后调用MapperFactoryBean的getObject方法,生成对应Mapper的代理bean。
MapperSessionFactoryBean的getObject方法:
在这里插入图片描述

MapperBean的创建过程:
在这里插入图片描述


3.8.4 Spring框架处理自定义命名空间原理解析

Spring框架处理XML中的自定义命名空间的步骤如下:

1. 获取XML中的自定义命名空间标签(element)
2. 根据元素自定义命名空间的URI获取自定义命名空间的解析器,使用获取到的解析器解析自定义的命名空间标签

1.获取XML中的自定义命名空间标签(element)
在Spring扫描完XML配置文件时,会将配置文件中的标签都获取到,并且依次解析(parse)这些标签。
这个获取XML中标签并且分析这些标签的过程处理代码如下:(此类为DefaultBeanDefinitionDocumentReader.java )

/*类名: DefaultBeanDefinitionDocumentReader */
		...
		...
		...
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();

            for(int i = 0; i < nl.getLength(); ++i) {
                Node node = nl.item(i);//获取xml中的节点信息
                if (node instanceof Element) {
                	//如果节点类型为element则获取并且进行下一步处理
                    Element ele = (Element)node;
                    if (delegate.isDefaultNamespace(ele)) {
                    	/*如果element为默认的命名空间(import、alias、bean、beans),
                    	就使用默认命名空间解析的方式*/
                        this.parseDefaultElement(ele, delegate);
                    } else {
                    	/*如果element不是默认的命名空间,
                    	那么就启用自定义命名空间的处理方式*/
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }
    }
		...
		...
		...

2.根据元素自定义命名空间的URI获取自定义命名空间的解析器,使用获取到的解析器解析自定义的命名空间标签
Spring根据自定义命名空间的URI获取到此标签对应的解析器并且完成解析(parseDefaultElement())。
其代码实现如下(DefaultNamespaceHandlerResolver.java):

	...
    @Nullable
    public BeanDefinition parseCustomElement(Element ele) {
    	//调用自身类对应的parseCustomElement()方法
        return this.parseCustomElement(ele, (BeanDefinition)null);
    }

    @Nullable
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    	//获取标签对应的命名空间
        String namespaceUri = this.getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        } else {
        	//获取命名空间对应的处理器(NamespaceHandler)
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler == null) {
                this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                return null;
            } else {
            	//使用命名空间处理器来解析标签
                return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
            }
        }
    }
    ...

其中,一般而言自定义命名空间的jar包中会在META-INF/spring.handlers目录文件中存放命名空间URI对应的类(Spring会自动加载):
在这里插入图片描述
每个命名空间处理器(NamespaceHandler)都起码会有俩个方法:init() 和 parse()。
init()方法:来注册命名空间中每个标签的处理器
parse()方法:用来解析标签

3.8.5 案例:自制自定义命名空间

我们根据上述原理分析知道自定义一个命名空间需要以下几个条件:

1.在META-INF目录下的spirng.handlers以及spring.schemas文件中有命名空间的键值对
2.拥有对应的xsd约束文件
3.有对应的解析器handler
4.各个对应标签均有对应的解析器

那么我们就不难根据以上条件来自己创建一个自定标签。此标签做一个简单的功能:将一个自定的BeanPostProcessor注册入Spring容器。

1.创建META-INF目录,在其中创建spring.handlers以及spring.schemas文件,在其中填写好自定义命名空间对应的处理器以及对应的约束文件。
在这里插入图片描述
spring.handlers文件:
在这里插入图片描述
spring.schemas文件:
在这里插入图片描述
约束文件BugMakerAnno.xsd对应的内容:

<?xml version="1.0" encoding="UTF-8" ?>

<xsd:schema xmlns="http://www.itheima.com/BugMaker"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.itheima.com/BugMakerAnno">
    <xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>

targetNamespace属性必须为之前的自定义命名空间。

2.创建自定义命名空间对应的处理器类BugMakerAnnoHandler
BugMakerAnnoHandler.java:

package com.itheima.handlers;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class BugMakerAnnoHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        注册解析器
//		“annotation-driven”为自定义命名空间中的标签名称,后面的时标签对应的处理类
        this.registerBeanDefinitionParser("annotation-driven",new BugMakerAnnoParser());
    }
}

我们可以看到,这个类结构十分简单,仅仅是在init()方法中注册了标签对应的解析器。这是因为此类继承的父类NamespaceHandlerSupport类,这个类的parse()方法会根据传入的标签名称获取到已经在这个类中注册过的解析器,并且调用解析器的parse()方法进行解析。
在这里插入图片描述
3.编写解析类
BugMakerAnnoParser类:

public class BugMakerAnnoParser implements BeanDefinitionParser {
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //在Spring容器中注册一个容器
//        1.创建BeanDefinition
        BeanDefinition annoBeanDefinition = new RootBeanDefinition();
        annoBeanDefinition.setBeanClassName("com.itheima.postProcessor.AnnoBeanPostProcessor");
//        2.将BeanDefiniton注册入Spring容器之中
        parserContext.getRegistry().registerBeanDefinition("BugMakerAnnoBD",annoBeanDefinition);
        return annoBeanDefinition;
    }
}

BugMakerAnnoParser的代码逻辑即为创建一个BeanDefition,并且将创建的BeanDefition文件注册入Spring容器之中。
AnnoBeanPostProcessor(BeanPostProcessor类):

public class AnnoBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("自定义标签注入的BeanPostProcessor已经运行");
        return bean;
    }
}

AnnoBeanPostProcessor类的逻辑也很简单,只是在每个类实例化之前输入一个简单的日志。

4.在XML配置文件添加自定义命名空间以及对应的标签
在这里插入图片描述
5.运行项目
在这里插入图片描述

Spring框架中applicationContext.xml和spring - servlet.xml的区别 在Spring框架里,和是两个重要的配置文件。Spring允许在父子层次结构中定义多个上下文,这两个文件在不同场景下发挥着不同的作用,理解它们的区别对于Spring应用的开发和配置至关重要。 阅读详情

相关推荐

Spring框架中部署log4j.xml

在开发Java应用程序时,日志记录是非常重要的。Log4j是一个常用的日志记录工具,它可以帮助我们记录应用程序的运行日志并进行灵活的配置。在Spring框架中,我们可以很方便地部署log4j.xml配置文件来管理日志记录。本文将介绍在Spring框架中部署log4j.xml的详细步骤,并提供相应的代码示例。

2301_79108888的博客 1249

关于“前言中不允许有内容”的XML错误

首先提一点,这类型的错误我主要在两个方面遇到 1、确实XML文件格式有问题 格式错误大多会有IDE提示,但例如“”中的路径这种就无法被IDE检查到,要注意是不是哪里多了个空格逗号一类的。 2、没有引入XML文件或者引入错误 在例如springframework中的XmlBeanDefinitionStoreException,该异常一个是xml配置文件有问题,还有就是根本没写ClassPa...

qq_35717643的博客 7447

Spring6.0 框架学习

Spring 框架是 Java 生态中最主流的企业级应用开发框架,它以 “控制反转(IOC)” 和 “面向切面编程(AOP)” 为核心,提供了一站式的企业级开发解决方案,涵盖依赖管理、事务控制、Web 开发、数据访问等多个领域。生态完善:Spring 生态涵盖 Web(Spring MVC)、微服务(Spring Boot/Spring Cloud)、安全(Spring Security)等全场景。Spring 本身不实现具体业务功能(如数据访问),而是通过集成第三方框架发挥价值。

m0_37843156的博客 877

Spring框架学习 -- Spring xml方式整合第三方框架

针对于SpringXML配置方式整合第三方框架的一些简单学习笔记的总结

bugmaker2000的博客 1074

Spring框架学习笔记07:基于XML配置方式使用Spring MVC

文章目录一、Spring MVC概述1、MVC设计模式2、Spring MVC3、使用Spring MVC的两种方式二、基于XML配置与注解的方式使用Spring MVC1、打开项目【SpringDemo2020】2、在pom.xml文件里添加相关依赖3、给项目添加Web功能4、创建三个页面(1)登录页面login.jsp(2)登录成功页面success.jsp(3)登录失败页面failure.jsp5、创建登录控制器 - LoginController6、创建Spring配置文件 - spring-con

howard2005的专栏 1626

Spring框架中XML配置与DTD应用指南

DTD(Document Type Definition,文档类型定义)是一种用于定义XML文档结构的规范。它的作用主要是为了约束XML文档中的元素类型、属性和它们之间的关系。DTD可以分为内部DTD和外部DTD两种。iBatis是一个基于Java的持久层框架,它的主要作用是为了解决数据访问层(Data Access Layer)的复杂性问题。通过使用iBatis,开发者可以将SQL语句从Java代码中分离出来,并且通过简单的XML配置文件或注解定义,就可以实现对数据库的操作。

weixin_35756637的博客 1045

熟悉并使用Spring框架 - XML篇

Spring框架作为Java开发中最流行的框架之一,为我们提供了多种配置方式。虽然现在注解和Java配置越来越流行,但XML配置作为Spring的经典配置方式,对于初学者和对底层源码感兴趣的同学,学习XML配置仍然是非常重要的一环。Spring的XML配置是通过XML文件来定义Bean以及它们之间关系的一种方式。这种配置方式直观明了,所有的配置信息都集中在XML文件中,便于管理和维护。System.out.println("UserService被创建了");

动动发财的小手点点赞 1168

Spring框架深入学习与应用实践

本文还有配套的精品资源,点击获取 简介:《Spring从入门到精通》是一本系统学习Spring框架的指南,涵盖了从基础到高级的全方位知识。本书深入探讨了Spring核心概念如IoC和DI、AOP、Spring MVC、数据库操作以及安全机制,并介绍Spring Boot和Spring Cloud在现代应用开发中的关键角色。通过案例和实战项目,本书帮助读者全面掌握Spring...

weixin_42097508的博客 630

Spring框架全面学习总结

本文系统总结了Spring框架的核心技术与应用实践。首先介绍了反射机制、动态代理和设计模式等基础原理,这些是Spring实现IoC和AOP的技术基础。其次详细解析了IoC容器、Bean管理、依赖注入、AOP和事务管理等核心组件特性。然后对比了XML配置和注解配置两种方式,阐述了自动装配机制及其解决循环依赖的原理。最后介绍了Spring与Log4j2的集成配置方法。全文涵盖了Spring框架从基础到应用的关键知识点,为开发者提供了全面的技术参考。

a52djibg1的博客 1116

UNITY2022 Cardboard环境搭建

必须安装 Git,并且 git 可执行文件必须位于 PATH 环境变量中。考文档:https://developers.google.cn/cardboard/develop/unity/quickstart?转到 Assets/Samples/Google Cardboard//Hello Cardboard/Scenes/HelloCardboard 场景中。粘贴https://github.com/googlevr/cardboard-xr-plugin.git到文本输入字段中。

weixin_39510925的博客 1210

NCCL 源码深度解析:从初始化到通信链路构建全流程

本文深入解析了NVIDIA Collective Communication Library (NCCL) 的核心工作机制,从通信组初始化、硬件拓扑发现,到P2P、共享内存及网络传输链路的构建。详细剖析了源码中集合通信任务的执行流程与Proxy线程的协同机制,为开发者进行高性能分布式训练的性能调优与深度问题排查提供了清晰的底层视角。

hp777的博客 551

手写Spring框架学习Spring原理 之xml(二)

手写Spring框架学习Spring原理 之xml(二)一. 使用XML文件配置BeanDefinition信息二. 增加ApplicationContext为什么要增加ApplicationContext而不是继续扩展BeanFactory?三.实现XmlApplicationContext四.生成测试类测试 GitHub地址:https://github.com/dixinjoker/king-spring-theory 一. 使用XML文件配置BeanDefinition信息 test文件夹内创建r

低语的博客 411

STM32实战开发(65):STM32摄像头图像采集开发教程

我们将会详细介绍如何使用STM32进行摄像头控制、图像数据采集以及如何将采集到的图像数据传输到其他设备进行处理。通过这篇博客,您将能够掌握STM32与摄像头模块的图像采集技术,应用于各种图像处理场景。为了确保STM32能够正确接收图像数据,我们需要配置STM32的GPIO引脚,并通过外部中断捕获这些信号。为了实现更高效的图像采集,可以通过DMA进行数据传输,将图像数据直接从GPIO引脚存储到内存中。通过DMA传输,我们可以将采集到的图像数据直接存储在内存中的一个缓冲区,然后进行后续的图像处理。

2201_76125393的博客 2537

Spring 框架学习

Spring 框架学习

我从不打没有准备的仗! 9563

基于MATLAB的小波包分解(WPD)算法详解

本文详细介绍了基于MATLAB的小波包分解(WPD)算法。WPD作为信号处理领域的重要技术,继承并发展了小波分析的优点,通过正交镜像滤波器组将信号分解为不同频率子带,形成完整的二叉树结构。相比传统小波变换,WPD具有更精细的频带划分、自适应基选择能力和更高的灵活性,特别适合分析非平稳信号。文章系统阐述了WPD的数学原理、算法实现流程及其在机械故障诊断、生物医学信号处理等领域的应用。通过MATLAB代码示例展示了WPD的具体实现方法,并分析了不同参数设置对分解效果的影响,为工程实践提供了有价值的参考。

qq_42212808的博客 292

为什么idea已经改了UTF-8还是乱码,如何解决?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1576

Spring 用法学习总结(一)之基于 XML 注入属性

Spring是轻量级的开源的JavaEE框架,提供了多个模块Spring可以解决企业应用开发的复杂性Spring有两个核心部分:IOC和Aop(1)IOC:控制反转,把创建对象过程交给Spring进行管理(2)Aop:面向切面,不修改源代码进行功能增强Spring特点(1)方便解耦,简化开发(2)Aop编程支持(3)方便程序测试(4)方便和其他框架进行整合(5)方便进行事务操作(6)降低API开发难度。

zjs246813的博客 1670

OpenClaw 多机器人多 Agent 模式:打造你的 AI 助手团队

OpenClaw多Agent模式为企业提供高效的AI助手团队解决方案。通过创建4个独立的飞书机器人(主助理、内容创作、技术开发、AI资讯),每个连接专属Gateway和Agent模型(如Claude Opus/Sonnet、Gemini 2.5),实现专业分工。相比单Agent模式,多Gateway架构具有配置清晰、互不干扰、无需手动切换等优势。系统采用Profile隔离机制,各Agent拥有独立配置和端口。部署过程包括创建机器人应用、配置Agent、运行自动化脚本等步骤,支持私聊或群组@使用。日常提供状态

Maynor的博客 5098

Spring框架使用XML配置

Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 轻量——从大小...

qqxm1的博客 961

基于YOLOv8的水果新鲜程度检测系统设计与实现

数据采集与预处理模块:负责收集水果图像数据并进行标注和增强深度学习模型:基于YOLOv8的目标检测模型新鲜度评估模块:根据检测结果评估水果新鲜程度用户界面:提供友好的交互界面系统集成:将各模块整合为完整系统水果新鲜程度检测需要包含多种水果在不同新鲜度状态下的图像数据集。:包含5种常见水果(苹果、香蕉、橙子、葡萄、草莓)在不同新鲜度下的图像包含约8,000张图像,分为新鲜和腐烂两类AgriVision:大型农业图像数据集,包含水果检测和分类任务Fruit-360:包含120种水果的高质量图像。

YOLO 444

(王道408考研操作系统)第一章计算机系统概述-第三节2:中断和异常

注意!!! 本节内容和《计算机组成原理》中的中断有重合,如果需要详细了解的话可以点击链接跳转:(计算机组成原理)第七章输入和输出系统-第四节2:I/O方式之程序中断方式 为了内容完整,部分内容会借用上述文章 文章目录一:中断和异常的基本概念(1)中断和异常的定义 一:中断和异常的基本概念 在操作系统引入核心态和用户态这两种工作状态后,就需要考虑两种状态之间是如何切换的。 操作系统内核工作在核心态,用户程序工作在用户态,系统不允许用户程序使用核心态的功能,但是用户程序为了完成某些操作又必须使用这些功能

快乐江湖的博客 3530

Spring 框架学习指南

Spring框架是Java企业级开发的主流选择,以IOC(控制反转)和AOP(面向切面编程)为核心,提供一站式解决方案。学习路径包括:1.掌握IOC容器管理对象依赖的方式(XML/注解/JavaConfig配置);2.理解AOP实现横切关注点模块化(如日志、事务),包括动态代理机制和切面编程实践。前置知识需具备Java基础、设计模式等。Spring生态涵盖Web、微服务等多个领域,通过轻量级、非侵入式设计简化开发,是Java开发者必备技能。

手心朝下的博客 1805

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

01.Spring 框架的演化:从 XML 配置到注解驱动

Spring 框架自诞生以来经历了显著的演变,主要体现在配置方式的变革上。这一演化过程从 XML 配置、XML+注解混合配置、Java 配置到纯注解配置,每一步都使开发变得更加简洁高效。通过分析 AnnotationConfigApplicationContext 的参数变化,我们可以清晰地看到这一演进过程。在 Spring 最初的版本中,所有配置都必须通过 XML 文件完成,包括 Bean 定义、依赖注入、AOP 配置等。这种方式将配置与代码完全分离

senhai的博客 1575

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

spring框架:pom.xml变红

Dependency ‘org.springframework:spring-context:5.0.2.RELEASE‘ not found ●在学习Spring框架的时候,对于XML配置文件出现,红色问题 ●解决办法:File → Settings → Maven(将maven配置好) ●最后,选择项目的pom.xml 右键maven → Download sources. ...

tulihetulihe的博客 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Spring框架

Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发

xiaoyudecsdndec的博客 2126

spring框架的概述以及spring中基于xml的ioc配置

1.spring的概述 什么是spring?

qq_40395100的博客 229
上一篇: javaweb 实战项目 --遇见狂神说 SMBMS 超市订单管理系统 数据库创建使用SQL
下一篇: Spring框架学习 -- Spring xml方式整合第三方框架
BugMaker2000
博客等级 码龄7年 84粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值