spring揭秘05-ApplicationContext

news2024/9/20 6:24:20

文章目录

  • 【README】
  • 【1】ApplicationContext概述
    • 【1.1】spring通过Resource对文件抽象
    • 【1.2】统一资源加载策略-ResourceLoader
      • 【1.2.1】 DefaultResourceLoader
      • 【1.2.2】FileSystemResourceLoader
      • 【1.2.3】 ResourcePatternResolver批量加载资源
      • 【1.2.4】Resource与ResourceLoader类图
    • 【1.3】ApplicationContext与ResourceLoader
      • 【1.3.1】Resource资源类型注入
      • 【1.3.2】ApplicationContext的Resource加载行为
  • 【2】容器内部事件发布
    • 【2.1】 java自定义事件发布
    • 【2.2】spring容器事件发布
      • 【2.2.1】spring容器事件发布类结构
      • 【2.2.2】spring容器事件发布代码示例
      • 【2.2.3】个人见解
  • 【3】多配置模块加载
    • 【3.1】多配置模块加载代码示例

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

ApplicationContext 继承了 EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver 接口;

  • EnvironmentCapable:与环境变量或配置文件相关;
  • ListableBeanFactory 和 HierarchicalBeanFactory : 都是BeanFactory;
  • MessageSource:国际化(本文先不展开讨论);
  • ApplicationEventPublisher:事件发布;
  • ResourcePatternResolver: 加载资源文件;

考虑到先了解基础功能,本文仅讨论 ApplicationEventPublisher 与 ResourcePatternResolver

【ApplicationContext 】源码

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    @Nullable
    String getId();

    String getApplicationName();

    String getDisplayName();

    long getStartupDate();

    @Nullable
    ApplicationContext getParent();

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

【1】ApplicationContext概述

Spring有2种ioc容器,BeanFactory,ApplicationContext; 其中ApplicationContext 继承自 BeanFactory ,是高级容器,提供了更加丰富的功能;

ApplicationContext 具体实现类;

  • FileSystemXmlApplicationContext: 从文件系统加载bean及相关资源的ApplicationContext实现;
  • ClassPathXmlApplicationContext: 从Classpath加载bean定义及相关资源的ApplicationContext实现;
  • XmlWebApplicationContext: 用于web应用程序的ApplicationContext 实现;

【1.1】spring通过Resource对文件抽象

1)背景:spring通过文件配置(如xml,properties),或者注解来管理对象依赖关系;如果使用文件配置,则spring就需要读取文件;为简化代码,spring使用Resource资源接口对文件进行抽象;

2)Resource资源接口的具体实现:

  • ByteArrayResource: 字节数组资源;
  • ClassPathResource: 从java应用程序的classpath加载资源并封装;
  • FileSystemResource:对 java.io.file 进行封装;可以以文件或URL访问该类型资源;
  • UrlResource: 对 java.net.URL 进行封装;
  • InputStreamResource: 对 InputStream的封装,较少使用;

3)Resource接口定义:

public interface Resource extends InputStreamSource {
    boolean exists();

    default boolean isReadable() {
        return this.exists();
    }

    default boolean isOpen() {
        return false;
    }

    default boolean isFile() {
        return false;
    }

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;
...... 

【1.2】统一资源加载策略-ResourceLoader

1)背景:Resource是spring对文件的抽象;ResourceLoader是spring对资源加载(读取文件)的抽象;

public interface ResourceLoader {
    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String location);

    @Nullable
    ClassLoader getClassLoader();
}

【1.2.1】 DefaultResourceLoader

1) DefaultResourceLoader定义: 默认资源加载器; 该类默认的资源查找处理逻辑如下:

  • 检查路径是否以 classpath: 前缀开头,如果是,则尝试构造 ClassPathResource 资源;
  • 否则: 尝试通过URL, 根据资源路径来定位资源,如果没有抛出异常,则返回 UrlResource类型的资源;
  • 否则,委派 getResourceByPath(String path) 方法 来定位, 默认实现是构造 ClassPathResource类型资源并返回;

【DefaultResourceLoaderMain】 DefaultResourceLoader测试入口

