dynamic component scanning in grails - java

I have a toy project on grails, where is the main idea is to load user components at runtime into application.
By my idea, user writes his own beans (controllers and services) right in my web application as a DSL bean set and, presses the button, and beans from that file are load int the application context. At the moment I can load and wire these beans with AC, but grails does not treat them as controllers. I suppose there are two strategies to find controllers in the grails application: grails scans beans that are located in some "magic" folder, wires them and supposes that all the methods of this bean are requestmappings. The second way to find controllers is to annotate them with #Controller and #RequestMapping. And that is what I don't know how to do.
When I annotate these DSL beans with #Controller and wire them, they do appear at the controller list, claculated this way
package ealo;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
public class ControllerLister {
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
//#PostConstruct
public void init1() {
Map<RequestMappingInfo, HandlerMethod> handlerMethods
= this.requestMappingHandlerMapping.getHandlerMethods();
//System.out.println("111 "+handlerMethods);
for (Entry<RequestMappingInfo, HandlerMethod> item : handlerMethods.entrySet()) {
RequestMappingInfo mapping = item.getKey();
HandlerMethod method = item.getValue();
// System.out.println("111");
for (String urlPattern : mapping.getPatternsCondition().getPatterns()) {
System.out.println(
method.getBeanType().getName() + "#" + method.getMethod().getName()
+ " <-- " + urlPattern);
if (urlPattern.equals("some specific url")) {
//add to list of matching METHODS
}
}
}
}
}
But it happens only when I change something in the project at runtime. I suppose grails rescans components at that moment. Anyway, even when I see these beans listed, I cannot make any request to them, getting 404. If a do not annotate the beans, I cannot even see them listed.
I need an idea how to solve this problem grails way or annotation way. Grails way is in preference. Thanks in advance.

Related

Debugging SpringBoot MVC service application 404 error

