How to use custom spring-boot-starter? - spring-boot

I have a custom starter that should measure the execution time of the method, if there is an annotation #SomeCustomAnnotation I use Aspect in this starter
I added this starter as a dependency in another project and specified an annotation on the method #SomeCustomAnnotation, but nothing works.please tell me how to solve
#Aspect
#Slf4j
public class LoggableAspect {
#Pointcut("#annotation(com.app.annotations.LogTime)")
public void executeTiming() {
}
#Around("executeTiming()")
public Object logMethodTiming(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object returnValue = proceedingJoinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.info("Total time is >>>>>>>>", totalTime);
return returnValue;
}
}
#Configuration
#ConditionalOnClass(LoggableAspect.class)
#EnableConfigurationProperties(LoggingProperties.class)
#RequiredArgsConstructor
public class LoggingServiceAutoConfiguration {
private final LoggingProperties properties;
#Bean
#ConditionalOnMissingBean
public LoggableAspect loggableAspect(){
return new LoggableAspect();
}
}

Related

#RefreshScope stops #Scheduled task

I have a monitoring app wherein I am running a fixedRate task. This is pulling in a config parameter configured with Consul. I want to pull in updated configuration, so I added #RefreshScope. But as soon as I update the config value on Consul, the fixedRate task stops running.
#Service
#RefreshScope
public class MonitorService {
#Autowired
private AppConfig appConfig;
#PostConstruct
public void postConstRun() {
System.out.println(appConfig.getMonitorConfig());
}
#Scheduled(fixedRate = 1000)
public void scheduledMonitorScan() {
System.out.println("MonitorConfig:" + appConfig.getMonitorConfig());
}
}
AppConfig class just has a single String parameter:
#Configuration
#Getter
#Setter
public class AppConfig {
#Value("${monitor-config:default value}")
private String monitorConfig;
}
As soon as I update the value in consul, the scheduled task just stops running (display in sheduledMonitorScan method) stop showing up.
I'm successfully get & override the values from consul config server using RefreshScopeRefreshedEvent
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
#RefreshScope
public class AlertSchedulerCron implements ApplicationListener<RefreshScopeRefreshedEvent> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
#Value("${pollingtime}")
private String pollingtime;
/*
* #Value("${interval}") private String interval;
*/
#Scheduled(cron = "${pollingtime}")
//#Scheduled(fixedRateString = "${interval}" )
public void task() {
System.out.println(pollingtime);
System.out.println("Scheduler (cron expression) task with duration : " + sdf.format(new Date()));
}
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// TODO Auto-generated method stub
}
}
Here's how we've solved this issue.
/**
* Listener of Spring's lifecycle to revive Scheduler beans, when spring's
* scope is refreshed.
* <p>
* Spring is able to restart beans, when we change their properties. Such a
* beans marked with RefreshScope annotation. To make it work, spring creates
* <b>lazy</b> proxies and push them instead of real object. The issue with
* scope refresh is that right after refresh in order for such a lazy proxy
* to be actually instantiated again someone has to call for any method of it.
* <p>
* It creates a tricky case with Schedulers, because there is no bean, which
* directly call anything on any Scheduler. Scheduler lifecycle is to start
* few threads upon instantiation and schedule tasks. No other bean needs
* anything from them.
* <p>
* To overcome this, we had to create artificial method on Schedulers and call
* them, when there is a scope refresh event. This actually instantiates.
*/
#RequiredArgsConstructor
public class RefreshScopeListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
private final List<RefreshScheduler> refreshSchedulers;
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
refreshSchedulers.forEach(RefreshScheduler::materializeAfterRefresh);
}
}
So, we've defined an interface, which does nothing in particular, but allows us to call for a refreshed job.
public interface RefreshScheduler {
/**
* Used after refresh context for scheduler bean initialization
*/
default void materializeAfterRefresh() {
}
}
And here is actual job, whose parameter from.properties can be refreshed.
public class AJob implements RefreshScheduler {
#Scheduled(cron = "${from.properties}")
public void aTask() {
// do something useful
}
}
UPDATED:
Of course AJob bean must be marked with #RefreshScope in #Configuration
#Configuration
#EnableScheduling
public class SchedulingConfiguration {
#Bean
#RefreshScope
public AJob aJob() {
return new AJob();
}
}
I have done workaround for this kind of scenario by implementing SchedulingConfigurer interface.
Here I am dynamically updating "scheduler.interval" property from external property file and scheduler is working fine even after actuator refresh as I am not using #RefreshScope anymore.
Hope this might help you in your case also.
public class MySchedulerImpl implements SchedulingConfigurer {
#Autowired
private Environment env;
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(this.taskExecutor());
taskRegistrar.addTriggerTask(() -> {
//put your code here that to be scheduled
}, triggerContext -> {
final Calendar nextExecutionTime = new GregorianCalendar();
final Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
if (lastActualExecutionTime == null) {
nextExecutionTime.setTime(new Date());
} else {
nextExecutionTime.setTime(lastActualExecutionTime);
nextExecutionTime.add(Calendar.MILLISECOND, env.getProperty("scheduler.interval", Integer.class));
}
return nextExecutionTime.getTime();
});
}
}
My solution consists of listening to EnvironmentChangeEvent
#Configuration
public class SchedulingSpringConfig implements ApplicationListener<EnvironmentChangeEvent>, SchedulingConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingSpringConfig.class);
private final DemoProperties demoProperties;
public SchedulingSpringConfig(DemoProperties demoProperties) {
this.demoProperties = demoProperties;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
LOGGER.info("Configuring scheduled task with cron expression: {}", demoProperties.getCronExpression());
taskRegistrar.addTriggerTask(triggerTask());
taskRegistrar.setTaskScheduler(taskScheduler());
}
#Bean
public TriggerTask triggerTask() {
return new TriggerTask(this::work, cronTrigger());
}
private void work() {
LOGGER.info("Doing work!");
}
#Bean
#RefreshScope
public CronTrigger cronTrigger() {
return new CronTrigger(demoProperties.getCronExpression());
}
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
#Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (event.getKeys().contains("demo.config.cronExpression")) {
ScheduledTasksRefresher scheduledTasksRefresher = new ScheduledTasksRefresher(triggerTask());
scheduledTasksRefresher.afterPropertiesSet();
}
}
}
Then I use the ContextLifecycleScheduledTaskRegistrar to recreate the task.
public class ScheduledTasksRefresher extends ContextLifecycleScheduledTaskRegistrar {
private final TriggerTask triggerTask;
ScheduledTasksRefresher(TriggerTask triggerTask) {
this.triggerTask = triggerTask;
}
#Override
public void afterPropertiesSet() {
super.destroy();
super.addTriggerTask(triggerTask);
super.afterSingletonsInstantiated();
}
}
Properties definition:
#ConfigurationProperties(prefix = "demo.config", ignoreUnknownFields = false)
public class DemoProperties {
private String cronExpression;
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
}
Main definition:
#SpringBootApplication
#EnableConfigurationProperties(DemoProperties.class)
#EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Based on previous answers I added the following interface and used it on #RefreshScope annotated beans:
public interface RefreshScopeScheduled {
#EventListener(RefreshScopeRefreshedEvent.class)
default void onApplicationEvent() { /*do nothing*/ }
}