public class DefaultResourceLoaderMain {
    public static void main(String[] args) {
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource("D:/studynote/spring_discover/ApplicationContext.txt");
        System.out.println(resource.getClass()); 
        // class org.springframework.core.io.DefaultResourceLoader$ClassPathContextResource

        Resource fileUrlResource = resourceLoader.getResource("file:D:/studynote/spring_discover/ApplicationContext.txt");
        System.out.println(fileUrlResource.getClass()); 
        // class org.springframework.core.io.FileUrlResource

        Resource urllResource = resourceLoader.getResource("https://www.baidu.com/");
        System.out.println(urllResource.getClass()); 
        // class org.springframework.core.io.UrlResource
    }
}

【1.2.2】FileSystemResourceLoader

FileSystemResourceLoader 继承自 DefaultResourceLoader,覆写 getResourceByPath() 方法, 从文件系统加载资源并以 FileSystemResource 类型返回;

【FileSystemResourceLoaderMain】文件系统资源加载器main

public class FileSystemResourceLoaderMain {
    public static void main(String[] args) {
        ResourceLoader fileSystemResourceLoader = new FileSystemResourceLoader();
        Resource resource = fileSystemResourceLoader.getResource("D:/studynote/spring_discover/ApplicationContext.txt");
        System.out.println(resource.getClass());
        // class org.springframework.core.io.FileSystemResourceLoader$FileSystemContextResource

        Resource fileURlResource = fileSystemResourceLoader.getResource("file:D:/studynote/spring_discover/ApplicationContext.txt");
        System.out.println(fileURlResource.getClass());
        // class org.springframework.core.io.FileUrlResource
    }
}

【1.2.3】 ResourcePatternResolver批量加载资源

1)背景: ResourceLoader每次只能根据资源路径读取单个Resource实例;而 ResourcePatternResolver可以批量加载资源(一次读取多个文件)

【ResourcePatternResolver定义】

public interface ResourcePatternResolver extends ResourceLoader {
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException; // 读取多个资源
}

2)PathMatchingResourcePatternResolver 是ResourcePatternResolver常用的实现类,其本身也是一个 ResourceLoader资源加载器

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
    public PathMatchingResourcePatternResolver() {
        this.resourceLoader = new DefaultResourceLoader();
    }

    public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
        Assert.notNull(resourceLoader, "ResourceLoader must not be null");
        this.resourceLoader = resourceLoader;
    }

    public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
        this.resourceLoader = new DefaultResourceLoader(classLoader);
    }

PathMatchingResourcePatternResolver 支持实现 ResourLoader级别的资源加载, 支持基于ant风格的路径匹配模式(类型 **/*.suffix之类的路径形式), 支持ResourcePatternResolver 新增加的 classpath*: 前缀等

PathMatchingResourcePatternResolver 默认使用 DefaultResourceLoader 资源加载器读取资源;当然可以通过构造器传入 加载器给PathMatchingResourcePatternResolver ;

【ResourcePatternResolverMain】PathMatchingResourcePatternResolver 测试main

public class ResourcePatternResolverMain {
    public static void main(String[] args) throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        // getResources() 批量加载多个文件或读取多个文件
        Resource[] resources = resourcePatternResolver.getResources("file:D:/studynote/spring_discover/ApplicationContext*.txt");
        for (Resource resource : resources) {
            System.out.println(resource.getFile().getName() + ", class=" + resource.getClass());
        }
        // ApplicationContext.txt, class=class org.springframework.core.io.FileSystemResource
        // ApplicationContext2.txt, class=class org.springframework.core.io.FileSystemResource
    }
}

【1.2.4】Resource与ResourceLoader类图

在这里插入图片描述

【1.3】ApplicationContext与ResourceLoader

1)ApplicationContext定义:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    @Nullable
    String getId();

    String getApplicationName();

    String getDisplayName();

    long getStartupDate();

    @Nullable
    ApplicationContext getParent();

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

2)ApplicationContext可以加载任何Resource资源 : ApplicationContext 继承 ResourcePatternResolver,也即ApplicationContext 间接继承 ResourceLoader;这就是 ApplicationContext 支持spring资源加载策略底层原理;即 ApplicationContext实现类就是 ResourceLoader(加载单个资源) 或 ResourcePatternResolver(加载多个资源), ApplicationContext本身就可以加载资源,如加载bean.xml配置文件;

3) AbstractApplicationContext 实现 ApplicationContext 接口,类图如下;

在这里插入图片描述

【1.3.1】Resource资源类型注入

1)业务场景: 发送短信需要短信模版,调用者提供对应参数即可; spring可以把短信模版文件抽象为 Resource,ApplicationContext或ClassPathXmlApplicationContext本身作为 ResourceLoader 加载短信模版文件;

【ResourceTypeInjectMain】Resource资源类型注入main

public class ResourceTypeInjectMain {
    public static void main(String[] args) throws IOException {
        ApplicationContext container =
                new ClassPathXmlApplicationContext("classpath:chapter05/beans0501resourcetypeinject.xml");
        TaskRemindShortMsgSender shortMsgSender = container.getBean("taskRemindShortMsgSender", TaskRemindShortMsgSender.class);
        shortMsgSender.send("张三");
    }
}

【beans0501resourcetypeinject.xml】

【带classpath】

<bean id="taskRemindShortMsgSender" class="com.tom.springnote.chapter05.chapter0501.TaskRemindShortMsgSender">
    <property name="template" value="classpath:chapter05/shortmsg_template.txt" />
</bean>

【不带classpath】 效果一样

<bean id="taskRemindShortMsgSender" class="com.tom.springnote.chapter05.chapter0501.TaskRemindShortMsgSender">
    <property name="template" value="chapter05/shortmsg_template.txt" />
</bean>

【shortmsg_template.txt】短信模版文件

%s 您好,您有待办任务需要处理

【TaskRemindShortMsgSender】任务提醒短信发送器

public class TaskRemindShortMsgSender {

    /** 短信模版 */
    private Resource template;

    public void send(String username) throws IOException {
        System.out.println("发送短信,内容=" + String.format(template.getContentAsString(StandardCharsets.UTF_8), username));
    }

    public Resource getTemplate() {
        return template;
    }

    public void setTemplate(Resource template) {
        this.template = template;
    }
}