A web application I've been working on recently the past like 2 weeks maybe for whatever reason when I finally tested it - won't seem to even enter the method that I have to return a JSON list of objects. I have included the Jackson library and Spring Boot Web, Tomcat, Data-JPA, Hibernate, MySQL, and a library to allow me to access JSP files. The index.jsp comes up but I almost feel like Spring Boot is giving me that free of charge as it's not even entering that method. I have been having the issue for a few days but trying to resolve it on my own - I found another answer that suggested to put a breakpoint inside one of the Spring classes but when I "debugged" it through Eclipse, it didn't even stop at that class - something about pattern matching - One answer suggested adding a context to the application.properties file - didn't help. I've reduced it to as simple as I think I can get it. Can anyone tell me what I might be doing wrong? Before my code, the project is on Github at: https://github.com/sfulmer/Scheduler.git
Here's my controller:
package net.draconia.schedule.controllers;
import java.util.List;
import net.draconia.schedule.beans.Event;
import net.draconia.schedule.dao.EventDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class ScheduleController
{
private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class);
#Autowired
private EventDAO mObjDAO;
protected EventDAO getDAO()
{
return(mObjDAO);
}
//#GetMapping("/events")
#RequestMapping(value = "events", method = RequestMethod.GET)
public #ResponseBody List<Event> getEvents()
{
logger.debug("I got here");
return(getDAO().getList());
}
#GetMapping("/")
public String index()
{
return("index");
}
}
Here is the DAO interface - I'll show the class if necessary but this is what the controller looks at:
package net.draconia.schedule.dao;
import java.util.List;
import javax.persistence.EntityNotFoundException;
import net.draconia.schedule.beans.Event;
public interface EventDAO
{
public Event getEventById(final long lId) throws EntityNotFoundException;
public List<Event> getList();
public void remove(final Event objEvent);
public void removeById(final long lId);
public Event save(final Event objEvent);
}
The Event class is so long but if I need to include it, I will. The application.properties file is here:
spring.datasource.url = jdbc:mysql://localhost:3306/schedule
spring.datasource.username = root
spring.datasource.password = R3g1n# M1lL$ 1$ My Qu3eN!
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
server.servlet.contextPath=/scheduler
and here is my Application class(with the SpringBootApplication annotation):
package net.draconia.schedule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication(scanBasePackages = {"net.draconia.schedule.controller"})
public class ScheduleApp implements WebMvcConfigurer
{
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
return(builder.sources(ScheduleApp.class));
}
public static void main(String[] args)
{
SpringApplication.run(ScheduleApp.class, args);
}
}
I'm relatively new to Spring Boot but haven't ever ran into this problem ever before as I work with it at work and it works fine but we use entirely REST services there and I am using JSP files as well as sorta end-points that respond with JSON but you can't respond from REST services with JSP views so unfortunately I can't copy work's project to get that working or I would sigh Any thoughts on how I can get this working or what I am omitting?
My guess is that you're mixing things from Spring and Spring boot, and that's getting problems on loading beans, as you're probably changing the annotations load order or loading other beans rather than spring boot defaults as expected. For example, you implements WebMvcConfigurer, but you aren't providing any WebMvc Configuration, like a ViewResolver bean
My advice is to follow this guide: https://spring.io/guides/gs/spring-boot/
and use only the annotations from spring boot if using spring boot, or spring if using spring (they're similar, but not exactly the same, configuration is different).
Anyways, you can check loaded beans in Spring application context (Inject it in Application class) with ctx.getBeanDefinitionNames() method and see if your controller is there (i guess not).
By looking into code, my first impression is that, you have some typo in here:
#SpringBootApplication(scanBasePackages = {"net.draconia.schedule.controller"})
Your controller class package name has net.draconia.schedule.controllers.
So can you please correct your scanBasePackages with proper package name.
If that is not the case, please update full stack trace along with GET request which you are submitting into application. Will take a look & update answer accordingly.

Why is #Repository annotation being proxied , but not #Component

I am trying to understand which types of beans in Spring are being proxied, so far i understood its the #Aspect , #Transactional annotated classes. So I played a bit on my PC, and noted that if I annotate a class with #Component, it's not getting proxied when Autowired, but if I just change the annotation to #Repository, it is proxied by CGLIB. As far as i understand, #Repository is just an alias of #Component, why is it getting proxied and the other one not ?
Here are the example very simple classes
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
#Repository
public class JustForTest {
private final String hello = "";
public String getHello() {
return hello;
}
}
and
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
#Component
public class JustForTest {
private final String hello = "";
public String getHello() {
return hello;
}
}
Why does Spring need to proxy bean classes? The basic high level answer is: because it needs to add additional logic to the class at runtime so you don't have to write it yourself (or configure things in lengthy XML descriptor files...). Just like the #Transactional annotation adds magic transaction management to bean methods, the Repository annotation must also have something attached to it; you know this for a fact already because the bean gets proxied as soon as you add the annotation.
So to find your answer, simply research what the purpose of the Repository annotation is. A basic search brings up this article from a reputable source which exactly puts it as simple as I was hoping to be able to put it:
https://www.baeldung.com/spring-component-repository-service
I quote:
#Repository’s job is to catch persistence-specific exceptions and re-throw them as one of Spring’s unified unchecked exceptions
Read: this functionality is added at runtime. Hence: proxy.

#RolesAllowed etc. without web.xml

I am currently working on a quite modular application where we have many jars that will be packaged and glued together in a war file.
Some of these jar files have REST resources that want to be secured. Common way is the #RolesAllowed annotation etc.
From my current knowledge this implies an existing web.xml in the WAR. This way we would have to implement jar-specific information (e.g. context roots) inside the WAR, not in the place where it belongs.
Like the most things nowadays - is there a way to programmatically set up security contexts etc. without a web.xml?
You can restrict access to your REST resources by registering RolesAllowedDynamicFeature in your REST configuration class that extends from ResourceConfig
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
super(ApplicationConfig.class);
register(RolesAllowedDynamicFeature.class);
}
}
So you can use your application roles on your resources methods like this
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
#Path("secured")
#PermitAll
public class SecuredResource {
#GET
#Path("user")
#RolesAllowed("user")
public String user(#Context SecurityContext sc) {
boolean test = sc.isUserInRole("user");
return (test) ? "true": "false";
}
#GET
#Path("admin")
#RolesAllowed("admin")
public String admin(#Context SecurityContext sc) {
boolean test = sc.isUserInRole("admin");
return (test) ? "true": "false";
}
}
Jersey documentation has more details on securing REST resources using annotations here
https://jersey.github.io/documentation/latest/security.html#d0e12428
I've not worked with JAX-RS for a while, but the last time I checked, when using annotation-based security, web.xml is not optional.
See my answer for details.
https://stackoverflow.com/a/20023018/839733

Spring. Registering Aspect with #Bean

