Spring Boot simple cache results in 404 error - java

I am trying add Spring cache to my project. I read the getting started and some examples on the net and look easy, but when I use cacheable annotation, my method results in HTTP 404 error.
These were my steps:
Add cache dependency to my pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Add #EnableCaching annotation to my main class.
#SpringBootApplication
#EnableCaching
public class MyProjectApplication {
public static void main(String[] args) {
SpringApplication.run(MyProjectApplication.class, args);
}
}
Add a CacheManager Bean to my project:
#Configuration
public class ConfigApplication {
#Bean
public CacheManager cacheManager() {
String[] cacheNames = { "videoInfo" };
return new ConcurrentMapCacheManager(cacheNames);
}
}
Now in the method that I like caching, I added #Cacheable("videoInfo") annotation. (This method's bean is annotated with #RestController). I added other method with #CacheEvict to reset the cache.
#Override
#Cacheable("videoInfo")
#RequestMapping(value = "/get-video-download", method = RequestMethod.POST, produces = "application/json")
public DownloadInfo getDownloadUrls(#RequestParam String videoId) {
DownloadInfo di = null;
di = downloadService.getDownloadInfo(videoId);
return di;
}
#CacheEvict(value = "videoInfo", allEntries = true)
#RequestMapping(value = "/get-video-download-reset-cache", method = RequestMethod.GET)
public void getDownloadUrlsResetCache() {
LOG.debug("Se ha limpiado la caché de videos correctamente");
}
Then, when I make an HTTP request for any method in this bean, I get 404 error. I don't see logs from error and all work fine if I comment these two annotations in the above methods. Any idea of this?

Related

Spring boot - beans configuration without #component annotation

As part of my university project I was asked to implement a simple spring-boot app(backend only) which can communicate with Postman through HTTP requests.
The project built in controller-service-repository architecture and conatins only 1 Entity(Post object with string content) and 2 end-points(create new post, get all posts).
I know there is a few ways to configure beans in spring-boot:
with an external XML file.
With #Configuration annotation & #Bean annotation
With #Component annotation(#RestController,#Service, #JpaRepository)
The 3rd way working great but i was asked to implement the 2nd way and I'm really struggling to get this working.
Im getting:
ServletException: Circular view path [post]: would dispatch back to the current handler URL [/post] again. Check your ViewResolver setup!
Tried to explore about this exception and i did manage to "solve" it by adding this maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.5.2</version>
</dependency>
which led to:
"org.thymeleaf.exceptions.TemplateInputException: Error resolving template [post], template might not exist or might not be accessible by any of the configured Template Resolvers"
what am i doing wrong ?
Configuration class:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.example.microblog.post.domain.repository"
})
public class ApplicationBeans {
#Bean
public PostController postController(PostService postService){
return new PostController(postService);
}
#Bean
public PostService postService(){
return new PostService();
}
}
Controller class:
#AllArgsConstructor
#RequestMapping(path = "post")
public class PostController {
#Autowired
private PostService service;
#CrossOrigin(origins = "http://localhost:4200")
#PostMapping("")
public PostEntity create(#RequestBody PostDto dto) {
return service.create(dto);
}
#GetMapping("/all")
#CrossOrigin(origins = "http://localhost:4200")
public List<PostEntity> getAll() {
return service.getAll();
}
}
Service Class:
#Transactional
public class PostService {
#Autowired
private PostRepository PostRepository;
public PostEntity create(PostDto dto){
PostEntity newPost = new PostEntity(dto.getContent());
return PostRepository.save(newPost);
}
public List<PostEntity> getAll(){
return PostRepository.findAll();
}
Repository class:
public interface PostRepository extends JpaRepository<PostEntity,Long> {}
For second approach, when you create a Bean, try not to have #Component/#Controller ... on the class for which you create the bean
#Configuration
public class AppConfig {
#Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
You can continue to autowire them like in third approach, try not to keep beans of same name
Your error points in the direction, that your Controller has difficulties to resolve the answer of your Service to a valid JSON response.
Note that #RestController is just a convenient way to add #Controller and #ResponseBody annotation. When you just add a #Bean annotation you are not adding either #Controller or #ResponseBody.
If you want to use the Controller class without using these Annotations you need to implement the functionality that these classes provide.
However I really see no way, why option 2 would be used for a Controller class. If you want to use it for a #Service class (which is doing the same as #Component) you can use the approach that Ravi suggested.

Getting 406 Could not find acceptable representation /Spring JSON Test. How to ignore .htm extension in tests?

Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html

RequestMapping placeholders don't work in JUnit tests

I have a simple Spring MVC controller whose RequestMapping is a property. It's a jar that include a controller. Downstream apps will take this jar and use the common endpoint, only the precise URL can vary by app)
Everything works fine when I include my jar into another app. The app has a property or yaml file and the property is set. I've verified that the endpoint works fine.
However, being the good developer that I am, I want to make an integration test that verifies that the URL determined by the property is exposed properly. I can get an #Value in the controller injected properly, but a ${} expression in the #RequestMapping will not be substituted from a properties file. I found a couple threads (Spring Boot REST Controller Test with RequestMapping of Properties Value and #RequestMapping with placeholder not working) But either they don't apply or I tried what they said and I couldn't get it to work.
The test that hits the static (iWork) endpoint works, but the one that is pulled from the property (iDontWork) doesn't work.
(This is Spring 4.2.6)
Controller
#RestController
#RequestMapping(value = {"/${appName}", "/iWork"})
public class Controller {
#Value("${appName}")
private String appName;
#RequestMapping(method= RequestMethod.GET)
public String handlerMethod (HttpServletRequest request) throws Exception {
// Proves the placeholder is injected in the class, but
// Not in the RequestMapping
assert appName != null;
assert !appName.equals("${appName}");
return "";
}
}
ControllerTest
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigWebContextLoader.class,
classes = { ControllerTest.Config.class })
public class ControllerTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
#Configuration
#ComponentScan(basePackages = {"test"})
static class Config {
// because #PropertySource doesnt work in annotation only land
#Bean
PropertyPlaceholderConfigurer propConfig() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocation(new ClassPathResource("test.properties"));
return ppc;
}
}
#Test
public void testStaticEndpoint() throws Exception {
mvc.perform(get("/iWork")).andExpect(status().isOk());
}
#Test
public void testDynamicEndpoint() throws Exception {
mvc.perform(get("/iDontWork")).andExpect(status().isOk());
}
}
test.properties
appName = iDontWork
You're "simply" missing
#EnableWebMvc
on your #Configuration class. Without it, Spring's Mock MVC stack will register your controller handler methods with DefaultAnnotationHandlerMapping which isn't smart enough to resolve the placeholders in #RequestMapping.
If you do provide it, Mock MVC will instead use RequestMappingHandlerMapping, which is.
You need to add placeholder value while creating mockMvc as shown below.
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(accountController)
.addPlaceholderValue("propertyName", "propertyValue")
.build();
}

