基于xml的Spring应用

SpringBean的配置详解

BeanName和别名配置

bean配置id则bean的beanName是id值,否则是bean实例的全限定名,可以在ApplicationContext中的BeanFactory中的单例池SingletonObjects(本质上是一个Map)中的table找到bean实例其中key就是beanName,value就是bean对象的地址.

配置name可以起为bean别名在断点调试中可以在ApplicationContext->BeanFactory->aliasMap中的table中找到别名指向bean对象

若没有配置id,配置了name,则取name的第一个值为beanName,其他为别名

Bean的作用范围

在纯Spring环境下仅有singleton和prototype两种作用范围:

singleton:单例,每次Spring容器加载创建时,就会进行bean的实例化,并存储到容器中的单例池中,每次getBean就会在单例池中获取相同的Bean实例

prototype:原型,Spring容器创建时不会对bean实例化,每次调用getBean时才会实例化bean,返回一个新的bean实例

Bean的延迟加载

当lazy-init="true"时(默认为fasle)启动延迟加载,也就是说,spring容器创建时不会创建bean实例,而是调用getBean时才会创建一个bean实例并存储到单例池中,之后的调用getBean还是从单例池中取出第一次创建的bean实例,本质上还是单例模式

Bean初始化方法和销毁方法

bean实例化之后,spring容器会执行指定的初始化方法对bean进行一些初始化操作,同时在bean销毁之后也会调用销毁方法进行一些操作,init-method和destroy-method方法通过指定bean中方法名来配置

销毁方法是要手动手动关闭spring容器才会触发(applicationContext.close()方法),直接终止程序相当于拔电源,还没来得及关闭spring容器所以不会触发销毁方法

⭐initializingBean接口

实现afterPropertiesSet方法,执行时机早于init-method,在属性设置之后执行

Bean的实例化配置(spring创建bean的方式)

构造方法实例化
无参构造

直接在bean中配置

有参构造

需要在bean标签中配置constructor-arg配置,其中的name参数为有参构造的参数的名字,value参数直接写,ref参数需要bean容器中配置的bean对象id

constructor-arg名为构造参数,不仅仅用于构造函数的参数,也可以只要是在bean创建时需要参数,都可以用此标签配置

⭐工厂方式实例化
静态工厂

工厂中创建一个静态方法其返回值为需要创建的bean对象,bean可直接配置factory-method调用工厂中的静态方法,不需要工厂对象,即可实例化bean,且在bean创建之前可以执行一些操作,逻辑更加灵活

jdbc中的DriverMannerger.getConnection就是这样配置的

实例工厂

需要配置bean中的factory-bean和factory-method

factory-bean参数为需要首先在容器中配置的工厂bean对象的beanName,然后配置factory-method指定工厂bean对象中的成员方法名即可实例化bean

有些第三方的bean并不是用构造方法产生的而是工厂方式

⭐实现FactoryBean接口规范延迟加载bean

实现factoryBean接口的getBean方法,在创建spring容器时不会调用getBean方法,单例池中保存的对象并不是需要的bean对象而是factoryBean工厂对象,只有在调用getBean方法时,才会存储在beanFactory中的factoryBeanObjectCache(map对象)中,这里面的bean对象才时需要的bean对象,从这里取出

Bean的依赖注入配置

注入的两种方式

依赖注入的三种数据类型

依赖注入具体操作

自动装配

byName自动装配 bean对象中的setxxx方法的xxx与容器中的id是否一致

byType自动装配bean对象中的属性与容器中的类型一致

但是只能有一个同类型的bean 多了就会报错

Spring的标签

默认标签

beans标签

<beans>可以指定多个配置环境 profile参数可以设置环境名

在第一个<beans>标签下的配置则是通用配置

这种方式基本上不会用到

import标签

在开发过程中有很多根据语义细分的其他配置文件 在applicationContext中一个一个导入狠麻烦 import标签可以导入其他配置文件 resource指定配置文件的名字

alias标签

与<bean name=""/>效果一致

自定义命名空间标签

在配置外部的命名空间时 需要提前在pom中导入对应的依赖

自定义标签的 xmlns和xsi:schemaLocation是成对配置的

