How to get basePackages of #ComponentScan programatically at runtime? - spring-mvc

The scanBasePackages of #SpringBootApplication configured as follow:
package com.xxx.boot.sample;
#SpringBootApplication(scanBasePackages = { "com.xxx.boot.sample", "com.xxx.boot.service" })
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The requirement is we want to integration Apache Dubbo component annotation scan with Spring Boot by programming at runtime for zero properties configuration, not by annotation.
#Configuration
#ConditionalOnClass({ EnableDubboConfig.class, AbstractConfig.class })
#ConditionalOnProperty(prefix = "dubbo", name = "enabled", havingValue = "true", matchIfMissing = true)
public class DubboAutoConfiguration {
/// Dubbo配置
#Configuration
#EnableDubboConfig
#ConditionalOnProperty(prefix = "dubbo.config", name = "enabled", havingValue = "true", matchIfMissing = true)
#EnableConfigurationProperties(DubboProperties.class)
public static class DubboConfigConfiguration {
}
/// Dubbo注解扫描
#Configuration
#ConditionalOnClass({ Service.class, Reference.class })
#ConditionalOnProperty(prefix = "dubbo.annotation", name = "enabled", havingValue = "true", matchIfMissing = true)
public static class DubboAnnotationConfiguration {
#Bean(name = "serviceAnnotationBeanPostProcessor")
#ConditionalOnMissingBean
public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(BeanFactory beanFactory) {
// 获取 Spring Boot 主入口类所在的包路径
List<String> packagesToScan = AutoConfigurationPackages.get(beanFactory);
if (packagesToScan == null) {
packagesToScan = Collections.emptyList();
}
return new ServiceAnnotationBeanPostProcessor(packagesToScan);
}
#Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
#ConditionalOnMissingBean
public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
return new ReferenceAnnotationBeanPostProcessor();
}
}
}
But AutoConfigurationPackages#get(BeanFactory) only return "com.xxx.boot.sample", not include "com.xxx.boot.service". I hope return all scanBasePackages value.
By debug, I found the #SpringBootApplication instance is a proxy class instance. I try to get Annotation use Class#getAnnotations, then get scanBasePackages field by reflection. But not success.
Question:
How to get scanBasePackages of #SpringBootApplication or basePackages of #ComponentScan programatically?

It isn't really advisable to try to reuse the scanBasePackages attributes for your own purposes. If you look at the source of #SpringBootApplication you'll see the following:
#AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
#AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
This is saying that these attributes are an alias for #ComponentScan. Since this annotation can be used on any #Configuration class it's actually legal to have many of them.
The #ComponentScan annotation triggers scanning by the ConfigurationClassParser. Look at the doProcessConfigurationClass method for all the gory details.
If you really want to find the annotation attributes yourself you can do the following:
applicationContext.getBeansWithAnnotation(ComponentScan.class).forEach((name, instance) -> {
Set<ComponentScan> scans = AnnotatedElementUtils.getMergedRepeatableAnnotations(instance.getClass(), ComponentScan.class);
for (ComponentScan scan : scans) {
System.out.println(Arrays.toString(scan.basePackageClasses()));
System.out.println(Arrays.toString(scan.basePackages()));
}
});
This will just get you those two values. You're still not considering any #Condition annotations or any include/exclude filters. You also won't deal with #ComponentScan() which means scan from the current package down.
What Spring Boot tends to do in these circumstances is define a new annotation for a specific purpose. For example, you can use #EntityScan to define where JPA entities are found. We then use AutoConfigurationPackages as the default value if you don't specify any override.

Related

How to Implement beanprocessor to examine the attribute of meta listener?