How to create a REST service with spring-boot?

I'm using spring-boot and want to integrate a simple REST service as follows.
#Controller
#RequestMapping("/content")
public class MyServiceRest extends SpringBeanAutowiringSupport {
#RequestMapping(method = RequestMethod.GET)
public String test() {
return "OK";
}
}
Result: both localhost:8080/<app-name>/services/content results "No service was found.". Why?
Do I have to explicit publish the service somehow?
Maybe it is due to my dispatcher servlet?
#Bean
public ServletRegistrationBean dispatcherServletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new CXFServlet(), "/services/*");
registration.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return registration;
}
Since you are using Spring Boot, make sure that your application is correctly setup by adding the correct annotations. For instance,
#EnableAutoConfiguration
#EnableWebMvc
#Configuration
#ComponentScan
/*
* Application Setups using Spring boot.
*/
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#EnableWebMvc is the annotation to add for using Spring MVC with Spring boot.
And then you can define your controller as you did in your question.
add package with controller class to #Component scan in main class like: #ComponentScan( basePackages = { "your.package.with.controller" } ), this happens when spring didn't initialize (doesn't know about) controller
you should also add url mapping for your method
#RequestMapping(method = RequestMethod.GET, value = "url_here",
try
#RequestMapping(method = RequestMethod.POST, value = "/",
In the latest version of Spring Boot, that I am currently using, the web Service would address be http://localhost:8080/content
Also, the class I use to launch the service looks as follows:
#ComponentScan("eu.buzea")
#EnableAutoConfiguration
#EnableTransactionManagement
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Source Code
https://drive.google.com/open?id=0BzBKpZ4nzNzUWmJmOTFwbTFjWWM
using Swagger
http://localhost:7070/swagger-ui.html#/
**Cheers*
As of current spring-boot.1.5.6 there is no requirement using cxf.
Just use a #RestController with #GetMapping, and be happy to access localhost:8080/content.

SpringApplication not able to instantiate bean

I am new to Spring & WebService and trying a few guides on Spring.io.
I planned to create a basic RESTful WebService which consumes Google Direction API and returns just the status.
Here are the classes:
Resource
#JsonIgnoreProperties(ignoreUnknown=true)
public class Direction {
// getters & setters
public Direction() {
super();
}
private String status;
public String toString() {
return status;
}
}
Controller
#Controller
public class Consumer {
public Consumer() {
super();
}
#Resource
private String url;
#Resource
private RestTemplate client;
#Resource
private String apiKey;
#RequestMapping(value = "/directions", method=RequestMethod.GET)
public #ResponseBody Direction consume(#RequestParam(value="source") String source, #RequestParam(value="destination") String destination) {
return consumeDirections(buildURI(source, destination));
}
// Builds URI
private String buildURI(...) {
...
}
private Direction consumeDirections(final String requestURI) {
return client.getForObject(requestURI, Direction.class);
}
}
Configuration v1
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
Springconfig
http://pastebin.com/dsNVBWQq
Spring returns that No qualifying bean of type [java.lang.String] found for dependency.
This happens for all the beans in Consumer.
However, this works Configuration v2
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
#Resource
private Consumer consumer;
public void execute() {
System.out.println(consumer.consume("x", "z"));
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("application-config.xml");
context.getBean(Application.class).execute();
}
}
Some observations
#Resouce(Explicitly define bean) doesnt work for v1
SpringApplication is not aware of the Springconfig and fails during bean instantiation
I would like to understand why this issue crops up and how to resolve it?
The reason is very easy, the xml config is not loaded. have a look at Spring-Boot: XML Config
if you don't wanna touch existing xml, you need another #configuration annotated class and #ImportResource to load the xml configuration, just like the document says.
IMO, you don't need apiKey and url in the config, you should annotate them with #value, and define them in a .properties file. There are also default settings of spring boot, you get take advantage of it. like, name the properities application.properities and put it on classpath, spring boot will load it automatically.

Categories

Resources