【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]