【打印日志】

发送短信,内容=张三 您好,您有待办任务需要处理

【spring加载资源文件原理】

ApplicationContext启动时,会通过 ResourceEditorRegistrar(资源编辑器注册器) 来注册spring提供的针对Resource类型的ResourceEditor到容器中;

【1.3.2】ApplicationContext的Resource加载行为

1)对于URL资源: 其资源路径,通常开始都会有一个协议前缀 ,如 file: 、 htttp: 、 ftp: 等;

2) spring扩展了协议前缀的集合,包括 classpath 与 classpath*: ResourceLoader新增了资源路径协议 classpath:, ResourcePatternResolver 新增了 classpath*: ; 我们可以通过 classpath: 或 classpath*: 等协议前缀告知spring容器从classpath中加载资源;

3)ApplicationContext实现类: ClassPathXmlApplicationContext 与 FileSystemXmlApplicationContext

  • ClassPathXmlApplicationContext 默认从classpath加载资源,可以不用指定 classpath 资源协议前缀;
  • FileSystemXmlApplicationContext 默认从文件系统加载,指定classpath协议前缀,才从classpath 加载;

【ResourceTypeInjectMain】不同ApplicationContext使用classpath协议前缀加载资源对比

public class ResourceTypeInjectMain {
    public static void main(String[] args) throws IOException {
        // 使用 ClassPathXmlApplicationContext 加载配置文件(默认从classpath加载资源)
        ApplicationContext container =
                new ClassPathXmlApplicationContext("classpath:chapter05/beans0501resourcetypeinject.xml");
        TaskRemindShortMsgSender shortMsgSender = container.getBean("taskRemindShortMsgSender", TaskRemindShortMsgSender.class);
        shortMsgSender.send("张三");

        // 使用 FileSystemXmlApplicationContext 加载配置文件(注意指定classpath前缀,否则从文件系统加载,文件系统找不到,则报错)
//        ApplicationContext noClasspathContainer = new FileSystemXmlApplicationContext("chapter05/beans0501resourcetypeinject.xml");
        ApplicationContext container2 =
                new FileSystemXmlApplicationContext("classpath:chapter05/beans0501resourcetypeinject.xml");
        TaskRemindShortMsgSender shortMsgSender2 = container2.getBean("taskRemindShortMsgSender", TaskRemindShortMsgSender.class);
        shortMsgSender2.send("张三");
    }
}

【2】容器内部事件发布

【2.1】 java自定义事件发布

