Filter specific packages in #ComponentScan - spring

I want to switch from XML based to Java based configuration in Spring. Now we have something like this in our application context:
<context:component-scan base-package="foo.bar">
<context:exclude-filter type="annotation" expression="o.s.s.Service"/>
</context:component-scan>
<context:component-scan base-package="foo.baz" />
But if I write something like this...
#ComponentScan(
basePackages = {"foo.bar", "foo.baz"},
excludeFilters = #ComponentScan.Filter(
value= Service.class,
type = FilterType.ANNOTATION
)
)
... it will exclude services from both packages. I have the strong feeling I'm overlooking something embarrassingly trivial, but I couldn't find a solution to limit the scope of the filter to foo.bar.

You simply need to create two Config classes, for the two #ComponentScan annotations that you require.
So for example you would have one Config class for your foo.bar package:
#Configuration
#ComponentScan(basePackages = {"foo.bar"},
excludeFilters = #ComponentScan.Filter(value = Service.class, type = FilterType.ANNOTATION)
)
public class FooBarConfig {
}
and then a 2nd Config class for your foo.baz package:
#Configuration
#ComponentScan(basePackages = {"foo.baz"})
public class FooBazConfig {
}
then when instantiating the Spring context you would do the following:
new AnnotationConfigApplicationContext(FooBarConfig.class, FooBazConfig.class);
An alternative is that you can use the #org.springframework.context.annotation.Import annotation on the first Config class to import the 2nd Config class. So for example you could change FooBarConfig to be:
#Configuration
#ComponentScan(basePackages = {"foo.bar"},
excludeFilters = #ComponentScan.Filter(value = Service.class, type = FilterType.ANNOTATION)
)
#Import(FooBazConfig.class)
public class FooBarConfig {
}
Then you would simply start your context with:
new AnnotationConfigApplicationContext(FooBarConfig.class)

Related

Spring Kotlin #ConfigurationProperties for data class defined in dependency

I've got a library that has a configuration class (no spring configuration class) defined as a data class. I want a Bean of that configuration which can be configured via application.properties. The problem is that I don't know how to tell Spring to create ConfigurationProperties according to that external data class. I am not the author of the configuration class so I can't annotate the class itself. #ConfigurationProperties in conjunction with #Bean does not work as the properties are immutable. Is this even possible?
Maybe change scan packages to inlcude the packages that do you want.
#SpringBootApplication( scanBasePackages = )
take a look this:
Configuration using annotation #SpringBootApplication
If I understand correctly, do you need a way to turn a third-party object into a bean with properties from your application.properties file ?
Given an application.properties file:
third-party-config.params.simpleParam=foo
third-party-config.params.nested.nestedOne=bar1
third-party-config.params.nested.nestedTwo=bar2
Create a class to receive your params from properties file
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
#Configuration
#ConfigurationProperties(prefix = "third-party-config")
data class ThirdPartConfig(val params: Map<String, Any>)
Here is an example of the object that you want to use
class ThirdPartyObject(private val simpleParam: String, private val nested: Map<String, String>) {
fun printParams() =
"This is the simple param: $simpleParam and the others nested ${nested["nestedOne"]} and ${nested["nestedTwo"]}"
}
Create the configuration class with a method that turns your third-party object into an injectable bean.
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
class ThirdPartObjectConfig(private val thirdPartConfig: ThirdPartConfig) {
#Bean
fun thirdPartyObject(): ThirdPartyObject {
return ThirdPartObject(
simpleParam = thirdPartConfig.params["simpleParam"].toString(),
nested = getMapFromAny(
thirdPartConfig.params["nested"]
?: throw IllegalStateException("'nested' parameter must be declared in the app propertie file")
)
)
}
private fun getMapFromAny(unknownType: Any): Map<String, String> {
val asMap = unknownType as Map<*, *>
return mapOf(
"nestedOne" to asMap["nestedOne"].toString(),
"nestedTwo" to asMap["nestedTwo"].toString()
)
}
}
So now you can inject your third-party object as a bean with custom configurated params from your application.properties files
#SpringBootApplication
class StackoverflowAnswerApplication(private val thirdPartObject: ThirdPartObject): CommandLineRunner {
override fun run(vararg args: String?) {
println("Running --> ${thirdPartObject.printParams()}")
}
}

#ComponentScan takes no effect

