I am consuming a spring boot project as jar inside another spring boot application using the maven dependency. I want to do the component scan of jar only if I enable a custom annotation from microservice.
#SpringBootApplication
//#ComponentScan({"com.jwt.security.*"}) To be removed by custom annotation
#MyCustomAnnotation //If I provide this annotation then the security configuration of the jar should be enabled.
public class MicroserviceApplication1 {
public static void main(String[] args) throws Exception {
SpringApplication.run(MicroserviceApplication1.class, args);
}
}
Please suggest some ideas.
In your library:
#Configuration
#ComponentScan(basePackages = { "com.jwt.security" })
public class MyCustomLibConfig{
}
#Retention(RUNTIME)
#Target(TYPE)
#Import(MyCustomLibConfig.class)
public #interface MyCustomAnnotation{
#AliasFor(annotation = Import.class, attribute = "value")
Class<?>[] value() default { MyCustomLibConfig.class };
}
So, in your application you can use the annotation
#SpringBootApplication
#MyCustomAnnotation //If I provide this annotation then the security configuration
of the jar should be enabled.
public class MicroserviceApplication1 {
public static void main(String[] args) throws Exception {
SpringApplication.run(MicroserviceApplication1.class, args);
}
}
You can use #Conditional to define configurations (see an example describe here). Some code from the source
#Configuration
public class MyConfiguration {
#Bean(name="emailerService")
#Conditional(WindowsCondition.class)
public EmailService windowsEmailerService(){
return new WindowsEmailService();
}
#Bean(name="emailerService")
#Conditional(LinuxCondition.class)
public EmailService linuxEmailerService(){
return new LinuxEmailService();
}
}
and conditional
public class WindowsCondition implements Condition{
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Windows");
}
}
You can use profiles. Just add the #Profile to your config class with scan of desired package.
One more alternative is described here.
#AutoconfigureAfter(B.class)
#ConditionalOnBean(B.class)
public class A123AutoConfiguration { ...}
Related
The HystrixCommandAspect bean is declared in the HystrixCircuitBreakerConfiguration class but I would like to use my own custom implementation of HystrixCommandAspect and inject a different bean.
Application:
#SpringBootApplication
#EnableAspectJAutoProxy
#EnableCircuitBreaker
#Import(HystrixConfiguration.class)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
Configuration:
#Configuration
public class HystrixConfiguration {
#Bean
#Primary
public HystrixCommandAspect hystrixCommandAspect(){
return new com.hystrix.HystrixCommandAspect();
}
}
Custom HystrixCommandAspect:
package com.hystrix;
#Aspect
public class HystrixCommandAspect extends com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect {
...
}
However, when I use the annotation #EnableCircuitBreaker it uses the HystrixCircuitBreakerConfiguration and doesn't even load my own #Bean definition.
I upgraded spring to the latest release and this fixed the problem. I also noticed in the logs that it said it was overriding the bean provided in HystrixCircuitBreakerConfiguration.
I am currently writing a Spring Boot autoconfiguration for Retrofit 2. What I am trying to do is to write some sort of an interface builder that is able instantiate an interface that is annotated with some annotation for autowiring just like Spring Data does it with repositories. As I cannot find any resources on how to do this (or if it can even be done with Spring), I would like to ask for your thoughts on that. Below is for an interface that I would like to instantiate.
My replacement for #Repository is #Retrofit the rest is just "ordinary" code you would write for any Retrofit repository.
The kind of interface I would like to autowire:
#Retrofit
public interface Api {
#GET("usernames")
String[] getUsernames();
}
An example for autowiring:
#SpringBootApplication
public class TestApplication {
#Autowired
private Api api;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
CommandLineRunner runner() {
return args -> {
System.out.println(api.getUsernames());
};
}
}
As I said I found a solution for my problem.
First we need an auto configuration class that is loaded by Spring Boot - as stated here - by adding the file META-INF/spring.factories with the content that is shown below. This auto configuration loads a registrar which itself searches for classes annotated with #Retrofit via a component provider. At last the registrar creates instances of RetrofitFactoryBean for each instance that could be found while this factory bean creates the Retrofit proxies itself.
The auto configuration
#Configuration
#Import(RetrofitRegistrar.class)
public class RetrofitAutoConfiguration {
#Bean
#ConditionalOnMissingBean
public Retrofit retrofit() {
return new Retrofit.Builder().build();
}
}
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
spring.retrofit.RetrofitAutoConfiguration
The imported registrar
public class RetrofitRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
#Setter
private BeanFactory beanFactory;
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
List<String> basePackages = AutoConfigurationPackages.get(this.beanFactory);
RetrofitComponentProvider provider = new RetrofitComponentProvider(registry);
basePackages.stream()
.map(provider::findCandidateComponents)
.flatMap(Set::stream)
.forEach(comp -> register(comp, registry));
}
private void register(BeanDefinition component, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.
rootBeanDefinition(RetrofitFactoryBean.class);
builder.addConstructorArgValue(component.getBeanClassName());
registry.registerBeanDefinition(
component.getBeanClassName().toLowerCase(), builder.getBeanDefinition());
}
}
The component provider
class RetrofitComponentProvider extends ClassPathScanningCandidateComponentProvider {
#Getter
private BeanDefinitionRegistry registry;
public RetrofitComponentProvider(BeanDefinitionRegistry registry) {
super(false);
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
this.registry = registry;
addIncludeFilter(new AnnotationTypeFilter(Retrofit.class, true, true));
}
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return true;
}
}
The factory bean
#Component
#RequiredArgsConstructor
public class RetrofitFactoryBean extends AbstractFactoryBean<Object> {
#Getter
private final Class<?> objectType;
private final retrofit2.Retrofit retrofit;
#Override
protected Object createInstance() throws Exception {
return retrofit.create(objectType);
}
}
The #Getter, #Setter and #RequiredArgsConstructor annotations are provided by ProjectLombok
Let me ask you firstly to avoid reinventing the wheel by creating a new Spring annotation (yours here is #Retrofit. However, it is absolutely okay to use retrofit with spring there is nothing to prevent it. You can simply try to use an existing Spring annotation which can be #Component as you can see in this question
you can autowire your interface without facing problems.
Hope this helps.
I am trying to create a three-tier application with Spring, view, logic, data, more or less. The view depends on logic which depends on data.
How can I configure the Spring application in the view project such that the dependency graph is able to be resolved?
For example:
In the view layer:
#Controller
public class SomeView {
private final SomeService someService;
#Autowired
public SomeView(SomeService someService) {
this.someService = someService;
}
}
#Configuration
#EnableAutoConfiguration
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
In the logic layer:
#Component
public class SomeService {
private final SomeData someData;
#Autowired
public SomeService(SomeData someData){
this.someData = someData;
}
}
In the data layer:
#Component
public class SomeData {
}
This configuration is not able to boot because SomeService can't resolve SomeData because SomeData is not scanned in the view layers Application.java
when using #SpringBootApplication Spring boot uses default values. If you take a look at the #SpringBootApplication definition you will see that :
Many Spring Boot developers always have their main class annotated with #Configuration, #EnableAutoConfiguration and #ComponentScan. Since these annotations are so frequently used together (especially if you follow the best practices above), Spring Boot provides a convenient #SpringBootApplication alternative.
The #SpringBootApplication annotation is equivalent to using #Configuration, #EnableAutoConfiguration and #ComponentScan with their default attributes: [...]
That means :
#SpringBootApplication // same as #Configuration #EnableAutoConfiguration #ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Also, it means that when using default values for #ComponentScan your packages sturctures shloud be as following :
com.example.model -> your entities
com.example.repositoriy -> your repositories
com.example.controller -> controllers
com.example -> MainApplication class
If not following this structure you should tell to the #ComponentScan the package where to find the components :
Example 1:
#Configuration
#EnableAutoConfiguration
#ComponentScan({"com.my.package.controller","com.my.package.domain"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Exemple 2 :
#Configuration
#EnableAutoConfiguration
#ComponentScan(basePackageClasses = {SomeService.class, SomeData.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Also, i advice you to check this guide on how to structuring your code in a Spring Boot Application.
Summary: Adding the #ComponentScan (or #SpringBootApplication) annotation to my application class changes the behaviour of SpringApplicationBuilder.properties() and breaks my integration test.
I am using a cut-down version of the Spring Boot sample:
spring-boot-sample-websocket-jetty
I have removed everything except what is required for the "echo" example (and I'm using Spring Boot 1.3.3).
I am left with the following SampleJettyWebSocketsApplication code:
#Configuration
#EnableAutoConfiguration
//#ComponentScan // --- If I uncomment this the test breaks ---
#EnableWebSocket
public class SampleJettyWebSocketsApplication
implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(), "/echo").withSockJS();
}
#Bean
public EchoService echoService() {
return new DefaultEchoService("Did you say \"%s\"?");
}
#Bean
public WebSocketHandler echoWebSocketHandler() {
return new EchoWebSocketHandler(echoService());
}
public static void main(String[] args) {
SpringApplication.run(SampleJettyWebSocketsApplication.class, args);
}
}
And the following test class (code straight from the Spring Boot samples):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(SampleJettyWebSocketsApplication.class)
#WebIntegrationTest({"server.port=0"})
#DirtiesContext
public class SampleWebSocketsApplicationTests {
private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class);
#Value("${local.server.port}")
private int port = 1234;
#Test
public void echoEndpoint() throws Exception {
logger.info("Running the echoEndpoint test. Port: " + port + ". Path: /echo/websocket");
ConfigurableApplicationContext context = new SpringApplicationBuilder(
ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
.properties("websocket.uri:ws://localhost:" + this.port
+ "/echo/websocket")
.run("--spring.main.web_environment=false");
long count = context.getBean(ClientConfiguration.class).latch.getCount();
AtomicReference<String> messagePayloadReference = context
.getBean(ClientConfiguration.class).messagePayload;
context.close();
assertThat(count).isEqualTo(0);
assertThat(messagePayloadReference.get())
.isEqualTo("Did you say \"Hello world!\"?");
}
#Configuration
static class ClientConfiguration implements CommandLineRunner {
#Value("${websocket.uri}")
private String webSocketUri;
private final CountDownLatch latch = new CountDownLatch(1);
private final AtomicReference<String> messagePayload = new AtomicReference<String>();
#Override
public void run(String... args) throws Exception {
logger.info("Waiting for response: latch=" + this.latch.getCount());
if (this.latch.await(10, TimeUnit.SECONDS)) {
logger.info("Got response: " + this.messagePayload.get());
}
else {
logger.info("Response not received: latch=" + this.latch.getCount());
}
}
#Bean
public WebSocketConnectionManager wsConnectionManager() {
logger.info("Setting up SimpleClientWebSocketHandler...");
WebSocketConnectionManager manager = new WebSocketConnectionManager(client(),
handler(), this.webSocketUri);
manager.setAutoStartup(true);
return manager;
}
#Bean
public StandardWebSocketClient client() {
return new StandardWebSocketClient();
}
#Bean
public SimpleClientWebSocketHandler handler() {
logger.info("Creating new SimpleClientWebSocketHandler using SimpleGreetingService...");
return new SimpleClientWebSocketHandler(greetingService(), this.latch,
this.messagePayload);
}
#Bean
public GreetingService greetingService() {
return new SimpleGreetingService();
}
}
}
Running the Application and the unit test as above all is fine but if I uncomment the #ComponentScan annotation on the application class the application still runs OK but the test breaks with the error:
Could not resolve placeholder 'websocket.uri' in string value "${websocket.uri}".
I have read at setting-the-run-time-properties-on-springapplicationbuilder that:
The properties you configure on SpringApplicationBuilder are made available in your application's Environment, not as system properties.
And in the #ComponentScan javadoc that:
If specific packages are not defined, scanning will occur from the package of the class that declares this annotation.
But I don't understand why the behaviour changes when the #ComponentScan annotation is added.
How can I set the System Property websocket.uri in the test when the application class is annotated with #ComponentScan (or #SpringBootApplication)?
(I aim to use #SpringBootApplication, which incorporates #ComponentScan, but I can't until I get this working.)
There are several ways to add a system properties.
Solution 1:
Add arguments for Test in format of -Dabc=xyz, that will add property abc to system properties.
Solution 2:
Just like floor 0.
Solution 3:
Just let spring-boot load the properties, such as classpath:bootstrap.yml, and you can specify whatever properties in there.
The annotation #ComponentScan will enable auto scanning based on current package or ComponentScan#basePackages. Which means SampleWebSocketsApplicationTests.ClientConfiguration will be scanned cause they have same base package samples.websocket.jetty.
However, SampleWebSocketsApplicationTests.ClientConfiguration should not be parsed by SpringJUnit4ClassRunner cause we need parse it in SampleWebSocketsApplicationTests#echoEndpoint manually. It's should only be parsed by ApplicationContext created in echoEndpoint().
What's more, #SpringBootApplication equals to use #Configuration and #EnableAutoConfiguration and #ComponentScan together, so comment out #ComponentScan or #SpringBootApplication will have same effect.
My suggestion is move class SampleWebSocketsApplicationTests into package samples.websocket.jettytest(different from samples.websocket.jetty) and enable #ComponentScan or #SpringBootApplication on SampleJettyWebSocketsApplication and try again. It should work.
Adding my thoughts on this (from whatever i could gather from your code):
-Try adding the property websocket.uri in you application properties or if your project contains src/test/resources/test.properties then add it into your test.properties file.#ComponentScan should pick it up.
-Else,you could just say :
public static void main(String[] args) {
System.setProperty("websocket.uri","<your uri>");
SpringApplication.run(SampleJettyWebSocketsApplication.class, args);
}
Hope it helps.
Specifically, I would like to be able share configuration classes by instantiating them and including them. Where you would normally do this:
#Configuration
#Import({SharedConfiguration.class})
public class MyAppContext extends WebMvcConfigurerAdapter {
//stuff
}
#Configuration
#ComponentScan("com.example")
public class SharedConfiguration {
//stuff
}
I would like to do this:
#Configuration
public class MyAppContext extends WebMvcConfigurerAdapter {
public SharedConfiguration sharedConfig(){
return new SharedConfiguration("com.example");
}
//stuff
}
#Configuration
public class SharedConfiguration {
public SharedConfiguration(String package){
//tell Spring to scan package
}
}
The reason for this is that I need to be able to tell the shared component doing the scan what package to look at. It will different depending on what project it is being used in.
EDIT:
To provide some additional context, I'm trying to make a general-use configuration for setting up Hibernate and EHCache using our external configuration provider that several projects can use. I'm certainly open to other methods to doing this, but this seemed like the most logical path to me. I'm sure there's ~something~ in Spring that I can fiddle with to say, "Here! Scan this path when Spring initializes you!" instead of hard-coding it into an annotation.
You can take advantage of property sources in this case.
In the test case, I am setting a system property that is picked up by the Spring property source configuration -
#RunWith(SpringRunner.class)
#ContextConfiguration
public class MyAppContextTest {
#Autowired
ApplicationContext context;
#BeforeClass
public static void beforeClass() {
// use a system property to configure the component scan location of the SharedConfiguration
// where the "ExampleBean" lives
System.setProperty("packages", "net.savantly.other.packages");
}
#Test
public void ensureExampleBeanExists() {
// throws exception if it doesnt exist
context.getBean(ExampleBean.class);
}
#Configuration
#Import(MyAppContext.class)
static class TestContext {
}
}
Using the Spring expression language in the ComponentScan -
#Configuration
#ComponentScan("${packages}")
public class SharedConfiguration {}
Other Referenced Classes -
#Configuration
#Import(SharedConfiguration.class)
public class MyAppContext extends WebMvcConfigurerAdapter {
#Autowired
SharedConfiguration sharedConfig;
//stuff
}
#Service
public class ExampleBean {
}