1)事件应用场景:

  • 状态更新:如订单状态发生变化时,可以发布一个订单状态更新事件;
  • 异步处理:如用户注册后发送欢迎邮件;
  • 监控和日志:如方法调用监控, 记录运行日志;
  • 缓存更新:如当产品价格更新时,通过事件将新的价格信息更新到缓存中;
  • 权限验证:如当用户登录系统时,发布一个登录成功事件;

2)java自定义事件发布,需要用到 EventObject 和 EventListener 接口; 所有自定义事件类型需要继承 java.util.EventObject 类, 事件监听器需要继承 java.util.EventListener接口

3)自定义事件业务场景(不用spring事件): 方法执行前后打印日志;

【MethodExecutionEvent】 自定义事件

public class MethodExecutionEvent extends EventObject {
    private String methodName;
    public MethodExecutionEvent(Object source) {
        super(source);
    }

    public MethodExecutionEvent(Object source, String methodName) {
        super(source);
        this.methodName = methodName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
}

【IMethodExecutionEventListener】事件监听器接口

public interface IMethodExecutionEventListener extends EventListener {

    void onMethodBegin(MethodExecutionEvent event);
    void onMethodEnd(MethodExecutionEvent event);
}

【MethodExecutionEventListenerImpl】监听器实现类

public class MethodExecutionEventListenerImpl implements IMethodExecutionEventListener {
    @Override
    public void onMethodBegin(MethodExecutionEvent event) {
        System.out.printf("方法%s执行开始\n", event.getMethodName());
    }

    @Override
    public void onMethodEnd(MethodExecutionEvent event) {
        System.out.printf("方法%s执行结束\n", event.getMethodName());
    }
}

【MethodExecutionEventPublisher】事件发布器

public class MethodExecutionEventPublisher {
    private List<IMethodExecutionEventListener> listenerList = new ArrayList<>();

    public void monitorMethod() {
        MethodExecutionEvent event = new MethodExecutionEvent(this, "monitorMethod");
        publishEvent(MethodExecutionStatus.BEGIN, event);
        System.out.println("执行具体业务逻辑");
        publishEvent(MethodExecutionStatus.END, event);
    }

    protected void publishEvent(MethodExecutionStatus status, MethodExecutionEvent event) {
        listenerList.forEach(listener -> {
            if (status.ifBegin()) {
                listener.onMethodBegin(event);
            } else {
                listener.onMethodEnd(event);
            }
        });
    }

    public void addListener(IMethodExecutionEventListener listener) {
        listenerList.add(listener);
    }
}

【MethodExecutionStatus】方法执行状态枚举

public enum MethodExecutionStatus {
    BEGIN,
    END;

    public boolean ifBegin() {
        return BEGIN.equals(this);
    }
}

【MethodExecutionEventPublisherMain】自定义事件发布器main

public class MethodExecutionEventPublisherMain {
    public static void main(String[] args) {
        MethodExecutionEventPublisher eventPublisher = new MethodExecutionEventPublisher();
        eventPublisher.addListener(new MethodExecutionEventListenerImpl());
        eventPublisher.monitorMethod();
    }
}

【打印日志】

方法monitorMethod执行开始
执行具体业务逻辑
方法monitorMethod执行结束

【2.2】spring容器事件发布

【2.2.1】spring容器事件发布类结构

1)ApplicationEvent: spring容器自定义事件类型,继承自 EventObject, 它是一个抽象类, 有3个实现:

  • ContextClosedEvent: spring容器关闭前发布的事件;
  • ContextRefreshedEvent: spring容器在初始化或刷新时发布的事件;
  • RequestHandledEvent: web请求处理后发布的事件;

2)ApplicationListener: spring容器自定义事件监听器接口, 继承自 EventListener;

ApplicationContext容器在启动时, 自动识别并加载 EventListener类型的bean定义, 一旦容器有事件发布,通知监听对应事件的监听器;

3)ApplicationContext:其本身就是事件发布者; 因为它继承了 ApplicationEventPublisher

  • ApplicationContext, 具体说是 AbstractApplicationContext 把事件监听器注册与事件发布逻辑,委托给 ApplicationEventMulticaster接口的实现类
  • 抽象类 AbstractApplicationEventMulticaster 实现了事件多播接口 ApplicationEventMulticaster 接口 ;
  • 具体类 SimpleApplicationEventMulticaster 继承了抽象类 AbstractApplicationEventMulticaster ;提供事件监听器管理,事件发布功能;
    • SimpleApplicationEventMulticaster 底层使用 SyncTaskExecutor 进行事件发布;

