在看 netty
的反射工厂之前,我们先来看一下实际业务项目中,工厂模式是如何使用的。类图交互关系如下图:
实际业务开发可能会用到 Spring 框架,Spring提供了两个特别的接口:ApplicationContextAware
和 InitializingBean
。
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext var1) throws BeansException;
}
BeanFactory
是根接口,也就是我们熟知的管理Bean的工厂,ListableBeanFactory
进一步继承了 BeanFactory
,提供了如下一个方法:
<T> Map<String, T> getBeansOfType(Class<T> var1) throws BeansException;
该方法可以根据类型获取对应的Bean实例。
而 ApplicationContext
继承的众多接口之一,就是继承了 ListableBeanFactory
。ApplicationContext
也称作是 Spring 上下文。这样来看,文章开头的代码就是将 Spring 上下文注入,也就是注入到我们的 TaskManagerFactory
。
另一个 Spring 接口 InitializingBean
,只要实现了该接口的 Bean(我们的 TaskFactory 就通过注解配置成了一个 Bean,比如 @Component),就会在属性初始化之后调用
void afterPropertiesSet() throws Exception;
所以经过上述两个接口的方法设置,我们就成功的在 TaskManagerFactory
初始化的时候将实现了 TaskManager
接口的两个对象:TaskManagerImplA
和 TaskManagerImplB
添加到了 TaskManagerFactory
的 Map 中。
使用者是需要注入 TaskManagerFactory
,并调用 getTaskManger 方法即可。如果要新增一个 任务处理器,只需要在枚举类中添加数值,然后新建一个 TaskManager
的实现即可。
我们常见的例子是工厂类的get方法中,会通过 if else 判断要获取哪个对象,然后实力化后返回。本质上和上面的案例差不多。
那么 netty 上面是如何运用工厂模式,有什么特别的呢?
反射,没错就是用反射的方法来解耦。反射损耗性能x x x,不过不能脱离环境,我们来看经典的 echo server
代码。
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
// 方法内部创建反射工厂
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(serverHandler);
}
});
// 方法内部使用反射工厂创建 ServerSocketChannel 对象
ChannelFuture f = b.bind(PORT).sync();
其中,.channel(NioServerSocketChannel.class)
方法内部实现为:
// 这里的 C 就是 Channel 接口
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
执行完之后,就是将这个实力化的反射工厂对象 ReflectiveChannelFactory
作为成员变量设置到了 ServerBootstrap
中。
而 NioServerSocketChannel
就是 Channel
接口的一种实现类,另外的实现类有 EpollServerSocketChannel
、OioServerSocketChannel(@Deprecated)
等。
在 new ReflectiveChannelFactory
时,我们看下内部实现:
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
}
...
}
}
可见,创建反射工厂时,就已经将 NioServerSocketChannel
的构造器设置进去了。
那么当程序执行 b.bind(PORT).sync();
这条语句时,我们看下 bind 内部是如何利用之前设置的构造器的?
// bind内部调用的一个方法
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 这一步调用方法
channel = channelFactory.newChannel();
init(channel);
}
}
channelFactory.newChannel();
这句的 channelFactory
就是之前初始化进去的。然后 newChannel
方法如下:
public T newChannel() {
try {
return constructor.newInstance();
}
}
可见,直接利用了上面注入的 constructor,也就是 NioServerSocketChannel
类的构造器来创建具体对象的。
这样的好处是,当我们想要新增一个新的channel类型时,比如新增 KQueueServerSocketChannel
,只需要开发这个新的channel实现,然后在 echoServer 里面修改一条语句(这里先忽略上面的 EventLoopGroup
的修改):
//将
.channel(NioServerSocketChannel.class)
//修改为:
.channel(KQueueServerSocketChannel.class)
满足开闭原则。
netty
追求高性能,而反射可能会降低性能,二者矛盾吗?不矛盾,这里的 ServerSocketChannel 相当于是服务器初始化过程中监听的channel,而不是每个连接接入时建立的 channel。对性能没有影响,且应用和维护起来简单。
既然如此,反射工厂模式和文章开头的案例能互换吗?可以,但是看从什么角度考量。
最后,再简单回顾下抽象工厂模式。抽象工厂相当于建立对工厂的抽象,客户端通过实力化不同的工厂,来创建不同种类的对象。如果理解了工厂模式,就可以把抽象工厂当作是工厂模式之上的对工厂的又一层封装。也就不必记忆产品簇产品种类什么的。
感兴趣的可以看看 netty 的源码,简单追踪下即可理解反射工厂的应用,从而对这类设计模式理解更加深刻。