Spring Aspect on Converter

I created simple Aspect an annotation for measuring time of execution of annotated method.
When I annotate method of a simple Spring Bean, inject bean, and run it like bean.annotatedMethod(), everything works fine.
However, when I annotate convert() method on Spring Converter, annotation is ignored. I'm guessing the reason is that convert() is called internally by Spring's ConversionService, and somehow Aspects are not respected. Is there any way to get it to work?
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LogExecTime {
}
Aspect, which I register in Spring:
#Aspect
#Component
public class LogTimeAspect {
#Around(value = "#annotation(annotation)")
public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable {
final long startMillis = System.currentTimeMillis();
try {
System.out.println("Starting timed operation");
final Object retVal = joinPoint.proceed();
return retVal;
} finally {
final long duration = System.currentTimeMillis() - startMillis;
System.out.println("Call to " + joinPoint.getSignature() + " took " + duration + " ms");
}
}
}
This works fine:
#Component
public class Operator {
#LogExecTime
public void operate() throws InterruptedException {
System.out.println("Performing operation");
Thread.sleep(1000);
}
}
#Bean
protected Void test(Operator o) {
o.operate();
return null;
}
But here, annotation is ignored:
public class SampleConverter implements Converter<SourceType, ResultType> {
#Override
#LogExecTime
public ImmutableData convert(#Nonnull ClassifiedEvent result) {
...
}
}
ConversionService conversionService;
...
conversionService.convert(source, ResultType.class));
Solved by comment of #EssexBoy, my converter was not a spring managed bean.

Testing Spring Boot Cache(Caffeine)