【2.2.2】spring容器事件发布代码示例

1) 事件发布者: spring容器中, 事件发布者需要调用ApplicationEventPublisher 发布事件; 而 ApplicationContext 继承了 ApplicationEventPublisher ;所以事件发布者需要实现 ApplicationEventPublisherAware接口 或 ApplicationContextAware接口; 当然, 建议实现ApplicationEventPublisherAware接口, 这样语义更加通顺;

  • 事件发布者就是我们的业务bean,即需要发布事件(如监控事件)的bean;

2)总结: 自定义spring事件发布的代码结构:

  • spring事件:继承 ApplicationEvent;
  • spring事件监听器: 实现 ApplicationListener;
  • spring事件发布者: 实现 ApplicationEventPublisherAware; 获取事件发布api ; 更确切的说,可以把事件发布者当做事件源

【BusiEventPublisherMain】事件发布者入口main

public class BusiEventPublisherMain {
    public static void main(String[] args) {
        ApplicationContext container = new ClassPathXmlApplicationContext("chapter05/beans0503springevent.xml");
        BusiSpringEventPublisher eventPublisher = container.getBean("busiSpringEventPublisher", BusiSpringEventPublisher.class);
        eventPublisher.monitorMethod();
    }
}

【beans0503springevent.xml】

<!-- 注册事件监听器 -->
<bean id="busiSpringEventListenerImpl"
      class="com.tom.springnote.chapter05.chapter0503.springevent.BusiSpringEventListenerImpl" />
<!-- 注册事件发布器 -->
<bean id="busiSpringEventPublisher"
      class="com.tom.springnote.chapter05.chapter0503.springevent.BusiSpringEventPublisher" />

【BusiSpringEvent】自定义spring事件

public class BusiSpringEvent extends ApplicationEvent {
    private String methodName;
    private MethodExecutionStatus status;

    public BusiSpringEvent(Object source, String methodName, MethodExecutionStatus status) {
        super(source);
        this.methodName = methodName;
        this.status = status;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public MethodExecutionStatus getStatus() {
        return status;
    }

    public void setStatus(MethodExecutionStatus status) {
        this.status = status;
    }
}

【BusiSpringEventListenerImpl】事件监听器

public class BusiSpringEventListenerImpl implements ApplicationListener<BusiSpringEvent> {

    @Override
    public void onApplicationEvent(BusiSpringEvent event) {
        String nowText = BusiDatetimeUtils.timestampToDatetime(event.getTimestamp());
        if (event.getStatus().ifBegin()) {
            System.out.printf("%s 方法%s执行开始\n", nowText, event.getMethodName());
        } else {
            System.out.printf("%s 方法%s执行结束\n", nowText, event.getMethodName());
        }
    }
    @Override
    public boolean supportsAsyncExecution() {
        return ApplicationListener.super.supportsAsyncExecution();
    }
}

【BusiSpringEventPublisher】事件发布者 (事件源)

public class BusiSpringEventPublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher eventPublisher;

    public void monitorMethod() {
        BusiSpringEvent beginEvent = new BusiSpringEvent(this, "monitorMethod", MethodExecutionStatus.BEGIN);
        this.eventPublisher.publishEvent(beginEvent); // 手动发布事件
        System.out.println("执行具体业务逻辑");
        BusiSpringEvent endEvent = new BusiSpringEvent(this, "monitorMethod", MethodExecutionStatus.BEGIN);
        this.eventPublisher.publishEvent(endEvent); // 手动发布事件 
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.eventPublisher = applicationEventPublisher;
    }
}

【打印日志】

2024-08-15 16:55:15 方法monitorMethod执行开始
执行具体业务逻辑
2024-08-15 16:55:15 方法monitorMethod执行开始

【2.2.3】个人见解

上述代码示例中, 执行具体业务逻辑前后分别手动发布事件, 这类似于手工实现aop编程,不是真正的aop

但个人认为,有侵入性, 不灵活; 如通过发布事件打印日志, 打印日志不应该侵入业务逻辑才对;


【3】多配置模块加载

1)多配置模块加载: 即加载多个配置文件,如加载多个bean注册及依赖关系配置的xml文件