I have the following code:
Implementation of Bean:
package my.persist.services;
#Component
public class MyService{
}
Test:
package my.persist.services;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={"other configuration classes", my.persist.services.MyService.class})
#ComponentScan(basePackageClasses = {my.persist.services.DummyPlaceHolder.class})
public class MyServiceTest extends AbstractJUnit4SpringContextTests {
#Autowired
MyService service;
}
When I remove "my.persist.services.MyService.class" from #ContextConfiguration, the compiler says "Could not autowire, no bean of ... found", it seems the #ComponentScan has no effect? Any help?
Rather than component scan for individual classes, does a wildcard scan of your base package work?
#ComponentScan(basePackages = {"my.persist.services.*"})
You can exclude certain ones in your test, i.e if you want to filter out your real implementation in your test, you can do the following:
#ComponentScan(basePackages = {"my.persist.services.*"}, excludeFilters={
#ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=MyService.class)})

Spring Boot Java Config - split web (controllers, etc), and services

In my old XML config projects, I could do the following in my configurations
mvc-context.xml
<context:component-scan base-package="com.foo" use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<mvc:annotation-driven/>
service-context.xml
<context:spring-configured />
<context:annotation-config />
<context:component-scan base-package="com.foo" >
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
in my tests, I could then do the following
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy(value = {
#ContextConfiguration(classes = { MockServices.class }),
#ContextConfiguration({ "classpath:/META-INF/spring/mvc-servlet-context.xml" }),
})
public class FooControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mvc;
#Before
public void setUp() throws Exception {
mvc = webAppContextSetup(wac).build();
}
}
and I could then run tests against the my MVC configuration, without loading my services and JPA respositories, and instead have my mocks #Autowired into my controllers.
However, Spring Boot applications have the following in the main context configuration
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
}
This #ComponentScan obviously finds all #Controller, #Service, etc
if I now tried to test my MVC context, I would load the unwanted services and repositories.
What I attempted to do was create 2 new configurations
Mvc.java
#Configuration
#ComponentScan(basePackages = { "com.foo" }, useDefaultFilters = false, includeFilters = {#Filter(value = org.springframework.stereotype.Controller.class)} )
#Order(2)
public class Mvc {
}
Services.java
#Configuration
#ComponentScan(basePackages = { "com.foo" }, useDefaultFilters = false, excludeFilters = {#Filter(value = org.springframework.stereotype.Controller.class)} )
#Order(1)
public class Services {
}
This however does not work, when I try start my app, I will get #Autowire errors No qualifying bean of type
Am I going about this the wrong way?
How do I make it so that I can run tests on my MVC context, without the time penalty of loading JPA EntityManagers, Spring Data Repositories, etc?
The solution M. Deinum gave in the comments is correct but may be you didn't get the hint.
when you say:
useDefaultFilters = false and excludeFilters = {#Filter(value = org.springframework.stereotype.Controller.class)}
nothing will be found because useDefaultFilters = false will prevent spring from looking for stereotype annotations like #Controller, #Service ...
Link to Spring API doc

ComponentScan.basePackageClasses vs ComponentScan.basePackages to register a single Spring webMVC Controller?

I want to add a single specific controller class to my Spring WebApplicationContext. I ran across the following example: (its in Scala, but is adapted from here: using ComponentScan or context:component-scan with only one class)
#Configuration
#ComponentScan(
basePackages = Array("com.example.controllers"),
useDefaultFilters = false,
includeFilters = Array(
new ComponentScan.Filter(`type` = FilterType.ASSIGNABLE_TYPE,
value = Array(classOf[com.example.controllers.MyController]))))
class MyConfig {
}
This works nicely (but is very verbose).
But Spring's #ComponentScan also has basePackageClasses
#Configuration
#ComponentScan( basePackageClasses=Array(classOf[com.example.controllers.MyController]))
class MyConfig {
}
Of basePackageClasses, Spring's docs says:
Type-safe alternative to basePackages() for specifying the packages to
scan for annotated components.
However, while the first ComponentScan correctly adds only com.example.controllers.MyController, but the second cause all of my #Controller to be scanned and added! Why? What's the use of basePackageClasses?
Example like: https://github.com/mikaelhg/springmvc-example/blob/master/src/main/java/mikaelhg/example/ExampleConfiguration.java
suggest that basePackageClasses can be used to load a single component.
Update:
As an aside, replacing:
#Configuration
#ComponentScan(
basePackages = Array("com.example.controllers"),
useDefaultFilters = false,
includeFilters = Array(
new ComponentScan.Filter(`type` = FilterType.ASSIGNABLE_TYPE,
value = Array(classOf[com.example.controllers.MyController]))))
class MyConfig {
}
with
#Configuration
class MyConfig {
#Bean
var myController = new com.example.controllers.MyController
}
it seems that MyController never gets gets connected to the servlet (the behavior changed to 404-NotFound) -- when added to a WebApplicationContext.
The full javadoc for that attribute reads
Type-safe alternative to basePackages() for specifying the packages to
scan for annotated components. The package of each class specified
will be scanned.
Consider creating a special no-op marker class or interface in each
package that serves no purpose other than being referenced by this
attribute.
By type-safe, you can't make any mistakes with the String value of the name of the package. If you specify an incorrect class, it will fail at compile time.
When you specify basePackageClasses, Spring will scan the package (and subpackages) of the classes you specify. This is a nice trick with no-op classes/interfaces like Controllers, Services, etc. Put all your controllers in one package containing the Controllers class and specify your Controllers class in the basePackageClasses. Spring will pick them all up.
You still need to specify the filters.