My meta listener code
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#KafkaListener(containerFactory = "myListenerContainerFactory", autoStartup = "false")
public #interface mylistener1 {
#AliasFor(annotation = KafkaListener.class, attribute = "topics")
String[] topics();
String myattr() default "";
}
Consume method:
#Service
Class ConsumerService(){
#mylistener1(topics = "new.topic",myattr="new.myatr.topic")
public void consume(String message) {
LOG.info("consumer-> " + message);
}
}
I tried to get value from ApplicationContext, but it was not getting the listener.
#Autowired
ApplicationContext ctx;
Map<String, Object> allBeansWithNames = ctx.getBeansWithAnnotation(mylistener1.class);
allBeansWithNames - 0, and I am not getting class list which is having #mylistener1 annotation`your text`
I want to implement beanpostprocessor to check myattr at runtime and use it to send message`
getBeansWithAnnotation() will only find beans with classes that are so annotated (or #Bean factory methods). It doesn't look at the class methods.
Take a look at KafkaListenerAnnotationBeanPostProcessor for an example of a BPP that examines annotations and their attributes.

How can i extend spring-kafka's '#KafkaListener' annotation to create my own annotation with limited attributes?

I've been using '#KafkaListener' at method level to create consumers. Now, I'm trying to create my own custom annotation by extending '#KafkaListener' and limit the no of attributes (for example, because of some reasons, I don't want to expose attributes like 'errorHandler' 'containerGroup' etc ). Now my question is, to implement this, is there any option to extend the existing '#KafkaListener' ? Please suggest.
Yes, it's quite easy.
#SpringBootApplication
public class So61684460Application {
public static void main(String[] args) {
SpringApplication.run(So61684460Application.class, args);
}
#MyListener(topics = "so61684460", id = "so61684460")
public void listen(String in) {
System.out.println(in);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so61684460").partitions(3).replicas(1).build();
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#KafkaListener(concurrency = "${my.concurrency}")
#interface MyListener {
#AliasFor(annotation = KafkaListener.class, attribute = "id")
String id();
#AliasFor(annotation = KafkaListener.class, attribute = "topics")
String[] topics() default "";
}
As you can see, as well as restricting the visibility of some attributes, you can make them required (id above), change the default value, or set hard-coded or parameterized values in the invisible attributes (concurrency above).
This is described in the documentation.

ConfigurationManager.AppSettings.Get(key).ConvertTo error in asp.net core

Below is my code. I try to convert below code from asp.net to asp.net core. But in asp.net core in last line ConvertTo is showing error because Get(key) does not have definition of ConvertTo. Don't know what is the problem.
I am unable to find any solution how can i write below code in asp.net core?
public static T Get<T>(string key)
{
if (!Exists(key))
{
throw new ArgumentException(String.Format("No such key in the AppSettings: '{0}'", key));
}
return ConfigurationManager.AppSettings.Get(key).ConvertTo<T>(new CultureInfo("en-US"));
}
Thanks in advance.
I suggest you carefully reading the documentation first. In .NET Core the way how we work with configuration is changed significantly ( different source using, mapping to POCO objects , etc).
In your case you may simply use ConfigurationBinder’s GetValue<T> extension method instead of implementing own method for value conversion:
IConfiguration.GetValue - extracts the value with the specified key
and converts it to type T.
Configuration in .net Core is now built on top of POCO's or IOptions for the most part. You don't get individual keys but instead you build up settings classes. Previously you had to either build a CustomConfiguration class or you would prefix AppSettings to "group them". Not anymore! If you take the approach of using IOptions it works something like the following.
You have your appSettings.json look like the following :
{
"myConfiguration": {
"myProperty": true
}
}
You then make up a POCO that matches your configuration. Something like this :
public class MyConfiguration
{
public bool MyProperty { get; set; }
}
Then in your startup.cs you need to load your configuration into an options object. It will end up looking pretty similar to the following.
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfiguration>(Configuration.GetSection("myConfiguration"));
}
}
Then the DI is all set up to inject an IOptions objects. You would then inject it into a controller like so :
public class ValuesController : Controller
{
private readonly MyConfiguration _myConfiguration;
public ValuesController(IOptions<MyConfiguration> myConfiguration)
{
_myConfiguration = myConfiguration.Value;
}
}
There is other ways to do this that don't use the IOptions object and you only inject in the POCO to your controllers. Some people (including me) prefer this method. You can read more here : http://dotnetcoretutorials.com/2016/12/26/custom-configuration-sections-asp-net-core/
And of course the documentation link for the official docs are here : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration

Urls not detected that ends with .ico in Spring Boot

I am using this annotation within a Controller's method in one Spring Boot app.
#RequestMapping(value="/{x}/{y}/{filename:.*}", method = RequestMethod.GET)
All is working good and the last parameter can be any filename.
The problem is with urls where that filename ends with ".ico"...Spring is not sending the request to this method...my guess it is that it thinks a favicon itself.
How can I avoid this kind of conflict?
Thanks.
Have a look at Spring MVC #PathVariable with dot (.) is getting truncated, especially one of the latest answers regarding Spring 4.x
I found the solution. I just need to disable this setting inside the application.properties file
spring.mvc.favicon.enabled=false
This way the FaviconConfiguration bean from WebMvcAutoConfiguration does not satisfies the constraint, thus is not created:
#Configuration
#ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
#Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Integer.MIN_VALUE + 1);
/**THIS WAS THE CONFLICTIVE MAPPING IN MY CASE**/
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
return mapping;
}
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler.setLocations(getLocations());
return requestHandler;
}
private List<Resource> getLocations() {
List<Resource> locations = new ArrayList<Resource>(CLASSPATH_RESOURCE_LOCATIONS.length + 1);
for (String location : CLASSPATH_RESOURCE_LOCATIONS) {
locations.add(this.resourceLoader.getResource(location));
}
locations.add(new ClassPathResource("/"));
return Collections.unmodifiableList(locations);
}
}
Source: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java

Adobe CQ5 custom servlet path

I am trying to add some functionality to json processing for some nodes. So I wrote custom servlet extended from SlingSafeMethodsServlet which I need to be executed when user makes GET for the following url : /data/events/any_sequence/any_sequence.json or /data/events/any_sequence/any_sequence.infinity.json or for example /data/events/any_sequence/any_sequence.2.json where any_sequence of course means any valid sequence of symbols.
The problem is that I cannot find in the sling docs how to map this template like urls.
I've been trying to set properties like this:
#Component
#Service
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "data/events/-/-"),
#Property(name = "sling.servlet.extensions", value = "json"),
#Property(name = "sling.servlet.methods", value = "GET"),
#Property(name = "service.description", value = "JSON advanced renderer")
})
But it didn't help. I checked felix console and found out that my service had started and running, so the problem is how to set url mappings. So my question is how to set url mapping in my case to invoke doGet of my custom servlet ?
Thanks.
As far as I understand CQ5 does not provide ability to map custom servlets on wildcard urls. The only way to accomplish goal similiar to one I needed is to use some unique for this servlet selector like this:
#Component
#Service
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "sling/servlet/default"),
#Property(name = "sling.servlet.extensions", value = "json"),
#Property(name = "sling.servlet.selectors", value = "advanced"),
#Property(name = "sling.servlet.methods", value = "GET"),
#Property(name = "service.description", value = "JSON advanced renderer")
})
This code means that if I'll try to make GET on some node with *.advanced.json selector and extension then request will be forwarded to my custom servlet.
See http://apache-sling.73963.n3.nabble.com/Register-servlet-for-subtree-td84106.html
I've solved this, exactly as the original poster was hoping. All other answers are effectively "it can't be done" or "here's a way to do it if you're willing to dirty up your clean RESTful API with selectors"
If you want to keep the clean API you envisioned, here's how. This works also for Apis without extensions, like /myservice/mythings/123123 , where 123123 is some dynamic ID
Create Two Files:
ResourceProvider
Servlet
The ResourceProvider
The purpose of this is only to listen to all requests at /data/events and then produce a "Resource" at that virtual path, which doesn't actually exist in the JCR.
#Component
#Service(value=ResourceProvider.class)
#Properties({
#Property(name = ResourceProvider.ROOTS, value = "data/events"),
#Property(name = ResourceProvider.OWNS_ROOTS, value = "true")
})
public class ImageResourceProvider implements ResourceProvider {
#Override
public Resource getResource(ResourceResolver resourceResolver, String path) {
AbstractResource abstractResource;
abstractResource = new AbstractResource() {
#Override
public String getResourceType() {
return TypeServlet.RESOURCE_TYPE;
}
#Override
public String getResourceSuperType() {
return null;
}
#Override
public String getPath() {
return path;
}
#Override
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
#Override
public ResourceMetadata getResourceMetadata() {
return new ResourceMetadata();
}
};
return abstractResource;
}
#Override
public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest httpServletRequest, String path) {
return getResource(resourceResolver , path);
}
#Override
public Iterator<Resource> listChildren(Resource resource) {
return null;
}
}
The Servlet
Now you just write a servlet which handles any of the resources coming from that path - but this is accomplished by handling any resources with the resource type which is produced by the ResourceProvider listening at that path.
#SlingServlet(
resourceTypes = TypeServlet.RESOURCE_TYPE,
methods = {"GET" , "POST"})
public class TypeServlet extends SlingAllMethodsServlet {
static final String RESOURCE_TYPE = "mycompany/components/service/myservice";
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
final String [] pathParts = request.getResource().getPath().split("/");
final String id = pathParts[pathParts.length-1];
response.setContentType("text/html");
PrintWriter out = response.getWriter();
try {
out.print("<html><body>Hello, received this id: " + id + "</body></html>");
} finally {
out.close();
}
}
}
This is the syntax I have used to accomplish similar tasks:
#Component(immediate = true, description = "JSON advanced renderer")
#Service(value = javax.servlet.Servlet.class)
#Properties(value = {
#Property(name = "sling.servlet.extensions", value = { "json" }),
#Property(name = "sling.servlet.methods", value = { "GET" }),
#Property(name = "sling.servlet.paths", value = {
"/data/events/any_sequence/any_sequence",
"/data/events/any_sequence/any_sequence.infinity",
"/data/events/any_sequence/any_sequence.2"
})
})
I had a similar problem and I needed wildcards I used
#Service
#Component
#Properties({
#Property(name = "sling.servlet.paths", value = "/bin/resolver/gb"),
#Property(name = "sling.servlet.extensions", value = "*")
})
public class Test extends SlingAllMethodsServlet {
#Override
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.print("The path you used is:" + request.getPathInfo());
}
}
Call is [server]:[port]/bin/resolver/gb.[wildcard]
So what can be done is : [server]:[port]/bin/resolver/gb.en/something
Everything after "." is considered an extension so need to be handled in the servlet, but helped me achieve my requirement
It seems that the path of your servlet is same.Just selectors are varying. When used with path, other things are ignored in SlingServlet. So using something like this should serve the purpose:
#SlingServlet(paths = " /data/events/any_sequence/any_sequence", extensions = "json")
You would need to add /data in execution paths from Felix console(/system/console/configMgr) as is it not there by default in Apache Sling Servlet Resolver property
This can be accomplished using the desired external URI pattern by constructing a Sling mapping or Apache rewrite to effectively move the JSON extension to just after "data" in the URI, so the single servlet at "/data" ends up receiving the arbitrary path via the suffix of the request. If you are also using data from selectors, you'll need to move them along with the extension.

Resources