2)应用场景: 对于DDD领域驱动设计,其有4层,包括表现层,应用层,领域层,基础设施层;每层bean都单独用一个xml配置文件,则整个容器启动时,需要加载多个配置文件;

  • 表现层: 本文用main方法表示;
  • 应用层: 本文用 appService 表示;
  • 领域层: 本文用 DomainService 表示;
  • 基础设置层:本文用dao表示;
  • 防腐层: 在业务模型与技术模型之间,即应用层或领域层与基础设施层之间(**作用:**技术模型底层做改造,仅防腐层改动代码,上游的应用层与领域层不修改代码);

【3.1】多配置模块加载代码示例

本示例用到了 xml配置文件版本的自动装配 autowire(没有使用注解)

  • beans-busi.xml配置文件中beans指定了default-autowire属性为 byType ,即通过了类型自动装配bean之间的依赖关系;
  • beans-support.xml配置文件中bean指定了autowire属性为 byName ,即通过了bean名称自动装配bean之间的依赖关系;

【MultiConfXmlLoadMain】多个配置xml文件加载main

public class MultiConfXmlLoadMain {
    public static void main(String[] args) {
        // 方式1: 详细指定每个bean xml配置文件
        String[] locations = {"chapter0504multiconfxml/conf/beans-busi.xml"
                , "chapter0504multiconfxml/conf/beans-support.xml"
                , "chapter0504multiconfxml/conf/beans-dao.xml"};
        ApplicationContext container = new ClassPathXmlApplicationContext(locations);
        container.getBean("roomBookAppService", RoomBookAppService.class).book("1", "zhangsan");

        // 方式2: 使用通配符, PathMatchingResourcePatternResolver支持基于ant风格的路径匹配模式(类型 **/*.suffix之类的路径形式)
        String[] locationsByWildcard = {"chapter0504multiconfxml/conf/*.xml"};
        ApplicationContext containerByWildcard = new ClassPathXmlApplicationContext(locationsByWildcard);
        containerByWildcard.getBean("roomBookAppService", RoomBookAppService.class).book("通配符2", "通配符lisi");
    }
}

【多个配置文件】在 chapter0504multiconfxml/conf 目录下

【beans-busi.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"
       default-autowire="byType">

    <bean id="roomBookAppService" class="com.tom.springnote.chapter05.chapter0504.RoomBookAppService"  />
    <bean id="roomBookDomainService" class="com.tom.springnote.chapter05.chapter0504.RoomBookDomainService"  />

</beans>

【beans-support.xml】防腐层bean配置

<bean id="roomBookSupportImpl" class="com.tom.springnote.chapter05.chapter0504.RoomBookSupportImpl" autowire="byName" />

【beans-dao.xml】 基础设施层bean配置

<bean id="roomBookDAO" class="com.tom.springnote.chapter05.chapter0504.RoomBookDAO"  />

【RoomBookAppService】

public class RoomBookAppService {

    private RoomBookDomainService roomBookDomainService;

    public void book(String roomId, String customer) {
        roomBookDomainService.book(roomId, customer);
    }

    public void setRoomBookDomainService(RoomBookDomainService roomBookDomainService) {
        this.roomBookDomainService = roomBookDomainService;
    }
}

【RoomBookDomainService】

public class RoomBookDomainService {

    private IRoomBookSupport roomBookSupport;

    public void book(String roomId, String customer) {
        roomBookSupport.saveRoomBookInf(roomId, customer);
    }

    public void setRoomBookSupport(IRoomBookSupport roomBookSupport) {
        this.roomBookSupport = roomBookSupport;
    }
}

【IRoomBookSupport】

public interface IRoomBookSupport {

    void saveRoomBookInf(String roomId, String customer);
}

【RoomBookSupportImpl】

public class RoomBookSupportImpl implements IRoomBookSupport {

    private RoomBookDAO roomBookDAO;

    @Override
    public void saveRoomBookInf(String roomId, String customer) {
        roomBookDAO.insertRoomBook(roomId, customer);
    }

    public void setRoomBookDAO(RoomBookDAO roomBookDAO) {
        this.roomBookDAO = roomBookDAO;
    }
}

【RoomBookDAO】

public class RoomBookDAO {

    public void insertRoomBook(String roomId, String customer) {
        System.out.printf("RoomBookDAO: 插入订房信息成功:roomId=[%s], customer=[%s]\n", roomId, customer);
    }
}

【打印日志】

