Closed CC007 closed 5 years ago
Hi, can you provide your pom.xml?
Nvm, im stupid. I only had the HttpClient in my dependencyManagement, not my dependencies. It did however expose another issue where it couldn't find a bean that is definitely in the scanned packages:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.github.cc007.headsplugin.rest.clients.MineSkinClient' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1655) ~[?:?]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1214) ~[?:?]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1168) ~[?:?]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[?:?]
My Application
class looks like this:
@SpringBootApplication(scanBasePackages = {"com.github.cc007.headsplugin"})
public class Application
{}
and I have a FeignConfig
class like this:
package com.github.cc007.headsplugin.config;
...
@Configuration
@EnableFeignClients
public class FeignConfig
{}
With the feign client like this:
package com.github.cc007.headsplugin.rest.clients;
...
@Component
@FeignClient(name = "MineSkin", url = "http://api.mineskin.org/")
public interface MineSkinClient
{
@RequestMapping(method = RequestMethod.GET, value = "/list?filter={searchTerm}")
SkinListDto getSkins(@PathVariable("searchTerm") String searchTerm);
@RequestMapping(method = RequestMethod.GET, value = "/id/{id}")
SkinDetailsDto getSkinDetails(@PathVariable("id") long id);
}
The @EnableFeignClients
scans the current package (com.github.cc007.headsplugin.config
) by default, which is not the same of the client (com.github.cc007.headsplugin.rest.clients
), you missed the @EnableFeignClients(basePackages = "com.github.cc007.headsplugin")
thx. I had that before, but I removed it when trying to fix the issues with http client. thx for your help.
But now im back to the issue I started with. Something causes the autoconfigure from feign to not be automatically included. The bean is defined in FeignAutoConfiguration. I checked, it is in the spring.factories of openfeign
Is the feign auto configuration located at META-INF/spring.factories
inside the jar file, or somewhere else?
spring factories seems to only contain your autoconfig...
my pom.xml files look like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.cc007</groupId>
<artifactId>headsplugin-parent</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../headsplugin-parent</relativePath>
</parent>
<artifactId>headsplugin-core</artifactId>
<packaging>jar</packaging>
...
<dependencies>
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</dependency>
<dependency>
<groupId>dev.alangomes</groupId>
<artifactId>spigot-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
...
</build>
</project>
With dep management for versions in the parent.
This is the spring.factories from openfeign that doesnt seem to be included into my jar:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
Could it be because the spring-boot-maven-plugin isn't added? Since it is a plugin, the jar doesn't have a main method and therefore that plugin doesn't work properly.
This seems to be caused by the manual build configured. Since the spring-boot-maven-plugin
is not used, the spring.factories
is not merged in the final artifact.
Try using the official plugin:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
And add a empty main method (as required by the spring-boot-maven-plugin
) in your plugin class:
public static void main(String[] args) {
}
I will change the documentation to suggest this way.
Ok, so my hunch was right :D
Ok, so my hunch was right :D
Yes, it is! I recommended a manual configuration to make the integration of spring and spigot less "painful", but I think there's no other way rather than using the official maven plugin.
Yea, it would be a hassle to go through all your dependencies and check if there is any spring.factories file and then add those files to your context.
When using the plugin after having added the main method, the program does build, but the plugin now breaks, because the resources aren't added anymore.
This is because the files were moved to BOOT-INF\classes
I noticed that now, I'm trying to find a workaround...
I think finding a way to move the contents of BOOT-INF/classes to the root of the jar and add the lib file to the root as well. then you can use something similar to https://bukkit.org/threads/tutorial-use-external-library-s-with-your-plugin.103781/ to load the libraries
It would of course be nice if you could make a maven plugin that does the file moving for you and processes and edits the onEnable to automatically load the jars :D
If you get this working, I think it is also not necessary anymore to shade the jar anymore
Also important to note: since the maven jar plugin, maven shade plugin and the spring boot maven plugin all act on the package goal, it matters in which order these are added to the build section in the pom. Something that you could add to your "known issues" section in your wiki.
After some digging, I managed to make the shading works:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
I think finding a way to move the contents of BOOT-INF/classes to the root of the jar and add the lib file to the root as well. then you can use something similar to https://bukkit.org/threads/tutorial-use-external-library-s-with-your-plugin.103781/ to load the libraries
It would of course be nice if you could make a maven plugin that does the file moving for you and processes and edits the onEnable to automatically load the jars :D
I don't think that writing code to dynamic load libraries is the right way, I want this project to be a "out of the box" support for spring, adding so much boilerplate code will not help starting new projects. I hope that this maven configuration will work for all cases.
This does indeed generate the factories file now. In this case, since you're not running a goal in the spring-boot-maven-plugin, that plugin can be removed I think.
Also, with that I get the following error:
java.lang.IllegalStateException: Cannot load configuration class: org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration
at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:413)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:253)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:286)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:130)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:139)
at org.springframework.cloud.bootstrap.BootstrapApplicationListener.bootstrapServiceContext(BootstrapApplicationListener.java:191)
at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:105)
at org.springframework.cloud.bootstrap.BootstrapApplicationListener.onApplicationEvent(BootstrapApplicationListener.java:71)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:75)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:347)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:306)
at com.github.cc007.headsplugin.HeadsPlugin.onEnable(HeadsPlugin.java:32)
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:265)
at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:347)
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:410)
at org.bukkit.craftbukkit.v1_13_R2.CraftServer.enablePlugin(CraftServer.java:436)
at org.bukkit.craftbukkit.v1_13_R2.CraftServer.enablePlugins(CraftServer.java:350)
at net.minecraft.server.v1_13_R2.MinecraftServer.l(MinecraftServer.java:580)
at net.minecraft.server.v1_13_R2.MinecraftServer.a(MinecraftServer.java:542)
at net.minecraft.server.v1_13_R2.MinecraftServer.a(MinecraftServer.java:420)
at net.minecraft.server.v1_13_R2.DedicatedServer.init(DedicatedServer.java:294)
at net.minecraft.server.v1_13_R2.MinecraftServer.run(MinecraftServer.java:698)
at java.lang.Thread.run(Thread.java:744)
Caused by: java.lang.ClassNotFoundException: org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:340)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:275)
at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:438)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:400)
I checked and the file IS actually there in the jar under org/springframework/cloud/bootstrap/BootstrapImportSelectorConfiguration
.
Some starters don't support a custom classloader (due to a bad implementation), a definitive workaround for this is changing the thread class loader (which spring uses by default):
public class TestPlugin extends JavaPlugin {
private ClassLoader defaultClassLoader;
@Override
public void onEnable() {
defaultClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClassLoader());
// init code
}
@Override
public void onDisable() {
// disable code
Thread.currentThread().setContextClassLoader(defaultClassLoader);
}
}
It should work without any problems, since each plugin has it's own class loader.
Ok, now I added that, here is the next issue :D
[04:46:55 INFO]: 2019-06-01 04:46:55.464 WARN 11288 --- [ Server thread] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mineSkinApiTestCommand': Unsatisfied dependency expressed through field 'mineSkinCl
ient'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.github.cc007.headsplugin.rest.clients.MineSkinClient': FactoryBean threw exception on object creation; nested exception is org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'feign.client-org.springfr
amework.cloud.openfeign.FeignClientProperties': Could not bind properties to 'FeignClientProperties' : prefix=feign.client, ignoreInvalidFields=false, ignoreUnknownFields=true; nested exception is org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'feign.client.config' to java.util.Map<java.lang.String, org.springframework.cloud.open
feign.FeignClientProperties$FeignClientConfiguration>
[04:46:55 INFO]: 2019-06-01 04:46:55.474 INFO 11288 --- [ Server thread] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
[04:46:55 INFO]: 2019-06-01 04:46:55.479 ERROR 11288 --- [ Server thread] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to bind properties under 'feign.client.config' to java.util.Map<java.lang.String, org.springframework.cloud.openfeign.FeignClientProperties$FeignClientConfiguration>:
Reason: No converter found capable of converting from type [org.bukkit.configuration.MemorySection] to type [java.util.Map<java.lang.String, org.springframework.cloud.openfeign.FeignClientProperties$FeignClientConfiguration>]
Action:
Update your application's configuration
Can you provide your config.yml?
I found a mapper implementation: convertMapsToSections
in YamlConfiguration:72
# plugin specific config
...
# feign config
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
I added a converter from ConfigurationSection
to Map
by default on 0.18.1, will be available ASAP (https://github.com/Alan-Gomes/mcspring-boot/commit/41b5b7321fdb1782e46055c20bacdf0ac4ce5654#diff-e99133dfd499da62a0f4c9dc230e9eb4R33)
Thanks!
This seems to be a separate issue regarding config, since without this part in the config, the server starts allright and Feign is working like a charm. I'll add a new issue and consider this one closed.
Oh, you already fixed it. No need for that issue then. I have another one for you though :D
Hello!
Sorry for bringing back to life this issue, but something wrong has been mentioned in this comment which can be misleading in fact:
Some starters don't support a custom classloader (due to a bad implementation), a definitive workaround for this is changing the thread class loader (which spring uses by default):
public class TestPlugin extends JavaPlugin { private ClassLoader defaultClassLoader; @Override public void onEnable() { defaultClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(getClassLoader()); // init code } @Override public void onDisable() { // disable code Thread.currentThread().setContextClassLoader(defaultClassLoader); } }
It should work without any problems, since each plugin has it's own class loader.
Modifying the context class loader is definitively not the perfect way to go forward with Bukkit. It's right that it seems to be the default first place where the Spring framework take a look at when searching a class loader before loading and resolving any bean. It's mentioned in the [ClassUtils#getDefaultClassLoader()
](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ClassUtils.html#getDefaultClassLoader()) method which is typically used by the [DefaultResourceLoader
class](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/DefaultResourceLoader.html#getClassLoader()) (when looking at the implementation):
/**
* Return the ClassLoader to load class path resources with.
* <p>Will get passed to ClassPathResource's constructor for all
* ClassPathResource objects created by this resource loader.
* @see ClassPathResource
*/
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
But there is certainly a cleaner way to customize the bean class loader by customizing the ApplicationContext
at initialization time before starting to process any bean:
@Override
public void onEnable() {
applicationContext =
new SpringApplicationBuilder(MyApplication.class)
.bannerMode(Mode.OFF)
.initializers(appContext -> appContext.setClassLoader(getClassLoader()))
.build()
.run();
}
Still, I'm not able to provide guarrantee that if it will work for all the Spring Boot starters (e.g. Feign here) since the default behavior can be overridden. You mentioned it typically: Some starters don't support a custom classloader (due to a bad implementation
. I would be curious to learn more about your experience on this front.
But anyway, one thing which is wrong is saying that the context class loader is dedicated to a specific plugin. The thread is shared between all the plugins which implies that it's the same for the context class loader. I have made some experimentations on my side and here are my findings:
[21:53:53] [Server thread/INFO]: [TemplateSpringPlugin] [STDOUT] Context classloader: java.net.URLClassLoader@759ebb3d [21:53:53] [Server thread/INFO]: [TemplateSpringPlugin] [STDOUT] System classloader: jdk.internal.loader.ClassLoaders$AppClassLoader@2a139a55 [21:53:53] [Server thread/INFO]: [TemplateSpringPlugin] [STDOUT] Plugin's classloader: PaperPluginClassLoader{libraryLoader=java.net.URLClassLoader@5fe36d7e, seenIllegalAccess=[], loadedJavaPlugin=TemplateSpringPlugin v0.1.0-SNAPSHOT}
As you can see, the PaperMC class loader dedicated to the plugin is not the same than the context class loader one. So, it's better to keet context class loader untouched in order to avoid potential conflicts with other plugins (especially when they are relying on Spring Boot while trying to alter all together the context class loader).
But anyway thanks a lot for this message since it gave me an hint for the solution to implement in order to make Spring Boot working with Bukkit! :)
I've moved to using Dagger2 about 2 years ago, but I hope your comments will help anyone else with similar issues.
When using the feign client and properly adding the annotations (
@EnableFeignClients(basePackages = "com.github.cc007.headsplugin")
on the application class and@FeignClient(name="..." , url="...")
on the client interfaces)I get the following exception:
I have added the spring cloud dependency management and openfeign dependency (using the Greenwich.RELEASE version). All fixes that google suggests for the missing FeignContext bean didn't work, so I feel like it could be something in mcspring-boot or spigot that prevents it from working.