I have several read-only resources in my Controller. I want to in-memory cache them, I'm not clear how to do it in Spring Boot.
What I've done:
annotated main Application with #EnableCaching
annotated a resource with #Cacheable
#Cacheable
#RequestMapping(value = "/api/graph", method=RequestMethod.GET, produces = { "application/json"})
public #ResponseBody Iterable<Map<String, String>> graph() {
return Repository.graph();
}
What am I missing?
Since it's a read-only resource I guess I don't neet #CachePut am I right?
Obiouvsly I added spring-boot-starter-cache ad dependency in maven
Here are the steps to enable the Cache. For example i am using Google GuavaCacheManager for this purpose.
Add dependency and Enable the caching by using annotation on Application class.
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
#SpringBootApplication
#EnableCaching
public class Application extends SpringBootServletInitializer {
}
Expose the GuavaCacheManager as a bean in your Application class.
#Bean
public CacheManager cacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager();
// Cache expires every day
cacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterAccess(1,
TimeUnit.DAYS).expireAfterWrite(1, TimeUnit.DAYS));
cacheManager.setCacheNames(Arrays.asList("findUser"));
return cacheManager;
}
Mark the method as Cacheable so all the calls to this method first try to find the entry in Cache if not found then actually calls the method
#Override
#Cacheable("findUser")
public User findUser(String username) {
// biz logic to find the user and return the object
return user;
}
Related
I am using Spring Boot framework and trying to create a structure where the developer can only return org.json.JSONObject instance. I have this endpoint declaration.
#RequestMapping(path = "/hello", method = RequestMethod.POST)
#ResponseBody
public org.json.JSONObject hello(HttpServletRequest request, HttpServletResponse response) throws IOException
This always returns {"empty":false} because Jackson used by the framework does not know how to serialize the org.json instance. I am trying to tell Jackson how to serialize the org.json instance by using the following dependency.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
<version>2.13.0</version>
</dependency>
But I cannot get it work unless I change the return type to Map value which is not possible. Using
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JsonOrgModule())
.build()
does not help. Is there a global ObjectMapper object that is used by Spring Boot where I can register the JsonOrgModule at the application startup? How can I use org.json.JSONObject return type using Spring Boot framework.
Thanks!
As per Spring Boot docs 4.3 Customize the Jackson ObjectMapper section:
Any beans of type com.fasterxml.jackson.databind.Module are
automatically registered with the auto-configured
Jackson2ObjectMapperBuilder and are applied to any ObjectMapper
instances that it creates. This provides a global mechanism for
contributing custom modules when you add new features to your
application.
Therefore, if you provide a #Bean of type JsonOrgModule it will be automatically applied to the default ObjectMapper created at startup.
For exmaple:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public JsonOrgModule jsonOrgModule() {
return new JsonOrgModule();
}
}
#SpringBootTest
class ObjectMapperTests {
#Autowired
ObjectMapper defaultObjectMapper;
#Test
void defaultObjectMapperShouldWriteJsonObject() throws JSONException, JsonProcessingException {
// Given
var jsonObject = new JSONObject().put("username", "eHayik");
// When
var json = defaultObjectMapper.writeValueAsString(jsonObject);
// Then
assertThat(json).isEqualTo("{\"username\":\"eHayik\"}");
}
}
I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}
Let's say I have the following controller:
#RestController
public class MyController {
#GetMapping("v1/remain")
public MyObject getRemain() {
// ...
}
}
How can I enable or disable this endpoint at runtime dynamically with Spring boot? Also, is it possible to change this without having to restart the application?
You can either use #ConditionalOnExpression or #ConditionalOnProperty
#RestController
#ConditionalOnExpression("${my.property:false}")
#RequestMapping(value = "my-end-point", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#RequestMapping(value = "endpoint1", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> endpoint1(
return new ResponseEntity<>("Hello world", HttpStatus.OK);
}
}
Now if you want the above controller to work, you need to add following in application.properties file.
my.controller.enabled=true
Without the above statement, it will behave like the above controller don't exist.
Similiarly,
#ConditionalOnProperty("my.property")
behaves exactly same as above; if the property is present and "true", the component works, otherwise it doesn't.
To dynamically reload beans when a property changes, you could use Spring boot actuator + Spring cloud so that you have access to the /actuator/refresh endpoint.
This can be done by adding the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
The latter does require that you add the BOM for Spring cloud, which is:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Now you can enable the /actuator/refresh endpoint by setting the following property:
management.endpoints.web.exposure.include=refresh
This will allow you to send a POST call to /actuator/refresh, which will return an array of all changed properties.
By using the /actuator/refresh endpoint, it also allows you to use the #RefreshScope annotation to recreate beans. However, there are a few limitations:
#RefreshScope recreates the bean without re-evaluating conditionals that might have changed due to the refresh. That means that this solution doesn't work with #RefreshScope, as seen in the comment section of this question.
#RefreshScope doesn't work nicely with filters either, as seen in this issue.
That means you have two options:
Add the #RefreshScope to the controller and do the conditional logic by yourself, for example:
#RefreshScope
#RestController
#RequestMapping("/api/foo")
public class FooController {
#Value("${foo.controller.enabled}")
private boolean enabled;
#GetMapping
public ResponseEntity<String> getFoo() {
return enabled ? ResponseEntity.of("bar") : ResponseEntity.notFound().build();
}
}
This means you would have to add this condition to all endpoints within your controller. I haven't verified if you could use this with aspects.
Another solution is to not use #RefreshScope to begin with, and to lazily fetch the property you want to validate. This allows you to use it with a filter, for example:
public class FooFilter extends OncePerRequestFilter {
private Environment environment;
public FooFilter(Environment environment) {
this.environment = environment;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("true".equalsIgnoreCase(environment.getProperty("foo.controller.enabled"))) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.NOT_FOUND.value());
}
}
}
You'll have to register the filter as well, for example by using:
#Bean
public FilterRegistrationBean<FooFilter> fooFilter(Environment environment) {
FilterRegistrationBean<FooFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new FooFilter(environment));
bean.addUrlPatterns("/api/foo");
return bean;
}
Please note, this approach only fetches the property dynamically from the Environment. Refreshing the Environment itself still requires you to use the /actuator/refresh endpoint.
My goal is to have a have integration tests that ensures that there isn't too many database queries happening during lookups. (This helps us catch n+1 queries due to incorrect JPA configuration)
I know that the database connection is correct because there is no configuration problems during the test run whenever MyDataSourceWrapperConfiguration is not included in the test. However, once it is added, the circular dependency happens. (see error below) I believe #Primary is necessary in order for the JPA/JDBC code to use the correct DataSource instance.
MyDataSourceWrapper is a custom class that tracks the number of queries that have happened for a given transaction, but it delegates the real database work to the DataSource passed in via constructor.
Error:
The dependencies of some of the beans in the application context form a cycle:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
┌─────┐
| databaseQueryCounterProxyDataSource defined in me.testsupport.database.MyDataSourceWrapperConfiguration
↑ ↓
| dataSource defined in org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat
↑ ↓
| dataSourceInitializer
└─────┘
My Configuration:
#Configuration
public class MyDataSourceWrapperConfiguration {
#Primary
#Bean
DataSource databaseQueryCounterProxyDataSource(final DataSource delegate) {
return MyDataSourceWrapper(delegate);
}
}
My Test:
#ActiveProfiles({ "it" })
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration({ DatabaseConnectionConfiguration.class, DatabaseQueryCounterConfiguration.class })
#EnableAutoConfiguration
public class EngApplicationRepositoryIT {
#Rule
public MyDatabaseQueryCounter databaseQueryCounter = new MyDatabaseQueryCounter ();
#Rule
public ErrorCollector errorCollector = new ErrorCollector();
#Autowired
MyRepository repository;
#Test
public void test() {
this.repository.loadData();
this.errorCollector.checkThat(this.databaseQueryCounter.getSelectCounts(), is(lessThan(10)));
}
}
UPDATE: This original question was for springboot 1.5. The accepted answer reflects that, however, the answer from #rajadilipkolli works for springboot 2.x
In your case you will get 2 DataSource instances which is probably not what you want. Instead use BeanPostProcessor which is the component actually designed for this. See also the Spring Reference Guide.
Create and register a BeanPostProcessor which does the wrapping.
public class DataSourceWrapper implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof DataSource) {
return new MyDataSourceWrapper((DataSource)bean);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
Then just register that as a #Bean instead of your MyDataSourceWrapper.
Tip: Instead of rolling your own wrapping DataSource you might be interested in datasource-proxy combined with datasource-assert which has counter etc. support already (saves you maintaining your own components).
Starting from spring boot 2.0.0.M3 using BeanPostProcessor wont work.
As a work around create your own bean like below
#Bean
public DataSource customDataSource(DataSourceProperties properties) {
log.info("Inside Proxy Creation");
final HikariDataSource dataSource = (HikariDataSource) properties
.initializeDataSourceBuilder().type(HikariDataSource.class).build();
if (properties.getName() != null) {
dataSource.setPoolName(properties.getName());
}
return ProxyDataSourceBuilder.create(dataSource).countQuery().name("MyDS")
.logSlowQueryToSysOut(1, TimeUnit.MINUTES).build();
}
Another way is to use datasource-proxy version of datasource-decorator starter
Following solution works for me using Spring Boot 2.0.6.
It uses explicit binding instead of annotation #ConfigurationProperties(prefix = "spring.datasource.hikari").
#Configuration
public class DataSourceConfig {
private final Environment env;
#Autowired
public DataSourceConfig(Environment env) {
this.env = env;
}
#Primary
#Bean
public MyDataSourceWrapper primaryDataSource(DataSourceProperties properties) {
DataSource dataSource = properties.initializeDataSourceBuilder().build();
Binder binder = Binder.get(env);
binder.bind("spring.datasource.hikari", Bindable.ofInstance(dataSource).withExistingValue(dataSource));
return new MyDataSourceWrapper(dataSource);
}
}
You can actually still use BeanPostProcessor in Spring Boot 2, but it needs to return the correct type (the actual type of the declared Bean). To do this you need to create a proxy of the correct type which redirects DataSource methods to your interceptor and all the other methods to the original bean.
For example code see the Spring Boot issue and discussion at https://github.com/spring-projects/spring-boot/issues/12592.
I'm trying to implement custom transactional cache in a spring boot application. I've created my own implementation of AbstractPlatformTransactionManager and some unit tests, which show transactions are working as expected. However the real application ignores my transaction manager - it`s methods are never called. What I'm doing wrong? Thank you.
Transaction manager implementation:
#Component
public class CacheTransactionManager extends AbstractPlatformTransactionManager{
#Override
protected Object doGetTransaction() throws TransactionException {
...
}
...
}
Cache transaction configuration:
#Configuration
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class CacheTransactionConfiguration {
#Bean(name = "cacheTransactionManager")
public PlatformTransactionManager cacheTransactionManager() {
return new CacheTransactionManager();
}
}
Custom transactional annotation (I've tried also without this, but no difference):
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(value = "cacheTransactionManager", rollbackFor = Exception.class)
public #interface CacheTransactional {
}
Cache service:
#Component
public class CacheService {
#CacheTransactional
public void add(Object o){
...
}
}
Working JUnit test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestApplication.class)
#Configuration
#EntityScan(...)
#IntegrationTest
#TransactionConfiguration(defaultRollback = false)
public class CacheTransactionManagerTest {
#Autowired
private CacheService cacheService;
#Test
#CacheTransactional
public void transactionTest(){
cacheService.add(new Object());
}
}
Not working wicket application main class (ignores cacheTransactionManager):
#Configuration("MyApplication")
#EnableAutoConfiguration
#EntityScan(...)
#EnableJpaRepositories(...)
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
#ComponentScan(...)
#ImportResource({...})
public class MyApplication extends AuthenticatedWebApplication {
...
}
My env: Java 8, Spring Boot 1.2.1, Spring 4.1.4, Spring data JPA 1.7.2, Hibernate 4.3.7, Apache Tomcat 8.0.15, Wicket 6.17.0
I found out some new facts:
when I remove AdviceMode.ASPECTJ from #EnableTransactionManagement on CacheTransactionConfiguration, transactions begin to work, but propagation of transaction is ignored - nested call from one #CacheTransactional method to another #CacheTransactional methods always creates new transaction. Same behavior in JUnit test and real application.
when AdviceMode.ASPECTJ is on CacheTransactionConfiguration setted, but I remove #CacheTransactional annotation from junit test, transaction stops working also in juint (in test body is a #CacheTransaction method called, so there should be a transaction created).
application log contains this entry:
o.s.c.a.ConfigurationClassBeanDefinitionReader isOverriddenByExistingDefinition:290 - Skipping bean definition for [BeanMethod:name=cacheTransactionManager,declaringClass=x.y.z.CacheTransactionConfiguration]: a definition for bean 'cacheTransactionManager' already exists. This top-level bean definition is considered as an override.
So I can get this working, but without propagation...
For propagation, you need to tell Spring's #Transactional what propagation mode to apply. You can define several tx annotations, each inherit from #Transactional, but with a different propagation mode.