diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java index aae01b667d0..457671646df 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java @@ -314,7 +314,8 @@ protected void processExtraRefresh(String preferredPrefix, InmemoryConfiguration // There may be no interface class when generic call return; } - if (!interfaceClass.isInterface()) { + + if (!interfaceClass.isInterface() && !canSkipInterfaceCheck()) { throw new IllegalStateException(interfaceName + " is not an interface"); } @@ -374,6 +375,15 @@ protected void processExtraRefresh(String preferredPrefix, InmemoryConfiguration } + /** + * it is used for skipping the check of interface since dubbo 3.2 + * rest protocol allow the service is implement class + * @return + */ + protected boolean canSkipInterfaceCheck() { + return false; + } + protected boolean verifyMethodConfig(MethodConfig methodConfig, Class interfaceClass, boolean ignoreInvalidMethodConfig) { String methodName = methodConfig.getName(); if (StringUtils.isEmpty(methodName)) { @@ -923,4 +933,5 @@ public ClassLoader getInterfaceClassLoader() { public void setInterfaceClassLoader(ClassLoader interfaceClassLoader) { this.interfaceClassLoader = interfaceClassLoader; } + } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java b/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java index cd65fc41194..ed2e821368f 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/Constants.java @@ -150,4 +150,6 @@ public interface Constants { String SERVER_THREAD_POOL_NAME = "DubboServerHandler"; String CLIENT_THREAD_POOL_NAME = "DubboClientHandler"; + + String REST_PROTOCOL="rest"; } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java index 57f850c18cb..78e6bf1c16f 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java @@ -51,7 +51,6 @@ public abstract class ServiceConfigBase extends AbstractServiceConfig { private static final long serialVersionUID = 3033787999037024738L; - /** * The interface class of the exported service */ @@ -173,8 +172,8 @@ protected void preProcessRefresh() { convertProviderIdToProvider(); if (provider == null) { provider = getModuleConfigManager() - .getDefaultProvider() - .orElseThrow(() -> new IllegalStateException("Default provider is not initialized")); + .getDefaultProvider() + .orElseThrow(() -> new IllegalStateException("Default provider is not initialized")); } // try set properties from `dubbo.service` if not set in current config refreshWithPrefixes(super.getPrefixes(), ConfigMode.OVERRIDE_IF_ABSENT); @@ -228,7 +227,7 @@ protected void completeCompoundConfigs() { protected void convertProviderIdToProvider() { if (provider == null && StringUtils.hasText(providerIds)) { provider = getModuleConfigManager().getProvider(providerIds) - .orElseThrow(() -> new IllegalStateException("Provider config not found: " + providerIds)); + .orElseThrow(() -> new IllegalStateException("Provider config not found: " + providerIds)); } } @@ -250,7 +249,7 @@ protected void convertProtocolIdsToProtocols() { if (globalProtocol.isPresent()) { tmpProtocols.add(globalProtocol.get()); } else { - throw new IllegalStateException("Protocol not found: "+id); + throw new IllegalStateException("Protocol not found: " + id); } } setProtocols(tmpProtocols); @@ -267,7 +266,7 @@ public Class getInterfaceClass() { try { if (StringUtils.isNotEmpty(interfaceName)) { this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() - .getContextClassLoader()); + .getContextClassLoader()); } } catch (ClassNotFoundException t) { throw new IllegalStateException(t.getMessage(), t); @@ -285,9 +284,9 @@ public void setInterfaceClass(Class interfaceClass) { } - public void setInterface(Class interfaceClass) { - if (interfaceClass != null && !interfaceClass.isInterface()) { + // rest protocol allow set impl class + if (interfaceClass != null && !interfaceClass.isInterface() && !canSkipInterfaceCheck()) { throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!"); } this.interfaceClass = interfaceClass; @@ -297,6 +296,23 @@ public void setInterface(Class interfaceClass) { } } + @Override + public boolean canSkipInterfaceCheck() { + // for multipart protocol so for each contain + List protocols = getProtocols(); + + if (protocols == null) { + return false; + } + + for (ProtocolConfig protocol : protocols) { + if (Constants.REST_PROTOCOL.equals(protocol.getName())) { + return true; + } + } + return false; + } + @Transient public T getRef() { return ref; diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/ControllerServiceConfigTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/ControllerServiceConfigTest.java new file mode 100644 index 00000000000..6f059143a83 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/ControllerServiceConfigTest.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.spring; + +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.spring.api.SpringControllerService; +import org.junit.jupiter.api.Test; + +public class ControllerServiceConfigTest { + + @Test + void testServiceConfig() { + + ServiceConfig serviceServiceConfig = new ServiceConfig<>(); + ApplicationConfig applicationConfig = new ApplicationConfig(); + applicationConfig.setName("dubbo"); + serviceServiceConfig.setApplication(applicationConfig); + serviceServiceConfig.setProtocol(new ProtocolConfig("rest",8080)); + serviceServiceConfig.setRef(new SpringControllerService()); + serviceServiceConfig.setInterface(SpringControllerService.class.getName()); + serviceServiceConfig.export(); + serviceServiceConfig.unexport(); + + + } +} diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/api/SpringControllerService.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/api/SpringControllerService.java new file mode 100644 index 00000000000..da2866f85f5 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/api/SpringControllerService.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.spring.api; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/controller") +public class SpringControllerService { + + @GetMapping("/sayHello") + public String sayHello(String say) { + return say; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/ServiceConfigTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/ServiceConfigTest.java new file mode 100644 index 00000000000..e37f646c08c --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/ServiceConfigTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.rest; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.remoting.http.config.HttpClientConfig; +import org.apache.dubbo.remoting.http.restclient.OKHttpRestClient; +import org.apache.dubbo.rpc.Exporter; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ModuleServiceRepository; +import org.apache.dubbo.rpc.model.ProviderModel; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.rpc.protocol.rest.mvc.SpringControllerService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +public class ServiceConfigTest { + + private final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("rest"); + private final ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + private final ModuleServiceRepository repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository(); + + @AfterEach + public void tearDown() { + protocol.destroy(); + FrameworkModel.destroyAll(); + } + + @Test + void testControllerService() throws Exception { + + int availablePort = NetUtils.getAvailablePort(); + URL url = URL.valueOf("rest://127.0.0.1:" + availablePort + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.mvc.SpringControllerService"); + + SpringControllerService server = new SpringControllerService(); + + url = this.registerProvider(url, server, SpringControllerService.class); + + Exporter exporter = protocol.export(proxy.getInvoker(server, SpringControllerService.class, url)); + + OKHttpRestClient okHttpRestClient = new OKHttpRestClient(new HttpClientConfig()); + + RequestTemplate requestTemplate = new RequestTemplate(null, "GET", "127.0.0.1:" + availablePort); + requestTemplate.path("/controller/sayHello?say=dubbo"); + requestTemplate.addHeader(RestConstant.CONTENT_TYPE, "text/plain"); + requestTemplate.addHeader(RestConstant.ACCEPT, "text/plain"); + requestTemplate.addHeader(RestHeaderEnum.VERSION.getHeader(), "1.0.0"); + + byte[] body = okHttpRestClient.send(requestTemplate).get().getBody(); + + + Assertions.assertEquals("dubbo", new String(body)); + exporter.unexport(); + } + + + private URL registerProvider(URL url, Object impl, Class interfaceClass) { + ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass); + ProviderModel providerModel = new ProviderModel( + url.getServiceKey(), + impl, + serviceDescriptor, + null, + null); + repository.registerProvider(providerModel); + return url.setServiceModel(providerModel); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringControllerService.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringControllerService.java new file mode 100644 index 00000000000..bf761196d46 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringControllerService.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.rest.mvc; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/controller") +public class SpringControllerService { + + @GetMapping("/sayHello") + public String sayHello(String say) { + return say; + } +}