需要的自定义标签可以去对应的官网找

Spring的get方法

根据名称获取 需要强转

根据类型获取 需要容器中的bean类型时唯一的

根据名称和类型获取 根据beanName从spring容器中获取Bean实例 返回值为Class类型实例 无需强转

Spring配置非自定的Bean

配置第三方jar包中的工具类需要考虑两点

1.被配置的bean的实例化方式是什么?

2.被配置的bean有没有必要的注入属性?

配置DruidDatasource(构造器实例化)

DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/book");
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");

实例化方式 无参构造

必要的注入属性 四大数据源基本信息

驱动类名DriverClassName:com.mysql.jdbc.Driver

数据源链接url: mysql:jdbc://localhost:3306/book

用户名useruame:root

密码password:123456

<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
  <property name="url" value="jdbc:mysql://localhost:3306/bookk"></property>
  <property name="username" value="root"></property>
  <property name="password" value="123456"></property>
</bean>

配置Connection(静态工厂实例化)

Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("mysql:jdbc://localhost:3306/book","username","password");

实例化方式 静态工厂

必要的注入属性 数据源基本信息

<bean id="clazz" class="java.lang.Class" factory-method="forName">
  <constructor-arg name="className" value="com.mysql.jdbc.Driver"/>
</bean>
<bean class="java.sql.DriverManager" factory-method="getConnection" id="connection">
  <constructor-arg name="url" value="jdbc:mysql://localhost:3306/book"/>
  <constructor-arg name="user" value="root"/>
  <constructor-arg name="password" value="123456"/>
</bean>
Connection和DruidDatasource的区别

Connection是Java JDBC API的一部分,用于表示与数据库的单个连接;而DruidDataSource是一个数据库连接池实现,可以管理和提供多个Connection对象,并提供了一些额外的功能。

配置Date(实例工厂)

Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(date);
System.out.println("format = " + format);

实例化方式 实例工厂

必要的注入属性 date

<bean class="java.util.Date" id="date"></bean>
<bean class="java.text.SimpleDateFormat" id="dateFormat"></bean>
<bean class="java.lang.String" id="format" factory-bean="dateFormat" factory-method="format">
  <constructor-arg name="date" ref="date"></constructor-arg>
</bean>

配置SqlSession

//静态工厂实例化
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//无参构造实例化
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//实例工厂实例化
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
<bean class="org.apache.ibatis.io.Resources" id="inputStream" factory-method="getResourceAsStream">
  <constructor-arg name="resource" value="mybatis-config.xml"></constructor-arg>
</bean>
<bean class="org.apache.ibatis.session.SqlSessionFactoryBuilder" id="sessionFactoryBuilder"></bean>
<bean id="sessionFactory" class="org.apache.ibatis.session.SqlSessionFactory" factory-bean="sessionFactoryBuilder" factory-method="build">
  <constructor-arg name="inputStream" ref="inputStream"></constructor-arg>
</bean>

⭐Bean实例化的基本流程

Spring容器初始化时,会将xml配置的<bean>的信息封装到BeanDefinition对象中,所有的BeanDefinition对象存储到一个名为BeanDefinition集合中,Spring框架在对该集合进行遍历时,使用反射创建Bean实例对象,创建好的Bean对象存储到SingletonObjectsMap集合单例池中,调用getBean时即从单例池中取出bean实例对象并返回

⭐Spring的后处理器

Spring的后处理器是Spring对外开发的一个重要扩展点,允许我们接入到Bean的实例化流程中来,达到动态注册BeanDefinition,动态修改BeanDefinition动态修改Bean对象的作用

⭐BeanFactoryPostProcesser

bean工厂后处理器 一般在填充BeanDefinitionMap之后 Bean实例化之前执行

BeanFactoryPostProcesser是一个接口规范 只要实现了该接口的类交由Spring容器取管理的话 Spring就会调用该接口的postProcesserBeanFactory方法 用于对BeanDefinition的注册和修改