Exclude subpackages from Spring autowiring?

Is there a simple way to exclude a package / sub-package from autowiring in Spring 3.1?
E.g., if I wanted to include a component scan with a base package of com.example is there a simple way to exclude com.example.ignore?
(Why? I'd like to exclude some components from my integration tests)
I'm not sure you can exclude packages explicitly with an <exclude-filter>, but I bet using a regex filter would effectively get you there:
<context:component-scan base-package="com.example">
<context:exclude-filter type="regex" expression="com\.example\.ignore\..*"/>
</context:component-scan>
To make it annotation-based, you'd annotate each class you wanted excluded for integration tests with something like #com.example.annotation.ExcludedFromITests. Then the component-scan would look like:
<context:component-scan base-package="com.example">
<context:exclude-filter type="annotation" expression="com.example.annotation.ExcludedFromITests"/>
</context:component-scan>
That's clearer because now you've documented in the source code itself that the class is not intended to be included in an application context for integration tests.
I am using #ComponentScan as follows for the same use case. This is the same as BenSchro10's XML answer but this uses annotations. Both use a filter with type=AspectJ
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ImportResource;
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages = { "com.example" },
excludeFilters = #ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "com.example.ignore.*"))
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
For Spring 4 I use the following
(I am posting it as the question is 4 years old and more people use Spring 4 than Spring 3.1):
#Configuration
#ComponentScan(basePackages = "com.example",
excludeFilters = #Filter(type=FilterType.REGEX,pattern="com\\.example\\.ignore\\..*"))
public class RootConfig {
// ...
}
It seems you've done this through XML, but if you were working in new Spring best practice, your config would be in Java, and you could exclude them as so:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "net.example.tool",
excludeFilters = {#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = {JPAConfiguration.class, SecurityConfig.class})
})
This works in Spring 3.0.5. So, I would think it would work in 3.1
<context:component-scan base-package="com.example">
<context:exclude-filter type="aspectj" expression="com.example.dontscanme.*" />
</context:component-scan>
I think you should refactor your packages in more convenient hierarchy, so they are out of the base package.
But if you can't do this, try:
<context:component-scan base-package="com.example">
...
<context:exclude-filter type="regex" expression="com\.example\.ignore.*"/>
</context:component-scan>
Here you could find more examples: Using filters to customize scanning
One thing that seems to work for me is this:
#ComponentScan(basePackageClasses = {SomeTypeInYourPackage.class}, resourcePattern = "*.class")
Or in XML:
<context:component-scan base-package="com.example" resource-pattern="*.class"/>
This overrides the default resourcePattern which is "**/*.class".
This would seem like the most type-safe way to ONLY include your base package since that resourcePattern would always be the same and relative to your base package.
Just an addition to existing answers.
If you want to exclude classes from sub-packages but not from the base package then you can change "com.example.ignore.* to "com.example.ignore.*..*" as follows
Verified this in spring-boot: 2.4.1
Taken code snippet from this answer
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ImportResource;
#SpringBootApplication
#EnableAutoConfiguration
#ComponentScan(basePackages = { "com.example" },
excludeFilters = #ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "com.example.ignore.*..*"))
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
You can also use #SpringBootApplication, which according to Spring documentation does the same functionality as the following three annotations:
#Configuration, #EnableAutoConfiguration #ComponentScan
in one annotation.
#SpringBootApplication(exclude= {Foo.class})
public class MySpringConfiguration {}
You can also include specific package and excludes them like :
Include and exclude (both)
#SpringBootApplication
(
scanBasePackages = {
"com.package1",
"com.package2"
},
exclude = {org.springframework.boot.sample.class}
)
JUST Exclude
#SpringBootApplication(exclude= {com.package1.class})
public class MySpringConfiguration {}

Resources