RoomBookDAO: 插入订房信息成功:roomId=[1], customer=[zhangsan]
RoomBookDAO: 插入订房信息成功:roomId=[通配符2], customer=[通配符lisi]


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2044195.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用住宅代理抓取奥运奖牌新闻,全面掌握赛事精彩瞬间

引言 什么是新闻抓取&#xff1f;目的是什么&#xff1f; 新闻抓取有哪些好处&#xff1f; 为什么需要关注奥运奖牌新闻&#xff1f; 如何进行新闻抓取——以Google 新闻为例 总结 引言 近日&#xff0c;巴黎奥运会圆满落幕&#xff0c;在这16天中&#xff0c;全球顶尖运…

一问讲透什么是 RAG,为什么需要 RAG?

一. 为什么要用 RAG &#xff1f; 如果使用 pretrain 好的 LLM 模型&#xff0c;应用在你个人的情境中&#xff0c;势必会有些词不达意的地方&#xff0c;例如问 LLM 你个人的信息&#xff0c;那么它会无法回答;这种情况在企业内部也是一样&#xff0c;例如使用 LLM 来回答企业…

VTK—vtkRectilinearGrid学习

vtkRectilinearGrid理解为沿着坐标轴方向一系列规格的网格&#xff0c;但是网格间距可以不同。需要显式的提供各坐标轴的点数据。单元数据不用指定&#xff0c;会隐式生成。与前面提到的vtkStructuredGrid 类似&#xff0c;但是每个网格线都是直的。 1.给三个坐标分配点&#…

Golang基于DTM的分布式事务SAGA实战

SAGA介绍 SAGA是“长时间事务”运作效率的方法&#xff0c;大致思路是把一个大事务分解为可以交错运行的一系列子事务的集合。原本提出 SAGA 的目的&#xff0c;是为了避免大事务长时间锁定数据库的资源&#xff0c;后来才逐渐发展成将一个分布式环境中的大事务&#xff0c;分…

关于tresos Studio(EB)的MCAL配置之DIO

General Dio Development Error Detect开发者错误检测 Dio Flip Channel Api翻转通道电平接口Dio_FlipChannel是否启用 Dio Version Info Api决定Dio_GetVersionInfo接口是否启用&#xff0c;一般打开就行。 Dio Reverse Port Bits让端口的位&#xff08;通道&#xff09;进…

最新号卡推广单页源码/仿制手机卡流量卡号卡代理推广源码/简洁实用/带弹窗公告+后台管理

源码简介&#xff1a; 最新号卡推广单页源码&#xff0c;它是手机卡流量卡号卡代理推广源码量身打造的&#xff0c;不仅设计得简洁实用&#xff0c;而且还有炫酷的弹窗公告功能和强大的后台管理系统哦&#xff01; 一款号卡推广单页源码&#xff0c;自己仿制来的&#xff0c;…

arcgis-坡度坡向分析

坡向的描述有定性和定量两种方式&#xff0c;定量是以东为0&#xff0c;顺时针递增&#xff0c;南为90&#xff0c;西为180&#xff0c;北为270等&#xff0c;范围在0&#xff5e;35959′59″之间。 定性描述有8方向法和4方向法. 8 方向为东、东南、南、西南、西、西北、北、东…

Linux安装jdk8,tomcat和mysql

目录 Linux安装jdk8 第一步&#xff1a;下载jdk8 第二步&#xff1a;把下载好的压缩包通过finalShell发送到linux虚拟机上 ​编辑 第三步&#xff1a;解压缩 第四步&#xff1a;配置环境变量 第五步&#xff1a;重新加载profile配置文件 第六步&#xff1a;检查是否安装成…

C++ -- 负载均衡式在线OJ (三)

文章目录 四、oj_server模块1. oj_server的功能路由2. 建立文件版的题库3. model模块4.controller模块5.judge模块&#xff08;负载均衡&#xff09;6.view模块整体代码结构&#xff08;前端的东西&#xff0c;不是重点&#xff09; 五、最终效果项目源码 前面部分请看这里C –…

Unite Shanghai 2024 团结引擎专场 | 团结引擎实时全局光照

在 2024 年 7 月 24 日的 Unite Shanghai 2024 团结引擎专场演讲中&#xff0c;Unity 中国高级技术经理周赫带大家深入解析了团结引擎的实时全局光照系统。该系统支持完全动态的场景、动态材质和动态灯光的 GI 渲染&#xff0c;包括无限次弹射的漫反射和镜面反射 GI。 周赫&…