操作beanDefinition
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println("在填充完填充BeanDefinitionMap之后 Bean实例化之前执行");
    //修改beanDefinition
    BeanDefinition userService = beanFactory.getBeanDefinition("userService");
    userService.setBeanClassName("com.heima.dao.Impl.UserDaoImpl");

    //注册beanDefinition
    DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
    BeanDefinition beanDefinition = new RootBeanDefinition();
    beanDefinition.setBeanClassName("com.heima.dao.Impl.PersonDaoImpl");
    defaultListableBeanFactory.registerBeanDefinition("personDao",beanDefinition);

}
<bean class="com.heima.factory.MyBeanFactoryPostProcessor" ></bean>
⭐BeanDefinitionRegistryPostProcessor

Spring提供了BeanFactoryPostProcesser的子接口BeanDefinitionRegistryPostProcessor主要用用注册BeanDefinition

@Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("postProcessBeanDefinitionRegistry执行...");
        //注册beanDefinition
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.heima.dao.Impl.PersonDaoImpl");
        beanDefinitionRegistry.registerBeanDefinition("personDao",beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("postProcessBeanFactory执行...");
    }

需要注意的是postProcessBeanDefinitionRegistry方法是在postProcessBeanFactory方法之前执行的

完善Bean实例化流程图

Demo:使用Spring的BeanFactoryPostProcessor扩展点完成自定义注解扫描

需要注意的是 在配置自定义注解时需要

@Target(ElementType.TYPE)来指定作用范围

@Retention(RetentionPolicy.RUNTIME)来指定存活周期

map的foreach循环用lambda表达式写起来更简洁

@Target(ElementType.TYPE)//作用在类上
@Retention(RetentionPolicy.RUNTIME)//存活到运行期间
public @interface MyComponent {
    String value();
}
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        System.out.println("postProcessBeanDefinitionRegistry执行...");
        Map<String, Class> stringClassMap = BaseClassScanUtils.scanMyComponentAnnotation("com.heima.dao");
        BeanDefinition beanDefinition = new RootBeanDefinition();
        stringClassMap.forEach((beanName,beanClass)->{
            beanDefinition.setBeanClassName(beanClass.getName());
            beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("postProcessBeanFactory执行...");
    }
}
public class BaseClassScanUtils {

    //设置资源规则
    private static final String RESOURCE_PATTERN = "/**/*.class";

    public static Map<String, Class> scanMyComponentAnnotation(String basePackage) {

        //创建容器存储使用了指定注解的Bean字节码对象
        Map<String, Class> annotationClassMap = new HashMap<String, Class>();

        //spring工具类,可以获取指定路径下的全部类
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            //MetadataReader 的工厂类
            MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                //用于读取类信息
                MetadataReader reader = refractory.getMetadataReader(resource);
                //扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                Class<?> clazz = Class.forName(classname);
                //判断是否属于指定的注解类型
                if(clazz.isAnnotationPresent(MyComponent.class)){
                    //获得注解对象
                    MyComponent annotation = clazz.getAnnotation(MyComponent.class);
                    //获得属value属性值
                    String beanName = annotation.value();
                    //判断是否为""
                    if(beanName!=null&&!beanName.equals("")){
                        //存储到Map中去
                        annotationClassMap.put(beanName,clazz);
                        continue;
                    }

                    //如果没有为"",那就把当前类的类名作为beanName
                    annotationClassMap.put(clazz.getSimpleName(),clazz);

                }
            }
        } catch (Exception exception) {
        }

        return annotationClassMap;
    }
}

⭐BeanPostProcesser

bean后处理器 一般在Bean实例化之后 填充单例池之前执行

Bean在实例化之后存入singletonObjects单例池之前还有一个初始化的过程包括了属性注入,初始化方法init调用的等,其中有一个对外进行扩展的点BeanPostProcessor,它跟BeanFactoryPostProcessor相似,是一个接口,实现了该接口并被Spring容器管理的BeanPostProcessor,会在生命周期上被Spring自动调用

before和after方法执行时机

构造器->postProcessorBeforeInitializing->afterPropertiesSet->init->postProcessAfterInitialization

Demo:使用BeanPostProcesser和动态代理对目标Bean方法进行时间增强

在调用目标bean的方法时就会直接打印时间

