I'm building REST services via Spring MVC in the application where the GUI is managed by Wicket. Basically, all I need is the DispatcherServlet and a controller with #RequestMapping/#RequestBody.
Because services serve JSON, I need to set MappingJackson2HttpMessageConverter. I can do this via AnnotationMethodHandlerAdapter and that works fine:
#Configuration
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
#Bean
public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter() {
HttpMessageConverter<?>[] converters = { new MappingJackson2HttpMessageConverter()};
AnnotationMethodHandlerAdapter adapter = new AnnotationMethodHandlerAdapter();
adapter.setMessageConverters(converters);
return adapter;
}
}
The problem is that AnnotationMethodHandlerAdapter is deprecated and it's recommended to use RequestMappingHandlerAdapter instead.
But if I use this configuration:
#Configuration
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
#Bean
public RequestMappingHandlerAdapter requestHandler() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
return adapter;
}
}
I receive an exception:
javax.servlet.ServletException: No adapter for handler [cz.swsamuraj.wicketspring.ws.api.QuestionApiController#69f8a79f]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
at org.springframework.web.servlet.DispatcherServlet.getHandlerAdapter(DispatcherServlet.java:1198)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
So, my question is: how can I set up a handler adapter in the RequestMappingHandlerAdapter?
I spent couple of days of research, but I didn't find any useful example of how to configure RequestMappingHandlerAdapter. All the advices just says to put #EnableWebMvc on the configuration, but this is not the way because of this Wicket-Spring coexistence.
Just to provide a full context, I've created a small buildable and runnable project on Bitbucket: sw-samuraj/blog-wicket-spring-rest
I was able to solve my problem with different approach - with usage of WebApplicationInitializer, I was able to put the #EnableWebMvc annotation on my configuration class and therefore neither the bean RequestMappingHandlerAdapter, nor AnnotationMethodHandlerAdapter are necessary. JSON now works fine, out-of-the-box.
Configuration
#Configuration
#EnableWebMvc
#ComponentScan("cz.swsamuraj.wicketspring")
public class SpringRestConfiguration {
// some additional beans needed for business logic
}
WebApplicationInitializer
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(SpringRestConfiguration.class);
servletContext.addListener(new ContextLoaderListener(dispatcherContext));
ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/rest/*");
}
}
Example project
Complete working example is on Bitbucket: blog-wicket-spring-rest
Related
I try to create websocket connection using sockjs on client and spring4 (with java config) on backend under GlassFish 3.1.2.2 but whatever I do it always fail with the following problem:
java.lang.IllegalArgumentException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml. Also you must use a Servlet 3.0+ container
However it works correctly under Tomcat without any error.
Is there a way to make this work under GlassFish 3.1.2.2? Or should I rewrite Spring java config back to web.xml style? If yes, can somebody show me an example?
Thank you.
I already enabled websocket in http protocol configuration in GlassFish manager and set
dynamic.setAsyncSupported(true);
in the WebApplicationInitializer.
#Configuration
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
final AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(IdCardReaderWebsocketApp.class);
ctx.setServletContext(servletContext);
final Dynamic dynamic = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
dynamic.setLoadOnStartup(2);
dynamic.addMapping("/sockjs/*");
dynamic.setAsyncSupported(true);
}
}
#Configuration
#ComponentScan("com.something.websocket.idcardreader.mock")
#EnableWebMvc
#Import({ IdCardReaderWebsocketConfig.class })
public class IdCardReaderWebsocketApp extends WebMvcConfigurerAdapter {
}
#Configuration
#EnableWebSocketMessageBroker
public class IdCardReaderWebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/idCardReaderWebsocketMockEndpoint").setAllowedOrigins("*").withSockJS();
}
#Override
public void configureMessageBroker(final MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/mockApp");
registry.enableSimpleBroker("/idcard");
}
}
Ok, finally I fixed the issue. I had also web.xml file with an other filter. So I added
<async-supported>true</async-supported>
to that filter and problem solved. It still failes with ws:// protocol but at least it works with http when sockjs trying.
I am working with a Spring boot application. I have a rest controller that returns Callable.
#GetMapping("/fb-roles")
#Timed
public Callable<List<FbRole>> getAllFbRoles() {
log.debug("REST request to get all FbRoles");
return (() -> { return fbRoleRepository.findAll(); });
}
A ThreadPoolTaskExecutor is configures as follow:
#Configuration
#EnableAsync
#EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final JHipsterProperties jHipsterProperties;
public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
#Override
#Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
executor.setThreadNamePrefix("fb-quiz-Executor-");
return new ExceptionHandlingAsyncTaskExecutor(executor);
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
2018-09-19 00:43:58.434 WARN 10104 --- [ XNIO-2 task-28] o.s.w.c.request.async.WebAsyncManager :
!!!
An Executor is required to handle java.util.concurrent.Callable return values.
Please, configure a TaskExecutor in the MVC config under "async support".
The SimpleAsyncTaskExecutor currently in use is not suitable under load.
But while accessing the api server is producing the following warning
Spring configuration is a bit confusing in this respect, since it requires separate configuration for MVC Async support, i.e. using a Controller handler method that returns a Callable, and for any Spring bean method annotated with #Async. To configure both of it correctly you can apply something like the configuration below keeping in mind that the AsyncTaskExecutor config might need amending:
#Configuration
#EnableAsync
public class AsyncConfig implements AsyncConfigurer {
#Bean
protected WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(getTaskExecutor());
}
};
}
#Bean
protected ConcurrentTaskExecutor getTaskExecutor() {
return new ConcurrentTaskExecutor(Executors.newFixedThreadPool(5));
}
}
On a side note, you might feel tempted to simply annotate your Controller handler method with #Async. This will only have the desired effect - freeing up web server threads - on fire and forget operations (this observation is based on Spring Boot 2.1.2, possibly they will address this in the future). If you want to leverage the power of Servlet 3.0 Asynchronous Processing, you really have to use Callables and configure them with a WebMvcConfigurer.
Given the warning and your Callable method.
Seems like Spring is not able to identify the Executor bean that you have just set up
in your configuration class.
You might need to annotate your method and specify the executor bean name, so
#GetMapping("/fb-roles")
#Timed
#Async("taskExecutor")
public Callable<List<FbRole>> getAllFbRoles() {
log.debug("REST request to get all FbRoles");
return (() -> { return fbRoleRepository.findAll(); });
}
Hope this helps
Guide can be found here: https://www.baeldung.com/spring-async
From your warning "Please, configure a TaskExecutor in the MVC config under "async support". The SimpleAsyncTaskExecutor currently in use is not suitable under load."
I wonder if you use the spring mvc or not?
With MVC, a few below links might help:
Configuring mvc async task executor in springboot application
Spring Boot - Any shortcuts for setting TaskExecutor?
I had combined mvc configuration (xml + annotations) and for me the following config helped to fix that warning:
mvc-servlet.xml:
<mvc:annotation-driven>
<mvc:async-support default-timeout="30000" task-executor="taskExecutor"
</mvc:annotation-driven>
AsyncConfig.java
#Configuration
#EnableAsync
public class AsyncConfig implements AsyncConfigurer {
#Bean
public AsyncTaskExecutor taskExecutor() {
return new ConcurrentTaskExecutor(Executors.newCachedThreadPool());
}
}
You need to configure an task executor like described by Fritz already. Sadly its solution uses now deprecated WebMvcConfigurerAdapter.
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class AsyncWebMvcConfiguration implements WebMvcConfigurer{
#Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(asyncExecutor());
}
private AsyncTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.initialize();
return executor;
}
}
Enhancement for solution from Fritz Duchardt and derived from: https://docs.sentry.io/platforms/java/guides/spring-boot/async/
I have a spring boot application that serves my jersey based api. I have a requirement to have the services layer serve blob data to a client as a stream. I wrote a servlet to do that and configured it as follows.
#Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new BlobReaderServlet(),"/blobReader/*");
}
However, in the servlet code I can't seem to inject any components (they are all null). I need to inject a component that actually loads the blob data from the database.
#WebServlet(name = "BlobReaderServlet",
urlPatterns = {"/blobreader"})
#Component
public class BlobReaderServlet extends HttpServlet {
Logger logger = Logger.getLogger(this.getClass().getName());
#Inject
DocumentLoaderComponent blobLoader;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
I strongly suspect the servlet isn't a spring managed component after all and dependency injection isn't working. How can I get access to a component from the context?
UPDATE
It was much simpler than I thought.
#Override
public void init() throws ServletException {
ApplicationContext ac = (ApplicationContext) getServletConfig().getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
this.documentBlob = (DocumentBlob) ac.getBean("documentBlobBean");
}
You are correct that your servlet isn't a Spring managed bean. That is because you are instantiating the instance directly (i.e., calling new BlobReaderServlet() in your bean method). Another solution is to change your configuration class as follows:
#Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(blobReaderServlet(),"/blobReader/*");
}
#Bean
public BlobReaderServlet blobReaderServlet(){
return new BlobReaderServlet();
}
This will allow Spring to manage the instance and perform autowiring on it.
I am trying to start my new project using spring webmvc (version 4.1.6) with primefaces (version 5.2) I am able start up the project, however when trying to acces css or other resources the urls looks like: http://localhost:8080/rais/public//javax.faces.resource/theme.css?ln=primefaces-aristo and results in a 404. The part: http://localhost:8080/rais/public/ looks as expected.
My configuration:
#Configuration
#EnableTransactionManagement
#EnableSpringConfigured
#EnableWebMvc
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
//Set init params
// Use JSF view templates saved as *.xhtml, for use with Facelets
servletContext.setInitParameter("javax.faces.DEFAULT_SUFFIX", ".xhtml");
servletContext.setInitParameter("javax.faces.FACELETS_VIEW_MAPPINGS", "*.xhtml");
ServletRegistration.Dynamic facesServlet = servletContext.addServlet("Faces Servlet", javax.faces.webapp.FacesServlet.class);
facesServlet.setLoadOnStartup(1);
facesServlet.addMapping("*.xhtml");
ServletRegistration.Dynamic registration = servletContext.addServlet("dsp", new DispatcherServlet());
registration.setInitParameter("contextConfigLocation", "");
registration.setLoadOnStartup(1);
registration.addMapping("/");
servletContext.addListener(ConfigureListener.class);
servletContext.addListener(org.springframework.web.context.request.RequestContextListener.class);
//Add OpenEntityManagerInViewFilter Filter
servletContext.addFilter("openEntityManagerInViewFilter", OpenEntityManagerInViewFilter.class).addMappingForUrlPatterns(null, true, "/*");
super.onStartup(servletContext);
}
WebMVC configuration:
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
ViewResolver viewResolver() {
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setViewClass(org.springframework.faces.mvc.JsfView.class);
resolver.setPrefix("/WEB-INF");
resolver.setSuffix(".xhtml");
return resolver;
}
faces-config.xml (ani hint in moving this to java config also greatly appreciated)
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
<action-listener>org.primefaces.application.DialogActionListener</action-listener>
<navigation-handler>org.primefaces.application.DialogNavigationHandler</navigation-handler>
<view-handler>org.primefaces.application.DialogViewHandler</view-handler>
</application>
Any help is greatly appreciated. Please ask if additional information is required:
Ok changed the UrlBasedViewResolver(); to a InternalResourceViewResolver(); now it seems to be working.
I am using Spring for IOC and transaction management, and am planning to use Apache Shiro as the security library.
Whenever I want to check a user's permissions, I call subject.isPermitted("right"), whereupon Shiro checks for the permission using a datastore. Within these calls, a database connection is established, and I have annotated the method with #Transactional. However, I always receive an error that there is no Hibernate session bound to the thread whenever I execute the permission check.
The method is in the Realm class. I defined a custom Shiro Realm class:
#Component
public class MainRealm extends AuthorizingRealm {
#Autowired
protected SysUserDao userDao;
#Transactional
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
...
final SysUser user = this.userDao.findByUsername(un);
...
return authInfo;
}
#Transactional
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
...
permissions = this.userDao.getAccessRights(un);
...
return authInfo;
}
}
Apache Shiro uses a Servlet Filter, so I have the following defined in web.xml:
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
I am using programmatic configuration for Spring. Here is my App Config class:
#Configuration //Replaces Spring XML configuration
#ComponentScan(basePackages = "com.mycompany")
#EnableTransactionManagement //Enables declarative Transaction annotations
public class SpringAppConfig {
#Bean
public DataSource sqlServerDataSource() throws Exception {...}
#Bean
#Autowired
public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {...}
#Bean
public AnnotationSessionFactoryBean getSessionFactory() throws Exception {...}
#Bean
public static PersistenceExceptionTranslationPostProcessor exceptionTranslation() {...}
#Bean
#Autowired
public DefaultWebSecurityManager securityManager(MainRealm mainRealm) {
final HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(shiroHash);
hcm.setHashIterations(shiroIter);
hcm.setStoredCredentialsHexEncoded(shiroHexEncoded);
mainRealm.setCredentialsMatcher(hcm);
final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
sm.setRealm(mainRealm);
return sm;
}
#Bean
#Autowired
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
final ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(securityManager);
return filter;
}
/**
* This method needs to be static due to issues defined here:<br>
* https://issues.apache.org/jira/browse/SHIRO-222
*/
#Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
LifecycleBeanPostProcessor lbpp = new LifecycleBeanPostProcessor();
return lbpp;
}
#Bean
#DependsOn("lifecycleBeanPostProcessor")
public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
#Bean
#Autowired
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager secMan) {
AuthorizationAttributeSourceAdvisor advBean = new AuthorizationAttributeSourceAdvisor();
advBean.setSecurityManager(secMan);
return advBean;
}
}
To summarize the issue, I believe my MainRealm class is being wired properly (it has an #Autowired dependency to a DAO object and I verified that it is not null) with the exception of the #Transactional annotation. Due to this, I cannot directly call user.isPermitted("") as it prompts an error: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.
Would like to ask for help in checking whether I missed anything in my Spring configuration.
In the meantime I have hacked over this issue by calling the user.isPermitted("") function within a method in my Service class that's correctly bound by a #Transactional.
EDIT When I checked the logs for Spring initialization I can see this:
Bean 'mainRealm' of type [class com.x.security.MainRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
According to this SO answer it means MainRealm isn't being postprocessed by the transaction manager therefore any #Transactional annotations get ignored. If so, how can I correct this?
EDIT 2 According to this SO question: "In other words, if I write my own BeanPostProcessor, and that class directly references other beans in the context, then those referenced beans will not be eligible for auto-proxying, and a message is logged to that effect." I just checked ShiroFilterFactoryBean and it is in fact a BeanPostProcessor. And the problem is it requires a SecurityManager instance that in turn requires a MainRealm instance. So both beans are autowired and are thus ineligible for proxying. I feel like I'm closer to the solution but I still can't resolve it.
The root cause of the issue is in fact due to the following:
All BeanPostProcessors and their directly referenced beans will be instantiated on startup... Since AOP auto-proxying is implemented as a BeanPostProcessor itself, no BeanPostProcessors or directly referenced beans are eligible for auto-proxying (and thus will not have aspects 'woven' into them.
Referenced SO question is here.
I have resolved this issue by decoupling the Realm bean creation from the SecurityManager bean creation.
The relevant change is from the following code:
#Bean
#Autowired
public DefaultWebSecurityManager securityManager(MainRealm mainRealm) {
final HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(shiroHash);
hcm.setHashIterations(shiroIter);
hcm.setStoredCredentialsHexEncoded(shiroHexEncoded);
mainRealm.setCredentialsMatcher(hcm);
final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
sm.setRealm(mainRealm);
return sm;
}
to the following code:
#Bean
public DefaultWebSecurityManager securityManager() {
final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
//sm.setRealm(mainRealm); -> set this AFTER Spring initialization so no dependencies
return sm;
}
Then I use a ServletContextListener which listens to when the Spring context initialization completes and I have the both the MainRealm and SecurityManager beans. Then I just plug one bean inside another.
#Override
public void contextInitialized(ServletContextEvent sce) {
try {
//Initialize realms
final MainRealm mainRealm = (MainRealm)ctx.getBean("mainRealm");
final DefaultWebSecurityManager sm = (DefaultWebSecurityManager)ctx.getBean("securityManager");
sm.setRealm(mainRealm);
} catch (Exception e) {
System.out.println("Error loading: " + e.getMessage());
throw new Error("Critical system error", e);
}
}
#Transactional annotation can be used before :
An interface def
An interface method
A class def
A PUBLIC method of a class
As explained in the documentation
The fact that your method is protected must be the reason of your problem, and your service method was maybe declared as public which would explained why it worked in that case