2024年职场常备!3款高效数据恢复软件免费版,让打工人工作无忧

嘿&#xff0c;职场的朋友们&#xff01;咱们现在工作&#xff0c;数据就跟空气一样重要&#xff0c;对吧&#xff1f;但有时候&#xff0c;手一滑&#xff0c;文件没了&#xff0c;硬盘突然就挂了&#xff0c;系统也闹点小情绪&#xff0c;那心情&#xff0c;比股市大跌还难受…

基于Django的boss直聘数据分析可视化系统的设计与实现

研究背景 随着互联网的发展&#xff0c;在线招聘平台逐渐成为求职者与企业之间的重要桥梁。Boss直聘作为国内领先的招聘平台&#xff0c;以其独特的直聊模式和高效的匹配算法&#xff0c;吸引了大量的用户。然而&#xff0c;随着平台用户的增长&#xff0c;海量的招聘数据带来…

基于Faster-RCNN的停车场空位检测,支持图像和视频检测(pytorch框架)【python源码+UI界面+功能源码详解】

功能演示&#xff1a; 基于Faster-RCNN的停车场空位检测系统&#xff0c;支持图像检测和视频检测&#xff08;pytorch框架&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于Faster-RCNN的停车场空位检测系统是在pytorch框架下实现的&#xff0c;这是一个…

YB5214B 同步开关型降压锂电池充电管理芯片

概述&#xff1a; 是一款支持 4.5-16V 输入电压范围&#xff0c;最大输出为 2A 电流的同步降压锂电池充电管理芯片。芯片内部集成了低阻功率 MOSFETS&#xff0c;采用 500kHz的开关频率以实现较小的元件尺寸和较高的充电效率。 内部还集成了多重保护功能&#xff0c;能够最大程…

基于NXP IMX6Q+FPGA全自动血液分析仪解决方案

全自动血细胞分析仪 &#xff0c;临床又称血常规检测仪、血液分析仪、血球分析仪、血液细胞分析仪、血球计数仪&#xff0c;是指对一定体积全血内血细胞异质性进行自动分析的临床检验常规仪器。 NXP IMX6Q核心板采用四核Cortex-A9架构&#xff0c;主频1GHz&#xff0c;12层PCB…

知识竞赛中风险题环节竞赛规则有哪些设计方案

风险题环节是知识竞赛活动中一个高潮环节&#xff0c;很多时候都是放到最后压轴&#xff0c;选手会根据之前的成绩进行最后一博。那么&#xff0c;常用的风险题环节规则应怎么设计呢&#xff1f;下面列出的这些大家可以参考一下。 1.设置不同分值的题&#xff0c;由选手根据自…

CSS——字体背景(Font Background)

一、字体族 1、字体的相关样式&#xff1a; ① color 用来设置字体颜色&#xff08;前景颜色&#xff09; ② font-size 字体的大小 和font-size相关的单位&#xff1a; em 相对于当前元素的一个font-size rem 相对于根元素的一个font-size ③ font-family 字体族&#x…

软件测试第4章 白盒测试方法(逻辑覆盖测试)

一、白盒测试方法 二、白盒测试 VS 静态测试 【在不运行程序的情况下(即静态测试&#xff0c;程序审查)】 三、白盒测试方法 1、程序控制流图 2、逻辑覆盖测试 测试覆盖率 用于确定测试所执行到的覆盖项的百分比&#xff0c;其中覆盖项是指作为测试基础的一个入口或属性&am…

异常信息转储笔记-demangle函数名字符

前情 上一篇笔记留下了两个待解决问题&#xff0c;其中之一是输出的函数名被奇怪字符覆盖了一部分&#xff0c;本篇笔记即将解决这个问题&#xff08;下图问题1&#xff09;。 问题描述 如上&#xff0c;使用libunwind输出core堆栈信息时&#xff0c;有部分字符被覆盖&#x…

fetch_20newsgroups报错403的两种解决办法

在使用sklearn机器学习库使用fetch_20newsgroups调用数据集时候&#xff08;如下方代码所示&#xff09;&#xff0c;报错403怎么办&#xff0c;本人亲测两种方法&#xff0c;分享大家&#xff1a; data fetch_20newsgroup(subset"train")一、第一种方法 1.下载压…