在代理过程中可以用判断来指定增强哪个bean 或者 bean的哪个方法

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    //使用动态代理对目标bean进行增强 进而存储到singletonObjects单例池中
    return Proxy.newProxyInstance(
        //目标bean的ClassLoader
        bean.getClass().getClassLoader(),
        //目标bean的Interfaces接口
        bean.getClass().getInterfaces(),
        //增强的逻辑 和 具体操作
        (proxy, method, args) -> {
            System.out.println("方法:"+method.getName()+"开始时间:"+new Date().toLocaleString());
            Object invoke = method.invoke(bean, args);
            System.out.println("方法:"+method.getName()+"结束时间:"+new Date().toLocaleString());
            return invoke;
        });
}
再次完善Bean实例化流程图

⭐SpringBean的生命周期

SpringBean的生命周期是从bean实例化,即通过反射创建出bean对象之后,bean经过实例化储过程发展成一个完整的bean对象最终存储到singletonObjects单例池中的过程,这个过程被称为Spring的生命周期,主要分为三个阶段

⭐Bean的实例化阶段

指的是Spring容器遍历取出BeanDefinitionMap中的BeanDefiniton,判断当前Bean的范围是不是singleton,是不是延迟加载,是不是实现了FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化

⭐Bean的初始化阶段

实例化后的bean还只是个半成品,还需要经过属性注入,执行Aware接口方法,执行init方法,执行BeanPostProcessor接口发不发,实现InitializingBean接口的初始化方法过程等.该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,Spring注解功能,Spring循环引用都有体现

Bean实例的属性注入
信息封装

在断点调试中在beanDefinitionMap中还维护着一个名为propertyValues的集合封装着Bean对象的注入信息

属性注入的三种情况

⭐循环依赖

按照单向引用的方法 注入属性时进行到检查spring容器中是否拥有被注入的bean对象时只能从单例池中找 但是这样就形成一个闭环 本注入的bean对象不能经过一系列过程存储到单例池中 这样就引出了三级缓存来解决这个问题

userDao和userService对象相互依赖时会进行以下操作

userService实例化但尚未初始化并存储到三级缓存->userService属性注入,需要userDao,缓存中获取,没有userDao检查容器中是否有userDao->userDao实例化但尚未初始化并存储到三级缓存->userDao属性注入,需要userService,从三级缓存中获取userService,userService从三级缓存中移入二级缓存中->userDao执行其他生命周期过程,最终成为一个完整的Bean存储到单例池中并删除二三级缓存->继续进行userService初始化->检查容器中是否有userDao->单例池中有userDao->注入userDao->完成userService初始化并存储到单例池中并删除二三级缓存->完成真个循环依赖

⭐三级缓存

这三个map集合分别存储不同状态的bean对象

singletonObjects是最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称为一级缓存

earlysingletonObjects是早期Bean单例池,用来存储半成品Bean对象,且当前Bean已经被其他对象引用,称为二级缓存

singletonFactories是早期Bean的工厂池,用来存储半成品Bean(在Spring中将BeanDefinition转化为Object对象的方法通常使用工厂方法来实现),且当前Bean没有被其他对象引用,使用时通过工厂创建Bean,称为三级缓存

在调用getBean方法时 Spring会为刚实例化未进行其他操作的半成品Bean对象提供一个ObjectFactory<?>对象然后存储到singletonFactories三级缓存

⭐Aware接口属性注入

框架一般都是高度封装的,轻易获取不了底层API,Spring可以调用BeanFactoryAware,BeanNameAware,ApplicationContextAware,ServletContextAware等接口的回调方法获取底层功能对象

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    System.out.println("beanFactory = " + beanFactory);
}

@Override
public void setBeanName(String beanName) {
System.out.println("beanName = " + beanName);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println("applicationContext = " + applicationContext);
}

BeanPostProcessor的before()方法回调
InitializingBean接口的初始化方法回调
自定义初始化init方法回调
BeanPostProcessor的after()方法回调

Bean的完成阶段

bean对象经过初始化阶段,成为了一个完整的Bean对象,存储到singletonObjects单例池中,即完成了SpringBean的整个生命周期

⭐SpringIoc容器实例化Bean整体流程

SpringXml方式整合第三方框架

