do not use ServiceLoader instances across threads
This commit is contained in:
parent
d624688f08
commit
a270ea2854
6 changed files with 116 additions and 117 deletions
|
@ -1,3 +1,3 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = net-http
|
name = net-http
|
||||||
version = 4.3.0
|
version = 4.4.0
|
||||||
|
|
|
@ -53,8 +53,7 @@ class Https1Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGoogleHttp() throws Exception {
|
void testGoogleHttp() throws Exception {
|
||||||
NettyHttpClientConfig config = new NettyHttpsClientConfig()
|
NettyHttpClientConfig config = new NettyHttpsClientConfig();
|
||||||
.setProtocolNegotiation(true);
|
|
||||||
try (NettyHttpClient client = NettyHttpClient.builder()
|
try (NettyHttpClient client = NettyHttpClient.builder()
|
||||||
.setConfig(config)
|
.setConfig(config)
|
||||||
.build()) {
|
.build()) {
|
||||||
|
|
|
@ -37,9 +37,7 @@ public class NettyHttpClient implements HttpClient<HttpRequest, HttpResponse>, C
|
||||||
|
|
||||||
private final AtomicBoolean closed;
|
private final AtomicBoolean closed;
|
||||||
|
|
||||||
private final HttpChannelInitializer httpChannelInitializer;
|
private HttpChannelInitializer httpChannelInitializer;
|
||||||
|
|
||||||
private final ServiceLoader<HttpChannelInitializer> httpChannelInitializerServiceLoader;
|
|
||||||
|
|
||||||
private Pool pool;
|
private Pool pool;
|
||||||
|
|
||||||
|
@ -53,7 +51,6 @@ public class NettyHttpClient implements HttpClient<HttpRequest, HttpResponse>, C
|
||||||
this.bootstrap = bootstrap;
|
this.bootstrap = bootstrap;
|
||||||
this.closed = new AtomicBoolean(false);
|
this.closed = new AtomicBoolean(false);
|
||||||
this.httpChannelInitializer = builder.httpChannelInitializer;
|
this.httpChannelInitializer = builder.httpChannelInitializer;
|
||||||
this.httpChannelInitializerServiceLoader = ServiceLoader.load(HttpChannelInitializer.class);
|
|
||||||
createBoundedPool(builder.nettyHttpClientConfig, bootstrap);
|
createBoundedPool(builder.nettyHttpClientConfig, bootstrap);
|
||||||
this.interactions = new CopyOnWriteArrayList<>();
|
this.interactions = new CopyOnWriteArrayList<>();
|
||||||
}
|
}
|
||||||
|
@ -209,15 +206,23 @@ public class NettyHttpClient implements HttpClient<HttpRequest, HttpResponse>, C
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lookup here needs to be thread-safe.
|
||||||
|
* @param httpAddress the HTTP address for the channel initializer to look up.
|
||||||
|
* @return the channel initializer
|
||||||
|
*/
|
||||||
private HttpChannelInitializer lookupChannelInitializer(HttpAddress httpAddress) {
|
private HttpChannelInitializer lookupChannelInitializer(HttpAddress httpAddress) {
|
||||||
if (httpChannelInitializer != null || httpAddress == null) {
|
if (httpChannelInitializer != null || httpAddress == null) {
|
||||||
return httpChannelInitializer;
|
return httpChannelInitializer;
|
||||||
}
|
}
|
||||||
for (HttpChannelInitializer initializer : httpChannelInitializerServiceLoader) {
|
synchronized (this) {
|
||||||
|
for (HttpChannelInitializer initializer : ServiceLoader.load(HttpChannelInitializer.class)) {
|
||||||
if (initializer.supports(httpAddress)) {
|
if (initializer.supports(httpAddress)) {
|
||||||
|
httpChannelInitializer = initializer;
|
||||||
return initializer;
|
return initializer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
throw new IllegalStateException("no channel initializer found for address " + httpAddress + ", check service provider");
|
throw new IllegalStateException("no channel initializer found for address " + httpAddress + ", check service provider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ public class NettyHttpClientBuilder {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(NettyHttpClientBuilder.class.getName());
|
private static final Logger logger = Logger.getLogger(NettyHttpClientBuilder.class.getName());
|
||||||
|
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
NettyHttpClientConfig nettyHttpClientConfig;
|
NettyHttpClientConfig nettyHttpClientConfig;
|
||||||
|
|
||||||
ByteBufAllocator byteBufAllocator;
|
ByteBufAllocator byteBufAllocator;
|
||||||
|
@ -93,68 +95,65 @@ public class NettyHttpClientBuilder {
|
||||||
if (byteBufAllocator == null) {
|
if (byteBufAllocator == null) {
|
||||||
byteBufAllocator = ByteBufAllocator.DEFAULT;
|
byteBufAllocator = ByteBufAllocator.DEFAULT;
|
||||||
}
|
}
|
||||||
EventLoopGroup myEventLoopGroup = createEventLoopGroup(nettyHttpClientConfig, eventLoopGroup);
|
createEventLoopGroup(nettyHttpClientConfig);
|
||||||
Class<? extends SocketChannel> mySocketChannelClass = createChannelClass(nettyHttpClientConfig, socketChannelClass);
|
createChannelClass(nettyHttpClientConfig);
|
||||||
Bootstrap bootstrap = createBootstrap(nettyHttpClientConfig, byteBufAllocator, myEventLoopGroup, mySocketChannelClass);
|
Bootstrap bootstrap = createBootstrap(nettyHttpClientConfig, byteBufAllocator, eventLoopGroup, socketChannelClass);
|
||||||
if (nettyCustomizer != null) {
|
if (nettyCustomizer != null) {
|
||||||
nettyCustomizer.afterBootstrapInitialized(bootstrap);
|
nettyCustomizer.afterBootstrapInitialized(bootstrap);
|
||||||
}
|
}
|
||||||
return new NettyHttpClient(this, myEventLoopGroup, bootstrap);
|
return new NettyHttpClient(this, eventLoopGroup, bootstrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected NettyHttpClientConfig createEmptyConfig() {
|
protected NettyHttpClientConfig createEmptyConfig() {
|
||||||
return new NettyHttpClientConfig();
|
return new NettyHttpClientConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EventLoopGroup createEventLoopGroup(NettyHttpClientConfig clientConfig,
|
private void createEventLoopGroup(NettyHttpClientConfig clientConfig) {
|
||||||
EventLoopGroup eventLoopGroup) {
|
|
||||||
if (eventLoopGroup != null) {
|
if (eventLoopGroup != null) {
|
||||||
return eventLoopGroup;
|
return;
|
||||||
}
|
}
|
||||||
EventLoopGroup myEventLoopGroup = null;
|
synchronized (lock) {
|
||||||
ThreadFactory threadFactory = new NamedThreadFactory("org-xbib-net-http-netty-client");
|
ThreadFactory threadFactory = new NamedThreadFactory("org-xbib-net-http-netty-client");
|
||||||
ServiceLoader<ClientTransportProvider> transportProviders = ServiceLoader.load(ClientTransportProvider.class);
|
for (ClientTransportProvider serverTransportProvider : ServiceLoader.load(ClientTransportProvider.class)) {
|
||||||
for (ClientTransportProvider serverTransportProvider : transportProviders) {
|
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINEST)) {
|
||||||
logger.log(Level.FINEST, "found event loop group provider = " + serverTransportProvider);
|
logger.log(Level.FINEST, "found event loop group provider = " + serverTransportProvider);
|
||||||
}
|
}
|
||||||
if (clientConfig.getTransportProviderName() == null || clientConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
if (clientConfig.getTransportProviderName() == null || clientConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
||||||
myEventLoopGroup = serverTransportProvider.createEventLoopGroup(clientConfig.getThreadCount(), threadFactory);
|
eventLoopGroup = serverTransportProvider.createEventLoopGroup(clientConfig.getThreadCount(), threadFactory);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (myEventLoopGroup == null) {
|
if (eventLoopGroup == null) {
|
||||||
myEventLoopGroup = new NioEventLoopGroup(clientConfig.getThreadCount(), threadFactory);
|
eventLoopGroup = new NioEventLoopGroup(clientConfig.getThreadCount(), threadFactory);
|
||||||
}
|
}
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINEST)) {
|
||||||
logger.log(Level.FINEST, "event loop group class: " + myEventLoopGroup.getClass().getName());
|
logger.log(Level.FINEST, "event loop group class: " + eventLoopGroup.getClass().getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return myEventLoopGroup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<? extends SocketChannel> createChannelClass(NettyHttpClientConfig clientConfig,
|
private void createChannelClass(NettyHttpClientConfig clientConfig) {
|
||||||
Class<? extends SocketChannel> socketChannelClass) {
|
|
||||||
if (socketChannelClass != null) {
|
if (socketChannelClass != null) {
|
||||||
return socketChannelClass;
|
return;
|
||||||
}
|
}
|
||||||
Class<? extends SocketChannel> myChannelClass = null;
|
synchronized (lock) {
|
||||||
ServiceLoader<ClientTransportProvider> transportProviders = ServiceLoader.load(ClientTransportProvider.class);
|
ServiceLoader<ClientTransportProvider> transportProviders = ServiceLoader.load(ClientTransportProvider.class);
|
||||||
for (ClientTransportProvider transportProvider : transportProviders) {
|
for (ClientTransportProvider transportProvider : transportProviders) {
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINEST)) {
|
||||||
logger.log(Level.FINEST, "found socket channel provider = " + transportProvider);
|
logger.log(Level.FINEST, "found socket channel provider = " + transportProvider);
|
||||||
}
|
}
|
||||||
if (clientConfig.getTransportProviderName() == null || clientConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) {
|
if (clientConfig.getTransportProviderName() == null || clientConfig.getTransportProviderName().equals(transportProvider.getClass().getName())) {
|
||||||
myChannelClass = transportProvider.createSocketChannelClass();
|
socketChannelClass = transportProvider.createSocketChannelClass();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (myChannelClass == null) {
|
if (socketChannelClass == null) {
|
||||||
myChannelClass = NioSocketChannel.class;
|
socketChannelClass = NioSocketChannel.class;
|
||||||
}
|
}
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINEST)) {
|
||||||
logger.log(Level.FINEST, "socket channel class: " + myChannelClass.getName());
|
logger.log(Level.FINEST, "socket channel class: " + socketChannelClass.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return myChannelClass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bootstrap createBootstrap(NettyHttpClientConfig nettyHttpClientConfig,
|
private static Bootstrap createBootstrap(NettyHttpClientConfig nettyHttpClientConfig,
|
||||||
|
|
|
@ -49,24 +49,18 @@ public class NettyHttpServer implements HttpServer {
|
||||||
|
|
||||||
private final Class<? extends ServerSocketChannel> socketChannelClass;
|
private final Class<? extends ServerSocketChannel> socketChannelClass;
|
||||||
|
|
||||||
private final HttpChannelInitializer httpChannelInitializer;
|
|
||||||
|
|
||||||
private final ServiceLoader<HttpChannelInitializer> serviceLoader;
|
|
||||||
|
|
||||||
private final Collection<ChannelFuture> channelFutures;
|
private final Collection<ChannelFuture> channelFutures;
|
||||||
|
|
||||||
private final Collection<Channel> channels;
|
private final Collection<Channel> channels;
|
||||||
|
|
||||||
NettyHttpServer(NettyHttpServerBuilder builder,
|
private HttpChannelInitializer httpChannelInitializer;
|
||||||
EventLoopGroup parentEventLoopGroup,
|
|
||||||
EventLoopGroup childEventLoopGroup,
|
NettyHttpServer(NettyHttpServerBuilder builder) {
|
||||||
Class<? extends ServerSocketChannel> socketChannelClass) {
|
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
this.parentEventLoopGroup = parentEventLoopGroup;
|
this.parentEventLoopGroup = builder.parentEventLoopGroup;
|
||||||
this.childEventLoopGroup = childEventLoopGroup;
|
this.childEventLoopGroup = builder.childEventLoopGroup;
|
||||||
this.socketChannelClass = socketChannelClass;
|
this.socketChannelClass = builder.socketChannelClass;
|
||||||
this.httpChannelInitializer = builder.httpChannelInitializer;
|
this.httpChannelInitializer = builder.httpChannelInitializer;
|
||||||
this.serviceLoader = ServiceLoader.load(HttpChannelInitializer.class);
|
|
||||||
this.channelFutures = new ArrayList<>();
|
this.channelFutures = new ArrayList<>();
|
||||||
this.channels = new ArrayList<>();
|
this.channels = new ArrayList<>();
|
||||||
logger.log(Level.FINE, "parent event loop group = " + parentEventLoopGroup +
|
logger.log(Level.FINE, "parent event loop group = " + parentEventLoopGroup +
|
||||||
|
@ -129,7 +123,8 @@ public class NettyHttpServer implements HttpServer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
channel.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).set(httpAddress);
|
channel.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).set(httpAddress);
|
||||||
createChannelInitializer(httpAddress).init(channel, getServer(), builder.nettyCustomizer);
|
createChannelInitializer(httpAddress);
|
||||||
|
httpChannelInitializer.init(channel, getServer(), builder.nettyCustomizer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (getNettyHttpServerConfig().isDebug()) {
|
if (getNettyHttpServerConfig().isDebug()) {
|
||||||
|
@ -248,15 +243,18 @@ public class NettyHttpServer implements HttpServer {
|
||||||
logger.log(Level.INFO, "server shutdown complete");
|
logger.log(Level.INFO, "server shutdown complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpChannelInitializer createChannelInitializer(HttpAddress address) {
|
private void createChannelInitializer(HttpAddress address) {
|
||||||
if (httpChannelInitializer != null && httpChannelInitializer.supports(address)) {
|
if (httpChannelInitializer != null && httpChannelInitializer.supports(address)) {
|
||||||
return httpChannelInitializer;
|
return;
|
||||||
}
|
}
|
||||||
for (HttpChannelInitializer httpChannelInitializer : serviceLoader) {
|
synchronized (this) {
|
||||||
|
for (HttpChannelInitializer httpChannelInitializer : ServiceLoader.load(HttpChannelInitializer.class)) {
|
||||||
if (httpChannelInitializer.supports(address)) {
|
if (httpChannelInitializer.supports(address)) {
|
||||||
return httpChannelInitializer;
|
this.httpChannelInitializer = httpChannelInitializer;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("no channel initializer found for address " + address);
|
throw new IllegalStateException("no channel initializer found for address " + address);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
public class NettyHttpServerBuilder implements HttpServerBuilder {
|
public class NettyHttpServerBuilder implements HttpServerBuilder {
|
||||||
|
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
NettyHttpServerConfig nettyHttpServerConfig;
|
NettyHttpServerConfig nettyHttpServerConfig;
|
||||||
|
|
||||||
Application application;
|
Application application;
|
||||||
|
@ -83,70 +85,66 @@ public class NettyHttpServerBuilder implements HttpServerBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public NettyHttpServer build() {
|
public NettyHttpServer build() {
|
||||||
return new NettyHttpServer(this,
|
createParentEventLoopGroup(nettyHttpServerConfig);
|
||||||
createParentEventLoopGroup(nettyHttpServerConfig, parentEventLoopGroup),
|
createChildEventLoopGroup(nettyHttpServerConfig);
|
||||||
createChildEventLoopGroup(nettyHttpServerConfig, childEventLoopGroup),
|
createSocketChannelClass(nettyHttpServerConfig);
|
||||||
createSocketChannelClass(nettyHttpServerConfig, socketChannelClass));
|
return new NettyHttpServer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EventLoopGroup createParentEventLoopGroup(NettyHttpServerConfig httpServerConfig,
|
private void createParentEventLoopGroup(NettyHttpServerConfig httpServerConfig) {
|
||||||
EventLoopGroup parentEventLoopGroup) {
|
|
||||||
if (parentEventLoopGroup != null) {
|
if (parentEventLoopGroup != null) {
|
||||||
return parentEventLoopGroup;
|
return;
|
||||||
}
|
}
|
||||||
EventLoopGroup eventLoopGroup = null;
|
synchronized (lock) {
|
||||||
ThreadFactory threadFactory = new NamedThreadFactory("org-xbib-net-http-netty-server-parent");
|
ThreadFactory threadFactory = new NamedThreadFactory("org-xbib-net-http-netty-server-parent");
|
||||||
ServiceLoader<ServerTransportProvider> transportProviders = ServiceLoader.load(ServerTransportProvider.class);
|
for (ServerTransportProvider serverTransportProvider : ServiceLoader.load(ServerTransportProvider.class)) {
|
||||||
for (ServerTransportProvider serverTransportProvider : transportProviders) {
|
|
||||||
if (httpServerConfig.getTransportProviderName() == null ||
|
if (httpServerConfig.getTransportProviderName() == null ||
|
||||||
httpServerConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
httpServerConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
||||||
eventLoopGroup = serverTransportProvider.createEventLoopGroup(httpServerConfig.getParentThreadCount(),
|
parentEventLoopGroup = serverTransportProvider.createEventLoopGroup(httpServerConfig.getParentThreadCount(),
|
||||||
threadFactory);
|
threadFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (eventLoopGroup == null) {
|
if (parentEventLoopGroup == null) {
|
||||||
eventLoopGroup = new NioEventLoopGroup(httpServerConfig.getParentThreadCount(), threadFactory);
|
parentEventLoopGroup = new NioEventLoopGroup(httpServerConfig.getParentThreadCount(), threadFactory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return eventLoopGroup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EventLoopGroup createChildEventLoopGroup(NettyHttpServerConfig httpServerConfig,
|
private void createChildEventLoopGroup(NettyHttpServerConfig httpServerConfig) {
|
||||||
EventLoopGroup childEventLoopGroup) {
|
|
||||||
if (childEventLoopGroup != null) {
|
if (childEventLoopGroup != null) {
|
||||||
return childEventLoopGroup;
|
return;
|
||||||
}
|
}
|
||||||
EventLoopGroup eventLoopGroup = null;
|
synchronized (this) {
|
||||||
ThreadFactory threadFactory = new NamedThreadFactory("org-xbib-net-http-netty-server-child");
|
ThreadFactory threadFactory = new NamedThreadFactory("org-xbib-net-http-netty-server-child");
|
||||||
ServiceLoader<ServerTransportProvider> transportProviders = ServiceLoader.load(ServerTransportProvider.class);
|
ServiceLoader<ServerTransportProvider> transportProviders = ServiceLoader.load(ServerTransportProvider.class);
|
||||||
for (ServerTransportProvider serverTransportProvider : transportProviders) {
|
for (ServerTransportProvider serverTransportProvider : transportProviders) {
|
||||||
if (httpServerConfig.getTransportProviderName() == null ||
|
if (httpServerConfig.getTransportProviderName() == null ||
|
||||||
httpServerConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
httpServerConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
||||||
eventLoopGroup = serverTransportProvider.createEventLoopGroup(httpServerConfig.getChildThreadCount(),
|
childEventLoopGroup = serverTransportProvider.createEventLoopGroup(httpServerConfig.getChildThreadCount(),
|
||||||
threadFactory);
|
threadFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (eventLoopGroup == null) {
|
if (childEventLoopGroup == null) {
|
||||||
eventLoopGroup = new NioEventLoopGroup(httpServerConfig.getChildThreadCount(), threadFactory);
|
childEventLoopGroup = new NioEventLoopGroup(httpServerConfig.getChildThreadCount(), threadFactory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return eventLoopGroup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<? extends ServerSocketChannel> createSocketChannelClass(NettyHttpServerConfig httpServerConfig,
|
private void createSocketChannelClass(NettyHttpServerConfig httpServerConfig) {
|
||||||
Class<? extends ServerSocketChannel> socketChannelClass) {
|
|
||||||
if (socketChannelClass != null) {
|
if (socketChannelClass != null) {
|
||||||
return socketChannelClass;
|
return;
|
||||||
}
|
}
|
||||||
Class<? extends ServerSocketChannel> channelClass = null;
|
synchronized (lock) {
|
||||||
ServiceLoader<ServerTransportProvider> transportProviders = ServiceLoader.load(ServerTransportProvider.class);
|
ServiceLoader<ServerTransportProvider> transportProviders = ServiceLoader.load(ServerTransportProvider.class);
|
||||||
for (ServerTransportProvider serverTransportProvider : transportProviders) {
|
for (ServerTransportProvider serverTransportProvider : transportProviders) {
|
||||||
if (httpServerConfig.getTransportProviderName() == null || httpServerConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
if (httpServerConfig.getTransportProviderName() == null || httpServerConfig.getTransportProviderName().equals(serverTransportProvider.getClass().getName())) {
|
||||||
channelClass = serverTransportProvider.createServerSocketChannelClass();
|
socketChannelClass = serverTransportProvider.createServerSocketChannelClass();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (channelClass == null) {
|
if (socketChannelClass == null) {
|
||||||
channelClass = NioServerSocketChannel.class;
|
socketChannelClass = NioServerSocketChannel.class;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return channelClass;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue