Im building a small application using Spring (Boot, Data, Data Rest).
I have some JpaRepositories that aumotatically are exported as Rest endpoints.
What i want to do is to change the base path from / to /api.
Now to list all people for example i do a GET to http://localhost:8080/people and i want the url to be http://localhost:8080/api/people.
I tried adding this config class but nothing happened (it seems that Spring Boot overrides this config):
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
#Override
protected Class<?>[] getRootConfigClasses()
{
return new Class<?>[] { Application.class};
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class<?>[] { RestExporterRestConfig.class, RepositoryRestMvcConfiguration.class };
}
#Override
protected String[] getServletMappings()
{
return new String[] { "/api/*" };
}
}
My Application.java:
#Configuration
#ComponentScan
#Import(RestExporterRestConfig.class)
#EnableJpaRepositories
#EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
and RestExporterRestConfig:
#Configuration
public class RestExporterRestConfig extends RepositoryRestMvcConfiguration {
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", validator());
}
#Bean
#Qualifier
public DefaultFormattingConversionService defaultConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(StringToDate.INSTANCE);
return conversionService;
}
#Bean
public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
}
}
Well i figured it out. SpringWebAppInitializer is not necesary in this case. I just added this code to Application.java:
#Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean reg = new ServletRegistrationBean(dispatcherServlet);
reg.addUrlMappings("/api/*");
return reg;
}
I think this is the correct way to modify (add, change mappings, etc) servlets using Spring Boot.
Related
I do really simple demo web app using Spring mvc, Java class annotations and .jsp instead of .html.
When I start Tomcat on localhost - I always get 404 error. Where do I do mistake?
Config.class
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.mylov.springsecurity.demo.config")
public class Config {
//Define bean for ViewResolver
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Dispatcher Servlet Initialization
public class DispatcherServletInit extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{Config.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
Controller
#Controller
public class DemoController {
#GetMapping({"", "/"})
public String showHome() {
return "home";
}
}
Project Structure:
You need to scan packages under demo package. So Spring will find your Controllers and Config classes. But now you point to only config. So package 'controller' won't be scan.
Try to do this:
#ComponentScan(basePackages = "com.mylov.springsecurity.demo")
Try providing the mapping as below so that all url's are mapped with the servlet:
public class DispatcherServletInit extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{Config.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/*"};
}
}
Also change the base packages to as below so that the controller class is also picked up for component scanning :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.mylov.springsecurity.demo")
public class Config {
..........
}
I've implemented a starter that configures Swagger the way I like. In addition, I'd like to redirect every call to the app's root URL (e.g. localhost:8080) to /swagger-ui.html.
Therefore, I added an own AbstractEndpoint which is instantiated in the #Configuration class as follows:
#Configuration
#Profile("swagger")
#EnableSwagger2
public class SwaggerConfig {
...
#Bean
public RootEndpoint rootEndpoint() {
return new RootEndpoint();
}
#Bean
#ConditionalOnBean(RootEndpoint.class)
#ConditionalOnEnabledEndpoint("root")
public RootMvcEndpoint rootMvcEndpoint(RootEndpoint rootEndpoint) {
return new RootMvcEndpoint(rootEndpoint);
}
}
The respective classes look like this:
public class RootEndpoint extends AbstractEndpoint<String> {
public RootEndpoint() {
super("root");
}
#Override
public String invoke() {
return ""; // real calls shall be handled by RootMvcEndpoint
}
}
and
public class RootMvcEndpoint extends EndpointMvcAdapter {
public RootMvcEndpoint(RootEndpoint delegate) {
super(delegate);
}
#RequestMapping(method = {RequestMethod.GET}, produces = { "*/*" })
public void redirect(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.sendRedirect("/swagger-ui.html");
}
}
As stated in public RootEndpoint(), the custom Endpoint is bound to /root. Unfortunately, I can't specify super(""); or super("/"); as those values throw an exception (Id must only contains letters, numbers and '_').
How can I achieve having a custom Endpoint listening to the root URL in a starter using #Configuration files to instantiate beans?
I solved it with an easier approach by adding a WebMvcConfigurerAdapter bean in the #Configuration:
#Bean
public WebMvcConfigurerAdapter redirectToSwagger() {
return new WebMvcConfigurerAdapter() {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("").setViewName("redirect:/swagger-ui.html");
}
};
}
I am new to spring and am working on a rest service written using Spring and Apache CXF with Java Configurations. I have the following rest service.
#Path("/release/")
#Component
#RestService
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ReleaseResource extends AbstractService implements IResource {
#Override
#CustomLogger
#GET
public Response get() {
//Some Logic
return Response.ok("Success!!").build();
}
}
I have created an aspect using #AspectJ for logging. However, the aspect is not working on the services written in CXF. I did a bit of searching in net and found that Spring needs proxy beans for the aspects to work. Then I tried few approaches such as
Making the service class implement an interface
Using CGLIB library and scope proxy mode TARGET_CLASS
Extending a class with method
#Override
public void setMessageContext(MessageContext context) {
this.context = context;
}
But none of them worked.
Any idea if it is possible to run the aspect around the services?
If yes, can someone please tell me how to.
I have read that this can be achieved by bytecode weaving the aspectj manually instead of using spring aspectj autoproxy (not sure how to do it though). Can someone tell me if this is a good option and how to do it?
EDIT:
Sorry for the incomplete info provided. Attaching the other classes
#Aspect
#Configuration
public class LoggerAspect {
#Pointcut(value = "execution(* *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(CustomLogger)")
public Object logAction(ProceedingJoinPoint pjp, CustomLogger customLogger) throws Throwable {
//Log Some Info
return pjp.proceed();
}
}
Web Initializer class:
#Configuration
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(TestConfig.class);
return appContext;
}
}
Config Class:
#Configuration
#ComponentScan(basePackages = "com.my.package")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TestConfig {
private static final String RESOURCES_PACKAGE = "com.my.package";
#ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public JacksonJsonProvider jacksonJsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public LoggerAspect getLoggerAspect() {
return new LoggerAspect();
}
#Bean
IResource getReleaseResource() {
return new ReleaseResource();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(),
JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setProvider(jacksonJsonProvider());
return factory.create();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, TestConfig.RESOURCES_PACKAGE);
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
}
RestServiceBeanScanner class
public final class RestServiceBeanScanner {
private RestServiceBeanScanner() {
}
public static List<Object> scan(ApplicationContext applicationContext, String... basePackages) {
GenericApplicationContext genericAppContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(genericAppContext, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RestService.class));
scanner.scan(basePackages);
genericAppContext.setParent(applicationContext);
genericAppContext.refresh();
List<Object> restResources = new ArrayList<>(
genericAppContext.getBeansWithAnnotation(RestService.class).values());
return restResources;
}
}
How can I register a custom converter in my MongoTemplate with Spring Boot? I would like to do this only using annotations if possible.
I just register the bean:
#Bean
public MongoCustomConversions mongoCustomConversions() {
List list = new ArrayList<>();
list.add(myNewConverter());
return new MongoCustomConversions(list);
}
Here is a place in source code where I find it
If you only want to override the custom converters portion of the Spring Boot configuration, you only need to create a configuration class that provides a #Bean for the custom converters. This is handy if you don't want to override all of the other Mongo settings (URI, database name, host, port, etc.) that Spring Boot has wired in for you from your application.properties file.
#Configuration
public class MongoConfig
{
#Bean
public CustomConversions customConversions()
{
List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
converterList.add(new MyCustomWriterConverter());
return new CustomConversions(converterList);
}
}
This will also only work if you've enabled AutoConfiguration and excluded the DataSourceAutoConfig:
#SpringBootApplication(scanBasePackages = {"com.mypackage"})
#EnableMongoRepositories(basePackages = {"com.mypackage.repository"})
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication
{
public static void main(String[] args)
{
SpringApplication.run(MyApplication.class, args);
}
}
In this case, I'm setting a URI in the application.properties file and using Spring data repositories:
#mongodb settings
spring.data.mongodb.uri=mongodb://localhost:27017/mydatabase
spring.data.mongodb.repositories.enabled=true
You need to create a configuration class for converter config.
#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
#Profile("!testing")
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.host}") //if it is stored in application.yml, else hard code it here
private String host;
#Value("${spring.data.mongodb.port}")
private Integer port;
#Override
protected String getDatabaseName() {
return "test";
}
#Bean
public Mongo mongo() throws Exception {
return new MongoClient(host, port);
}
#Override
public String getMappingBasePackage() {
return "com.base.package";
}
#Override
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new LongToDateTimeConverter());
return new CustomConversions(converters);
}
}
#ReadingConverter
static class LongToDateTimeConverter implements Converter<Long, Date> {
#Override
public Date convert(Long source) {
if (source == null) {
return null;
}
return new Date(source);
}
}
Within a normal Spring Application, I have:
#Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
#Override
protected Map<String, MediaType> getDefaultMediaTypes() {
Map<String, MediaType> mediaTypes = super.getDefaultMediaTypes();
mediaTypes.put("extension", new MediaType("foo", "bar"));
return mediaTypes;
}
}
So I can do something like:
#RequestMapping(produces = "foo/bar")
public void test() { ... }
And then call:
http://.../myResource.extension
When I do this with Spring Boot, then extends WebMvcConfigurationSupport would prevent all the auto configuration.
So how can I easily register new Extension-Accept-Header mappings with Spring Boot?
This should do it, I have verified the code with Boot 1.2.1.RELEASE
#Configuration
public class EnableWebMvcConfiguration extends WebMvcAutoConfiguration.EnableWebMvcConfiguration {
#Override
protected Map<String, MediaType> getDefaultMediaTypes() {
Map<String, MediaType> mediaTypes = super.getDefaultMediaTypes();
mediaTypes.put("extension", new MediaType("foo", "bar"));
return mediaTypes;
}
}
according to avi's answer, we should use extends WebMvcAutoConfiguration.EnableWebMvcConfiguration
For adding additional extensions and media types, it might be more easy to just override configureContentNegotiation:
#Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.mediaType("extension", new MediaType("foo", "bar"));
}