I need some help with creating Aspect bean.
I have module A, B... and module starter-x.
I have a couple of Aspects declared in module starter-x, and configurations for them(like spring data repository monitoring aspect with configuration annotated with #ConditionalOnClass(Repository.class) etc).
Here's how example configuration looks like
#Configuration
#ConditionalOnClass(Repository.class)
public class RepositoryMonitoringConfiguration {
#Bean
#Qualifier("RepositoryCallCounter")
public DatabaseCallCounter repositoryCounter(){
return new RepositoryCallCounter();
}
}
And i have class with #Aspect annotation(but not #Component, when i create bean with #Component not #Configuration, everything is ok)
So my question, is it possible to provide aspect bean in #Configuration class, without #Component annotation on it (I want to create cool starter with auto configuration)
It's possible, you just need to mark the configuration class like this:
#Configuration
#EnableAspectJAutoProxy
#ConditionalOnClass(Repository.class)
public class RepositoryMonitoringConfiguration {
#Bean
public DatabaseCallCounter repositoryCounter(){
return new RepositoryCallCounter();
}
}
I encountered same problem as you.
And I figured out the cause.
The solution is to exclude your Config class from #Around like following code.
package hello.hellospring.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Slf4j #Aspect
public class TimeTraceAop {
#Around("execution(* hello.hellospring..*(..)) && !target(your config class)")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
log.info("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
log.info("END: " + joinPoint + " " + (finish - start) + "ms");
}
}
}
You probably configured #Around to root package and its subpackages.
And that means #Around method is also applied to the configuration class.
So, when the spring tries to make a bean for not only AOP but also other else,
Spring finds AOP class in container that you want to make.
But, of course, there doesn't exist AOP class
if #Component is not applied to AOP class.
And Spring thinks AOP references AOP.
So Spring makes an error like following log.
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
memberController defined in file [/Users/macbook/Desktop/git/hello-spring/build/classes/java/main/hello/hellospring/controller/MemberController.class]
↓
memberService defined in class path resource [hello/hellospring/SpringConfig.class]
┌─────┐
| timeTraceAop defined in class path resource [hello/hellospring/SpringConfig.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

in jersey 2.x am getting the error like java.util.concurrent.ExecutionException

i have used the jersey implementation of a jaxrs but iam unable to the programme following is case where i am getting problem any idea help me
in following programme i used the jersy 2.x implementaion of jaxrs
i implemented the programme using jersey implemetation of jax-rs(restfull)
2 classes i have written instead of web.xml i used the class
MyResource.java
package com.rest.application;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import com.rest.webservice.SampleService;
#ApplicationPath("rest")
public class MyResource {
private Set s;
public MyResource() {
s=new HashSet();
s.add(new SampleService());
}
public Set getSingletons() {
return s;
}
}
SampleService.java
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
#Path("sample")
public class SampleService {
#GET
#Produces("text/html")
#Path("{username}")
public String sayHello(#PathParam("username")String s) {
return "<font color='blue' size=8>Hello:" +s+ "</font>";
}
#GET
#Produces("text/plain")
public String sayBye() {
return "Bye";
}
}
i added the all jars needed to this programm
still i am getting following error
java.util.concurrent.ExecutionException:
org.apache.catalina.LifecycleException:
Failed to start component [StandardEngine[Catalina].
StandardHost[localhost].StandardContext[/RestApp2]]
Caused by: org.apache.catalina.LifecycleException:
Failed to start component [StandardEngine[Catalina].
StandardHost[localhost].StandardContext[/RestApp2]]
like this same error is displaying everywhere
when i changed the server tomcat 7 to 6
it is working but not displaying the output
will anybody have any idea thanking you in advance
This says, #ApplicationPath("rest") may be applied only to the subclass of Application.
Can you share more on what are you trying to do and what is the complete stack trace. Are you using web.xml ?
As #MSD mentioned, your use of #ApplicationPath is incorrect. See the Jersey documentation on Application Deployment to see all the different deployment options, and how they work in different environments.
Basically, the easiest way put the #ApplicationPath on an empty Application class
#ApplicationPath("/rest")
public class MyApplication extends Application {}
This will scan the entire classpath for #Provider and #Path annotated classes, to register with the application. Though this may seem easier, the more common approach, when working with Jersey is to use it's ResourceConfig class, which is a subclass of Application. You can register packages, which will scan the packages and subpackages
#ApplicationPath("/rest")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("com.my.packages");
}
}
One benefit is that sometimes there will be third party dependencies that are annotated, but you don't want registered. To register individual classes just use register(...class) in the ResourceConfig.
Now the reason for the error in Tomcat 7 and not 6, is most likely because Tomcat 6 (servlet 2.5) does not have the sevlet pluggability mechanism, which uses the ServletContainerInitializer. The Jersey implementation of this initializer loads the application, looking for the #ApplicationPath on the Application subclass. If you're not in a 3.0 environment, this functionality will not work.
Note the Jersey initializer is included in the jersey-container-servlet jar. You can read more about it here

Categories

Resources