xml整合第三方一般分为两种整合方案

不需要自定义控件,不需要使用Spring的配置文件配置第三方框架本身内容例如:Mybatis

需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容例如:Dubbo

springxml方式整合mybatis

导入pom
<!--        导入数据源-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.8</version>
</dependency>
<!--        导入jdbc驱动-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.1.3.RELEASE</version>
</dependency>
<!--        导入mybaits-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.5</version>
</dependency>
<!--        导入mybatis_spring整合-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.0</version>
</dependency>
编写Mapper和Mapper.xml
⭐配置DateSource sqlSessionFactoryBean和MapperScannerConfigure

<bean id="userService" class="com.heima.service.Impl.UserServiceImpl">
  <!--        这个userMapper是MapperScannerConfigurer产生的Mapper对象可以直接注入-->
  <property name="userMapper" ref="userMapper"></property>
</bean>


<!--    <bean id="userDao" class="com.heima.dao.Impl.UserDaoImpl"></bean>-->

<!--  数据库操作需要数据源配置-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
  <property name="url" value="jdbc:mysql://localhost:3306/book"></property>
  <property name="username" value="root"></property>
  <property name="password" value="123456"></property>
</bean>
<!--    配置SqlSessionFactoryBean 作用是产生SqlSessionFactory对象存储到spring容器中-->
<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.heima.mapper"></property>
</bean>
⭐SqlSessionFactoryBean源码剖析

SqlSessionFactoryBean实现了FactoryBean<SqlSessionFactory>, MapperScannerConfigure接口

大致流程:

SqlSessionFactoryBean->afterPropertiSet()->buildSqlSessionFactory()->SqlSessionFactoryBuilder.build()

详细流程:

属性注入时注入dataSource和SqlSessionFactoryBuilder

回调afterPropertiSet()->回调buildSqlSessionFactory()->注册SqlSessionFactoryBuilder调用build()->产生SqlSessionFactory返回

当外部需要SqlSessionFactory时调用getObject()返回SqlSessionFactory对象

⭐MapperScannerConfigure源码剖析

MapperScannerConfigure实现了BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口

大致流程:

MapperScannerConfigure->postProcessBeanDefinitionRegistry()->ClassPathMapperScanner->scan&doScan->MapperFactoryBean->getOject()

详细流程:

postProcessBeanDefinitionRegistry()->注册ClassPathMapperScanner调用scan()->doScan()返回BeanDefinitionHolder集合->processBeanDefinitions()中循环遍历并设置definition.setBeanClass(this.mapperFactoryBean.getClass())

目的是告诉spring使用mapperFactoryBean作为具体的实现类来创建Mapper接口的Bean(之前definition中的beanClass为UserMapper接口,接口是没办法反射产生对象的)

向definition中的PropertyValues添加sqlSessionTemplate和sqlSessionFactory

这两句代码意味着MapperFactoryBean提供了与SqlSessionFactory和SqlSessionTemplate的自动装配,即它依赖于这两个Bean

设置definition中的属性根据类型自动注入setAutowireMode(AUTOWIRE_BY_TYPE);

这句代码目的是通过设置AUTOWIRE_BY_TYPE模式,调用MapperFactoryBean时会根据类型自动注入属性,也就是SqlSessionFactory,SqlSessionTemplate还有Mapper接口类型(在scan方法过程中,会扫描到Mapper接口类型)在调用<T>getObject时通过getSqlSession().getMapper(this.mapperInterface)创建对应的代理Mapper对象,这样就实现了自动的Mapper接口扫描和代理生成过程.

加载外部properties文件

编写properties文件

加载自定义命名空间context

加载propertie

⭐自定义命名空间解析

如果第三方想要用自定义命名空间的方式跟spring框架集成,需要执行以下流程:

1.在对应的路径依赖的jar包下的META-INF/spring.handlers中用键值对的方式封装命名空间名称和对应的命名空间处理器两者的映射关系(Namespace Handler这是一个类的全限定名,表示用于处理特定自定义命名空间的处理器类。处理器类必须实现Spring的NamespaceHandler接口,并提供相应的命名空间解析和处理逻辑)

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