I have my cache config as below;
#Configuration
public class CacheConfiguration {
#Bean
public CacheManager cacheManager(Ticker ticker) {
CaffeineCache bookCache = buildCache("books", ticker, 30);
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Collections.singletonList(bookCache));
return cacheManager;
}
private CaffeineCache buildCache(String name, Ticker ticker, int minutesToExpire) {
return new CaffeineCache(name, Caffeine.newBuilder()
.expireAfterWrite(minutesToExpire, TimeUnit.MINUTES)
.maximumSize(100)
.ticker(ticker)
.build());
}
#Bean
public Ticker ticker() {
return Ticker.systemTicker();
}
}
And the service I want to test:
#Service
public class TestServiceImpl implements TestService {
private final BookRepository bookRepository; // interface
#Autowired
public TestServiceImpl(final BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
#Override
public Book getByIsbn(String isbn) {
return bookRepository.getByIsbn(isbn);
}
}
The required method in repository is annotated with #Cacheable("books").
#Override
#Cacheable("books")
public Book getByIsbn(String isbn) {
LOGGER.info("Fetching Book...");
simulateSlowService(); // Wait for 5 secs
return new Book(isbn, "Some book");
}
I need to write a test showing the caching works. So I created another ticker bean in test to override the one existing in CacheConfiguration. The code;
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestServiceTests {
private static final String BOOK_ISBN = "isbn-8442";
#SpyBean
private BookRepository bookRepository;
#Autowired
private TestService testService;
#Configuration
#Import(SpringBootCacheApplication.class)
public static class TestConfiguration {
//testCompile('com.google.guava:guava-testlib:23.6-jre')
static FakeTicker fakeTicker = new FakeTicker();
#Bean
public Ticker ticker() {
return fakeTicker::read;
}
}
#Before
public void setUp() {
Book book = fakeBook();
doReturn(book)
.when(bookRepository)
.getByIsbn(BOOK_ISBN);
}
private Book fakeBook() {
return new Book(BOOK_ISBN, "Mock Book");
}
#Test
public void shouldUseCache() {
// Start At 0 Minutes
testService.getByIsbn(BOOK_ISBN);
verify(bookRepository, times(1)).getByIsbn(BOOK_ISBN);
// After 5 minutes from start, it should use cached object
TestConfiguration.fakeTicker.advance(5, TimeUnit.MINUTES);
testService.getByIsbn(BOOK_ISBN);
verify(bookRepository, times(1)).getByIsbn(BOOK_ISBN); // FAILS HERE
// After 35 Minutes from start, it should call the method again
TestConfiguration.fakeTicker.advance(30, TimeUnit.MINUTES);
testService.getByIsbn(BOOK_ISBN);
verify(bookRepository, times(2)).getByIsbn(BOOK_ISBN);
}
}
But it fails at the line marked with //FAILS HERE with message;
org.mockito.exceptions.verification.TooManyActualInvocations:
simpleBookRepository.getByIsbn("isbn-8442");
Wanted 1 time:
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
But was 2 times. Undesired invocation:
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)`
Why it fails? Shouldn't it use cache? Or my test is wrong?
Any help or pointers are greatly appreciated! :)
verify(bookRepository, times(1)).getByIsbn(BOOK_ISBN); // FAILS HERE
Ofcourse it fails here. because ~4 lines before you already called one times this method. In this check you should put times(2). And on the next checking number of invocations should be times(3)

JPA #EntityListener does not work as expected

I am integrating Spring4 and Hibernate5, but there is a problem that I can't resolve.
I use #EntityListener annotation on the BaseEntity class that is a super class for other business model.
Also I use #MappedSuperclass on the BaseEntity.
But it don't work!
Use Spring base annotation and run application successfully.
Also I inserted a record to db.
So I think my configuration of project is current.
Any body let me know why?
Thanks very much.
This is BaseEntity class.
#MappedSuperclass
#EntityListeners(EntityListener.class)
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, updatable = false)
private Date createDate;
#Column(nullable = false)
private Date modifyDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
This is EntityListener class.
public class EntityListener {
#PrePersist
public void prePersist(BaseEntity entity) {
entity.setCreateDate(new Date());
entity.setModifyDate(new Date());
}
#PreUpdate
public void preUpdate(BaseEntity entity) {
entity.setModifyDate(new Date());
}
}
The following is my project configuration base on Spring annotation.
#Configuration
#EnableWebMvc
//#ImportResource({ "classpath:xxxxx.xml" })
#PropertySources({
#PropertySource("classpath:application.properties")
})
#ComponentScan({"com.yeager.admin.persistence","com.yeager.admin.web","com.yeager.admin.service","com.yeager.admin.common"})
#EnableAspectJAutoProxy
//#EnableRetry
public class AppConfig {
#Bean(name = "multipartResolver")
public CommonsMultipartResolver getResolver() throws IOException {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
return resolver;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public static SpringContext springContext() {
return new SpringContext();
}
}
The main configuration about DAL like this,
#Configuration
#EnableTransactionManagement
#PropertySource({"classpath:persistence-mysql.properties"})
public class PersistenceConfig {
#Autowired
private Environment env;
public PersistenceConfig() {
super();
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.yeager.admin.persistence.entity");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
try {
comboPooledDataSource.setDriverClass(env.getProperty("jdbc.driver"));
} catch (PropertyVetoException e) {
e.printStackTrace();
}
comboPooledDataSource.setJdbcUrl(env.getProperty("jdbc.url"));
comboPooledDataSource.setUser(env.getProperty("jdbc.username"));
comboPooledDataSource.setPassword(env.getProperty("jdbc.password"));
comboPooledDataSource.setInitialPoolSize(Integer.valueOf(env.getProperty("datasource.pool.initialPoolSize")));
return comboPooledDataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private final Properties hibernateProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.generate_statistics",env.getProperty("hibernate.generate_statistics"));
hibernateProperties.setProperty("hibernate.jdbc.fetch_size", env.getProperty("hibernate.jdbc.fetch_size"));
hibernateProperties.setProperty("hibernate.jdbc.batch_size", env.getProperty("hibernate.jdbc.batch_size"));
hibernateProperties.setProperty("hibernate.max_fetch_depth", env.getProperty("hibernate.max_fetch_depth"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache",env.getProperty("hibernate.cache.use_second_level_cache"));
hibernateProperties.setProperty("hibernate.cache.use_query_cache",env.getProperty("hibernate.cache.use_query_cache"));
// hibernateProperties.setProperty("hibernate.cache.provider_class",env.getProperty("hibernate.cache.provider_class"));
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");
return hibernateProperties;
}
}
I use LocalSessionFactoryBean class of Hibernate rather than EntityManager class of JPA. I wonder if this cause ?
--------------- 6.19 --------------
I am wrong. I don't should use #EntityListener annotation base on Spring LocalSessionFactoryBean class.
For hibernate5, there is a special configuration way.
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
Now, I modify my code as following,
#Component
public class EntityEventListener {
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
public void registerListeners(){
EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.PRE_INSERT, PreInsertEntityListener.class);
}
}
PreInsertEntityListener
public class PreInsertEntityListener implements PreInsertEventListener {
#Override
public boolean onPreInsert(PreInsertEvent event) {
// if (event.getEntity() instanceof AdminUser){
// ((AdminUser) event.getEntity()).setCreateDate(new Date());
// ((AdminUser) event.getEntity()).setModifyDate(new Date());
// }
BaseEntity baseEntity = (BaseEntity) event.getEntity();
baseEntity.setCreateDate(new Date());
baseEntity.setModifyDate(new Date());
return false;
}
}
But, I have a other problem.
I read hibernate doc and search many information about this. My code don't work already when I insert entity data.
Please help me, thanks!
Although you did neither post the concrete / derived entity nor the business code to persist it, the code you posted seems correct.
For giving it a small test I added a generated UID to the super class and created a concrete entity:
import javax.persistence.Entity;
#Entity
public class DerivedEntity extends BaseEntity {
private static final long serialVersionUID = -6441043639437893962L;
}
And since you mentioned Spring, here is a Spring Data JPA repository to save it:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface DerivedEntityRepository extends CrudRepository<DerivedEntity, Long> {
}
This small test should show that the (#PrePersist) listener works:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
#RunWith(SpringRunner.class)
#Transactional
#SpringBootTest
public class DerivedEntityRepositoryTests {
#Autowired
private DerivedEntityRepository derivedEntityRepository;
#Test
public void insertDerivedEntity() {
DerivedEntity entity = new DerivedEntity();
entity = derivedEntityRepository.save(entity);
assertThat(entity.getCreateDate()).isNotNull();
}
}
And just to mention it, if you don't want to enhance your custom listener in future, the existing Spring Data JPA AuditingEntityListener does exactly what you are doing at the moment (and even more). In this case you could just enhance a #Configuration class with #EnableJpaAuditing and modify your BaseEntity as following:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
// ...
#CreatedDate
#Column(nullable = false, updatable = false)
private Date createDate;
#LastModifiedDate
#Column(nullable = false)
private Date modifyDate;
// ...
}
That would make your custom EntityListener dispensable.
Just take a look Spring JPA Auditing for more information. If you want to enhance auditing with Hibernate, try Hibernate Envers.
I ran into this same issue and in my case the listener defined with #EntityListeners was referring to class (not in the same classloader) in another package and it wasn't being scanned. After adding the class to my persistence context it began working as expected.
So always be sure that any classes related to the persistence are added to the persistence context.
Thanks very much for everyone. I have resolved this problem.
I will share my solution, hope it's helpful for you if you are doing same things.
First, my starting point is wrong. Because I use JPA before, so I use acquiescently #EntityListener annotation when I integrate Spring4 and Hibernate5.
Then, I read Hibernate doc and many relevant article and found there is a new way to implement entity listener. See hibernate doc
Finally, my solution is following.
This is my BaseEntity class.
#MappedSuperclass
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, updatable = false)
private Date createDate;
#Column(nullable = false)
private Date modifyDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
First of all, you need to define EntityListener class.
public class EntityListener implements PreInsertEventListener, PreUpdateEventListener {
private static final String CREATE_DATE_PROPERTY = "createDate";
private static final String MODIFY_DATE_PROPERTY = "modifyDate";
#Override
public boolean onPreInsert(PreInsertEvent event) {
if (event.getEntity() instanceof BaseEntity){
//property name of entity
String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
//property value of entity
Object[] state = event.getState();
for (int i = 0; i < propertyNames.length ; i ++) {
if (CREATE_DATE_PROPERTY.equals(propertyNames[i]) || MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
state[i] = new Date();
}
}
}
return false;
}
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
if (event.getEntity() instanceof BaseEntity){
//property name of entity
String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
//property value of entity
Object[] state = event.getState();
for (int i = 0; i < propertyNames.length ; i ++) {
if (MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
state[i] = new Date();
}
}
}
return false;
}
}
Last, you should register entity event listener.
#SuppressWarnings("unchecked")
#Component
public class EntityEventListenerRegistry {
#Autowired
private SessionFactory sessionFactory;
/**
* EventListenerRegistry:http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
*/
#PostConstruct
public void registerListeners(){
EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.PRE_INSERT, EntityListener.class);
eventListenerRegistry.prependListeners(EventType.PRE_UPDATE, EntityListener.class);
}
}

How does Spring beans work with Prototype scope?

I have following spring bean with Prototype scope. In the AppRunner class, I want a new bean to injected by spring within the for loop (if loop count is 2, then i want only 2 new beans to be injected).
But spring injects a new bean every time the setter methods of the SimpleBean is called.
SimpleBean.java
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode =
ScopedProxyMode.TARGET_CLASS)
public class SimpleBean {
private String id;
private Long value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
AppRunner.java
#Component
public class AppRunner {
#Autowired
SimpleBean simpleBean;
public void execute(List<Output> results){
List<SimpleBean> finalResults = new ArrayList<SimpleBean>();
for(Output o : results){
simpleBean.setId(o.getAppId());
simpleBean.setValue(o.getAppVal());
finalResults.add(simpleBean);
}
}
}
Output.java
public class Output {
private String appId;
private Long appVal;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Long getAppVal() {
return appVal;
}
public void setAppVal(Long appVal) {
this.appVal = appVal;
}
}
Unfortunately prototype scope doesn't work like this. When your AppRunner bean is instantiated by the container it is asking for its dependencies. Then a new instance of SimpleBean is created. This instance stays as dependency. Prototype scope starts working when you will have multiple beans with dependency on SimpleBean. Like:
#Component
class BeanOne {
#Autowired
SimpleBean bean; //will have its own instance
}
#Component
class BeanTwo {
#Autowired
SimpleBean bean; //another instance
}
There is one rather straightforward update which can lead to your desired behaviour. You can remove autowired dependency and ask for a new dependency in your loop from context. It would look like this.
#Component
public class AppRunner {
#Autowired
ApplicationContext context;
public void execute(List<Output> results){
List<SimpleBean> finalResults = new ArrayList<SimpleBean>();
for(Output o : results) {
SimpleBean simpleBean = context.getBean(SimpleBean.class);
simpleBean.setId(o.getAppId());
simpleBean.setValue(o.getAppVal());
finalResults.add(simpleBean);
}
}
}
Other option could be technique called Method injection. It is described in the relevant documentation for prototype scope. You can take a look here 7.5.3 Singleton beans with prototype-bean dependencies

Resources