2.在对应的路径依赖的jar包下的META-INF/spring.schemas中用键值对的方式封装命名空间schema约束文件的虚拟地址和xsd文件相对路径的真实地址的映射关系(XSD Location表示与命名空间URI对应的物理约束文件(XSD)的路径,它可以是相对路径或绝对路径,指定了Spring框架在解析和验证配置文件时需要加载的物理约束文件。)

3.准备好NameSpaceHandler,如果命名空间只有一个标签,那么在parse方法中直接解析,一般解析的结果是注册对应标签的beanDefinition.

如果命名空间中有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser,在执行NameSpaceHandler的parse方法时再分流给不同的BeanDefinitionParser进行解析(重写doParse方法即可)

在自定义命名空间中,有两个重要的约束涉及到物理约束文件和网格约束名称:

  1. 物理约束文件(Physical Constraint File):物理约束文件是一个用于描述自定义命名空间的XML Schema文件(XSD),它定义了自定义命名空间所支持的标签和属性及其结构、数据类型以及约束条件。物理约束文件的作用是为自定义命名空间提供语法和结构的验证,在配置文件中使用时,IDE可以根据物理约束文件提供智能的代码补全、错误检查等功能。
  2. 网格约束名称(Grid Constraint Name):网格约束名称是指在自定义命名空间的物理约束文件中为标签或属性指定的约束条件的名称。网格约束名称可以用于描述标签或属性的值的限制、格式要求、依赖关系等。通过在物理约束文件中定义网格约束名称,可以为自定义标签和属性添加约束条件,以确保配置的准确性和合法性。

通过物理约束文件和网格约束名称的约束,自定义命名空间可以提供更加严格的配置限制和规范,帮助开发者编写正确且符合规范的配置文件。同时,IDE也可以根据这些约束提供更好的开发支持,减少错误和提高开发效率。

⭐Demo:自定义框架与Spring集成

框架编写者步骤分析
1.确定 命名空间名称,schema虚拟地址名称,标签名称
2.编写schema约束文件custom-annotation.xsd
<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns="http://www.heima.com/heima/custom"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://www.heima.com/heima/custom">

  <xsd:element name="annotation-driven"/>

</xsd:schema>
3.在类加载路径下创建META-INF目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
http\://www.heima.com/heima/custom=com.heima.handlers.CustomNamespaceHandler
https\://www.heima.com/heima/custom/custom-annotation.xsd=com/heima/custom/config/custom-annotation.xsd
4.编写命名空间处理器CustomNamespaceHandler,在init方法中注册AnnotationDrivenBeanDefinitionParser

CustomNamespaceHandler需要继承NamespaceHandlerSupport,其中的parse方法已经为我们写过逻辑,根据标签名获取对应的标签解析器再去调用解析器的parse方法对BeanDefiniton进行解析.同时需要实现NamespaceHandler接口的init方法,在init方法中对每一个标签注册一个标签解析器

public class CustomNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    }
}
5.编写标签解析器BeanDefinitionParser,在parse方法中注册BeanPostProcessor

AnnotationDrivenBeanDefinitionParser需要实现BeanDefinitionParser,其中的parse方法会被NamespaceHandlerSupport调用,如果我的标签名是"annotation-driven",那么NamespaceHandlerSupport会帮我们调用定义的AnnotationDrivenBeanDefinitionParser解析器的parse方法解析标签


public class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //注入beanPostProcessor
        BeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.heima.factory.CustomBeanPostProcessor");
        parserContext.getRegistry().registerBeanDefinition("customBeanPostProcessor",beanDefinition);
        return beanDefinition;
    }
}
6.编写CustomBeanPostProcessor
public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization调用...");
        return bean;
        
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization调用...");
        return bean;
    }
}
框架使用者步骤分析
1.在applicationContext.xml配置文件中引入自定义命名空间和schema虚拟地址
2.在applicationContext.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"
       xmlns:custom="http://www.heima.com/heima/custom"
       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
        http://www.heima.com/heima/custom
        https://www.heima.com/heima/custom/custom-annotation.xsd">
<!--    使用自定义命名空间的标签-->
<custom:annotation-driven></custom:annotation-driven>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值