Choosing a Bean by Profile and Name - spring-boot

I have an application where a FakeMailService will be injected if i am not on the Production environment. The configurarion is the following:
#Bean
#Profile("prod")
public MailService mailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#ConditionalOnMissingBean(MailService.class)
public MailService fakeMailService() {
return new FakeMailService();
}
Now I need to inject que real MailServiceImpl on some specific situations, not caring about the environment. I supposed that it could be achieved using qualified beans, like so:
#Bean
#Profile("prod")
public MailService mailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Qualifier("realMailService")
#ConditionalOnMissingBean(MailService.class)
public MailService forceRealMail(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Primary
#ConditionalOnMissingBean(MailService.class)
public MailService fakeMailService() {
return new FakeMailService();
}
And then:
class SomeService {
#Autorwired #Qualifier("realMailService")
MailService theRealMailService;
}
class OtherService {
#Autorwired
MailService fakeMailService;
}
But it seems that doing that the real MailService (MailserviceImpl) will be injected all the time, it does not matter if I am using the qualifier or not.
Am I missing something? Is there eny ther way to achieve that?

Solved it with:
#Bean
#Profile("prod")
public MailService mailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Qualifier("realMailService")
#Profile("!prod")
public MailService realMailService(JavaMailSender javaMailSender) {
return new MailServiceImpl(javaMailSender);
}
#Bean
#Primary
#Profile("!prod")
public MailService fakeMailService() {
return new FakeMailService();
}

Related

Spring boot: Two FactoryBean<RestTemplate> implementations

I've just created a FactoryBean implementation in order to request RestTemplate:
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
//init resttemplate headers
}
So, now I'm able to inject a RestTemplate at whichever class:
#Service
public class DocumentServiceBackOffice {
private RestTemplate restTemplate;
public DocumentServiceBackOffice(RestTemplate restTemplate) {//...}
}
However, I'd like to create another FactoryBean<RestTemplate> in order to initialize other parameters.
How could I create in order to inject one or other according to a qualifier?
Any ideas?
EDIT
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
private RestTemplate restTemplate;
private JWTService jwtService;
public RestTemplateFactory(JWTService jwtService) {
this.jwtService = jwtService;
}
public RestTemplate getObject() {
return this.restTemplate;
}
public Class<RestTemplate> getObjectType() {
return RestTemplate.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() {
this.restTemplate = new RestTemplate();
JWTHeaderRequestInterceptor jwtInterceptor = new JWTHeaderRequestInterceptor(this.jwtService);
this.restTemplate.setInterceptors(Arrays.asList(jwtInterceptor));
}
}
Instead of using a FactoryBean just use an #Bean annotated method which accepts a RestTemplateBuilder and use that to configure instances.
#Bean
#Primary
public RestTemplate fooRestTemplate(RestTemplateBuilder builder, JWTService jwtService) {
return builder.additionalInterceptors(Collections.singletonList(new JwtHeaderInterceptor(jwtService)).build();
}
#Bean
public RestTemplate barRestTemplate(RestTemplateBuilder builder {
return builder.build();
}
This will result in 2 available RestTemplate instances. The fooRestTemplate (marked as default due to #Primary) and barRestTemplate. To specify the specific one to use add an #Qualifier("barRestTemplate") to use the not default one.
public DocumentServiceBackOffice(#Qualifier("barRestTemplate") RestTemplate restTemplate) { ... }
Another way to do it would be defining a configuration with two RestTemplate beans with qualifiers.
#Configuration
public class Configuration {
#Bean
#Qualifier("firstRestTemplate")
public RestTemplate firstRestTemplate(){
// any building logic here
return new Resttemplate();
}
#Bean
#Qualifier("secondRestTemplate")
public RestTemplate secondRestTemplate(){
// any building logic here
return new Resttemplate();
}
}
Then, in your code, use the right #Qualifier when autowiring.
Setter injection example:
#Service
public class Service {
#Autowired
#Qualifier("firstRestTemplate")
private RestTemplate template;
// ...
}
Constructor injection example:
#Service
public class Service {
private RestTemplate template;
public Service(#Autowired #Qualifier("firstRestTemplate") RestTemplate template) {
this.template = template;
}
// ...
}

Spring JPA Custom Validator Bean Inyection

Any bean inyected into CustomValidator Implementation are always null. Im using Spring Boot 2, the applciation is a REST API, i'm not using MVC.
I have tried everything I have read about this with no luck so far.
this topic here for example did not work for me
Should I validate otherwise?, I've been stuck with this for 2 days already.
This is my Config class:
#Configuration
public class Beans {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:idiomas/idioma");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setDefaultLocale(new Locale("es"));
return resolver;
}
#Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource());
return validatorFactoryBean;
}
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(true);
filter.setAfterMessagePrefix("REQUEST: ");
return filter;
}
}
My CustomValidator looks like this:
public class AlmacenValidator implements ConstraintValidator {
#Autowired
private AlmacenService servicio;
#Autowired
private ApplicationContext contexto;
#Override
public void initialize(AlmacenValido constraintAnnotation) {
}
#Override
public boolean isValid(Almacen item, ConstraintValidatorContext context) {
//Database calls
}
JPA Entity:
#Entity
#AlmacenValido
#Table(name = "almacenes")
public class Almacen {
//Entity fields
}
The Annotation:
#Documented
#Retention(RUNTIME)
#Target({ TYPE, FIELD, ANNOTATION_TYPE, PARAMETER })
#Constraint(validatedBy = AlmacenValidator.class)
public #interface AlmacenValido {
String message() default "{validacion.almacen}";
Class[] groups() default {};
Class[] payload() default {};
}
I've created test example for you. Please check it https://github.com/alex-petrov81/stackoverflow-answers/tree/master/validation-bean-injection

Multiple LDAP repositories with Spring LDAP Repository

I would like to set more than one LDAP repositories with Spring LDAP. My aim is to create or update objects in all repositories at the same time.
I use LdapRepository Spring interface and I think that isn't possible for now.
I wonder if I can create my own LdapRepository extending the Spring one but I have no idea how to start.
This my configuration :
#Configuration
#EnableLdapRepositories("com.xxx.repository.ldap")
#PropertySource("classpath:ldap.properties")
public class LdapConfiguration {
#Autowired
Environment ldapProperties;
#Bean
public LdapContextSourceCustom contextSourceTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap.truststore"));
return ldapContextSource;
}
#Bean
public LdapTemplate ldapTemplate(){
return new LdapTemplate(contextSourceTarget());
}
}
And to be complete, one repository:
public interface LdapUserRepository extends LdapRepository<LdapUser> {
}
Any idea how to do it ?
Thanks in advance for any help.
1) It is possible specify more than one LDAP Repository configuration. Please see the following example. [Notice: This depends on spring-boot libraries]
#Configuration
#EnableLdapRepositories("com.xxx.repository.ldap")
#EnableConfigurationProperties(LdapProperties.class)
public class LdapConfiguration {
#Autowired
private Environment environment;
#Bean(name="contextSource1")
public LdapContextSource contextSourceTarget(LdapProperties ldapProperties) {
LdapContextSource source = new LdapContextSource();
source.setUserDn(this.properties.getUsername());
source.setPassword(this.properties.getPassword());
source.setBase(this.properties.getBase());
source.setUrls(this.properties.determineUrls(this.environment));
source.setBaseEnvironmentProperties(Collections.<String,Object>unmodifiableMap(this.properties.getBaseEnvironment()));
return source;
}
#Bean
public LdapTemplate ldapTemplate(#Qualifier("contextSource1") LdapContextSource contextSource){
return new LdapTemplate(contextSource);
}
}
You can use the spring.ldap prefix in application.properties to configure the above LdapConfiguration. You can see the available properties by checking out https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ldap/LdapProperties.java.
#Configuration
#EnableLdapRepositories(basePackages="com.yyy.repository.ldap", ldapTemplateRef="ldapTemplate2")
public class LdapConfiguration2 {
#Autowired
private Environment environment;
#Bean(name="ldapProperties2")
#ConfigurationProperties(prefix="spring.ldap2")
public LdapProperties ldapProperties() {
return new LdapProperties();
}
#Bean(name="contextSource2")
public LdapContextSource contextSourceTarget(#Qualifier("ldapProperties2") LdapProperties ldapProperties) {
LdapContextSource source = new LdapContextSource();
source.setUserDn(this.properties.getUsername());
source.setPassword(this.properties.getPassword());
source.setBase(this.properties.getBase());
source.setUrls(this.properties.determineUrls(this.environment));
source.setBaseEnvironmentProperties(Collections.<String,Object>unmodifiableMap(this.properties.getBaseEnvironment()));
return source;
}
#Bean(name="ldapTemplate2")
public LdapTemplate ldapTemplate(#Qualifier("contextSource2") LdapContextSource contextSource){
return new LdapTemplate(contextSource);
}
}
LdapConfiguration2 will be configured by the spring.ldap2 prefix in application.properties.
2) I don't think extending the Repository is the solution. I would recommend creating a #Service method that iterated through your repositories and applied the updates. I will provide two approaches below.
Example 1)
#Service
public class UpdateRepositories {
public void updateAllRepositories(LdapUserRepository userRepository1, LdapUserRepository userRepository2) {
// apply updates to userRepository1 and userRepository2
}
}
Example 2)
#Service
public class UpdateRepositories {
public void updateAllRepositories(ApplicationContext appContext) {
Map<String, LdapRepository> ldapRepositories = appContext.getBeansofType(LdapRepository.class)
// iterate through map and apply updates
}
}
I haven't compiled this code, so let me know if something is off or if you need additional guidance.
I don't known if I understood correctly but here is what we did:
Global configuration class
#Bean("odm")
public ObjectDirectoryMapper odm() {
return new DefaultObjectDirectoryMapper();
};
First LDAP configuration class
#Configuration
#PropertySource("classpath:ldap-one.properties")
public class LdapOneConfiguration {
#Autowired
Environment ldapProperties;
#Bean(name = "contextSourceOne")
public LdapContextSourceCustom contextSourceLdapOneTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap-one.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap-one.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-one.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap-one.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-one.truststore"));
return ldapContextSource;
}
#Bean(name = "ldapTemplateOne")
public LdapTemplate ldapOneTemplate(#Qualifier("contextSourceOne") LdapContextSourceCustom contextSource) {
return new LdapTemplate(contextSource);
}
#Bean(name = "ldapUserRepoOne")
public LdapUserRepository ldapUserRepositoryOne(#Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapUserRepository(ldapTemplate, odm);
}
#Bean(name = "ldapFamilyRepoOne")
public LdapFamilyRepository ldapFamilyRepositoryOne(#Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapFamilyRepository(ldapTemplate, odm);
}
}
Second LDAP configuration class
#Configuration
#PropertySource("classpath:ldap-two.properties")
public class LdapTwoConfiguration {
#Autowired
Environment ldapProperties;
#Bean(name = "contextSourceTwo")
public LdapContextSourceCustom contextSourceLdapTwoTarget() {
LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
ldapContextSource.setUrl(ldapProperties.getProperty("ldap-two.url"));
ldapContextSource.setBase(ldapProperties.getProperty("ldap-two.base"));
ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-two.userDn"));
ldapContextSource.setPassword(ldapProperties.getProperty("ldap-two.password"));
ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-two.truststore"));
return ldapContextSource;
}
#Bean(name = "ldapTemplateTwo")
public LdapTemplate ldapTwoTemplate(#Qualifier("contextSourceTwo") LdapContextSourceCustom contextSource) {
return new LdapTemplate(contextSource);
}
#Bean(name = "ldapUserRepoTwo")
public LdapUserRepository ldapUserRepositoryTwo(#Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapUserRepository(ldapTemplate, odm);
}
#Bean(name = "ldapFamilyRepoTwo")
public LdapFamilyRepository ldapFamilyRepositoryTwo(#Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
#Qualifier("odm") ObjectDirectoryMapper odm) {
return new LdapFamilyRepository(ldapTemplate, odm);
}
}
LdapUser repository
public class LdapUserRepository extends SimpleLdapRepository<LdapUser> {
public LdapUserRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
super(ldapOperations, odm, LdapUser.class);
}
}
LdapFamily repository
public class LdapFamilyRepository extends SimpleLdapRepository<LdapFamily> {
public LdapFamilyRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
super(ldapOperations, odm, LdapFamily.class);
}
}
LdapUser service (same for LdapFamily service)
#Service
public class LdapUserServiceImpl implements LdapUserService {
#Autowired
private ApplicationContext appContext;
private LdapUserRepository uniqueLdapUserRepo;
private List<LdapUserRepository> ldapUserRepoList;
#PostConstruct
private void setUniqueRepo() {
uniqueLdapUserRepo = appContext.getBeansOfType(LdapUserRepository.class).values().iterator().next();
ldapUserRepoList = new ArrayList<>(appContext.getBeansOfType(LdapUserRepository.class).values());
}
#Override
public LdapUser getUser(String uid) {
return uniqueLdapUserRepo.findOne(query().where("uid").is(uid));
}
#Override
public void saveUser(LdapUser user) {
for(LdapUserRepository repo: ldapUserRepoList){
repo.save(user);
}
}
}
We deleted the auto configuration of LDAP repo:
#EnableLdapRepositories(basePackages = "com.afklm.paul.repository.ldap", ldapTemplateRef = "ldapTwoTemplate")
Thanks ryan2049 for your help.
there is actually an easier way now:
create multiple configuration that is anotated with #EnableLdapRepositories with corresponding attributes
Create first configuration
#Configuration
#EnableLdapRepositories(basePackages = "first.ldap.package.repository.**", ldapTemplateRef = "firstLdapTemplate")
public class FirstLDAPConfig {
....detail
#Bean("firstLdapTemplate")
public LdapTemplate firstLdapTemplate() {
...template creation
}
}
Create second configuration
#Configuration
#EnableLdapRepositories(basePackages = "second.ldap.package.repository.**", ldapTemplateRef = "secondLdapTemplate")
public class SecondLDAPConfig {
....detail
#Bean("secondLdapTemplate")
public LdapTemplate secondLdapTemplate() {
...template creation
}
}
each configuration should handle it's own contextSource
then only the specified repository within the EnableLdapRepositories annotation will use that specific ContextSource and LdapTemplate

Implementing Snapshot in AXON 3.0 : Aggregate Type is unknown in this snapShotter

I'm still new to the axon frame work.
I'm trying to implement snapshotting using mongodb in my application and I keep on getting an error saying
"AbstractSnapshotter : An attempt to create and store a snapshot resulted in an exception. Exception summary: Aggregate Type is unknown in this snapshotter: com.myworklife.contacts.domain.contact.Contact"
This is a part of my java config file.
#Bean
public AggregateSnapshotter snapShotter(EventStore eventStore, AggregateFactory<Contact> contactAggregateFactory) {
return new AggregateSnapshotter(eventStore);
}
#Bean
public SnapshotTriggerDefinition snapshotTriggerDefinition(Snapshotter snapShotter) throws Exception {
return new EventCountSnapshotTriggerDefinition(snapShotter, 1);
}
#Bean
public EventStore eventStore(EventStorageEngine eventStorageEngine) {
return new EmbeddedEventStore(eventStorageEngine);
}
#Bean
public Repository<Contact> contactAggregateRepository(EventStore eventStore, SnapshotTriggerDefinition snapshotTriggerDefinition) {
return new ContactRepository(eventStore, snapshotTriggerDefinition);
}
And my repository.
#Repository("ContactRepository")
public class ContactRepository extends EventSourcingRepository<Contact> {
#Autowired
public ContactRepository(EventStore eventStore, SnapshotTriggerDefinition snapshotTriggerDefinition) {
super(Contact.class, eventStore, snapshotTriggerDefinition);
}
public Contact findContact(ContactId contactId) {
return load(contactId.toString()).getWrappedAggregate().getAggregateRoot();
}
}
My aggregate.
#Aggregate(repository="contactAggregateRepository")
public class Contact {
#AggregateIdentifier
private ContactId id;
private String name;
private String mobileNumber;
public Contact() {
// do nothing, Axon requires default constructor
}
#CommandHandler
public Contact(CreateContactCommand createContactCommand) {
apply(new ContactHasBeenCreatedEvent(createContactCommand.getContactId(), createContactCommand.getName(),
createContactCommand.getMobileNumber()));
}
}
Is there something I'm doing wrong?
since I'm getting an error saying "An attempt to create and store a snapshot resulted in an exception. Exception summary: Aggregate Type is unknown in this snapshotter: com.myworklife.contacts.domain.contact.Contact"
Any help will be highly appreciated.
Thanks,
Pat
You need to add the contactAggregateFactory to the constructor of the AggregateSnapshotter in the snapShotter bean:
#Bean
public AggregateSnapshotter snapShotter(EventStore eventStore, AggregateFactory<Contact> contactAggregateFactory) {
return new AggregateSnapshotter(eventStore, contactAggregateFactory);
}
1.depencity jar
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-autoconfigure</artifactId>
<version>${axon.version}</version>
</dependency>
2.At first,you should config your application.ymp or bootstrap.yml,like this:spring:
data:
mongodb:
host: localhost
port: 27017
database: axonframework
events: domainevents
snapshots: snapshotevents
3.config your mongoDB:
#Bean(name = "axonMongoTemplate")
public MongoTemplate axonMongoTemplate() {
MongoTemplate template = new DefaultMongoTemplate(mongoClient(), mongoDbName, eventsCollectionName, snapshotCollectionName);
return template;
}
#Bean
public MongoClient mongoClient(){
MongoFactory mongoFactory = new MongoFactory();
mongoFactory.setMongoAddresses(Arrays.asList(new ServerAddress(mongoHost)));
return mongoFactory.createMongo();
}
#Bean
public EventStorageEngine eventStorageEngine(Serializer serializer){
return new MongoEventStorageEngine(
serializer,null, axonMongoTemplate(), new DocumentPerEventStorageStrategy());
4.config repository for your aggregate,for example,i config a Element Aggreaget's repository
#Configuration
public class ElementConfiguration {
#Autowired
private EventStore eventStore;
#Bean
#Scope("prototype")
public Element elementAggregate() {
return new Element();
}
#Bean
public AggregateFactory<Element> elementAggregateAggregateFactory() {
SpringPrototypeAggregateFactory<Element> aggregateFactory = new SpringPrototypeAggregateFactory<>();
aggregateFactory.setPrototypeBeanName("elementAggregate");
return aggregateFactory;
}
#Bean
public SpringAggregateSnapshotterFactoryBean springAggregateSnapshotterFactoryBean(){
SpringAggregateSnapshotterFactoryBean factory = new SpringAggregateSnapshotterFactoryBean();
return factory;
}
#Bean
public Repository<Element> elementAggregateRepository(Snapshotter snapshotter) {
EventCountSnapshotTriggerDefinition eventCountSnapshotTriggerDefinition = new EventCountSnapshotTriggerDefinition(snapshotter, 3);
EventSourcingRepository<Element> repository = new EventSourcingRepository<Element>(
elementAggregateAggregateFactory(),
eventStore,
eventCountSnapshotTriggerDefinition
);
return repository;
}
5.enjoy
If you're using Axon 3.3 and SpringBoot 2, this is how we did it in this project:
#Configuration
public class SnapshotConfiguration {
#Bean
public SpringAggregateSnapshotter snapshotter(ParameterResolverFactory parameterResolverFactory,
EventStore eventStore,
TransactionManager transactionManager) {
// https://docs.axoniq.io/reference-guide/v/3.3/part-iii-infrastructure-components/repository-and-event-store#snapshotting
// (...) By default, snapshots are created in the thread that calls the scheduleSnapshot() method, which is generally not recommended for production (...)
Executor executor = Executors.newSingleThreadExecutor();
return new SpringAggregateSnapshotter(eventStore, parameterResolverFactory, executor, transactionManager);
}
#Bean
public EventCountSnapshotTriggerDefinition snapshotTrigger(SpringAggregateSnapshotter snapshotter) {
int snapshotThreshold = 42;
return new EventCountSnapshotTriggerDefinition(snapshotter, snapshotThreshold);
}
}
And if you need the EventStore configuration:
#Configuration
public class AxonMongoEventStoreConfiguration {
#Bean
public MongoClient axonMongoClient(MongoClientURI axonMongoClientUri) {
return new MongoClient(axonMongoClientUri);
}
#Bean
public MongoClientURI axonMongoClientUri() {
return new MongoClientURI("mongodb://host:port/database");
}
#Bean
#Primary
public EventStorageEngine eventStore(MongoClient axonMongoClient, MongoClientURI axonMongoClientUri) {
DefaultMongoTemplate mongoTemplate = new DefaultMongoTemplate(axonMongoClient, axonMongoClientUri.getDatabase());
return new MongoEventStorageEngine(mongoTemplate);
}
}

Cannot autowired beans when separate configuration classes

I have a JavaConfig configurated Spring Batch job. The main job configuration file is CrawlerJobConfiguration. Before now, I have all the configuration (infrastructure, autowired beans, etc) in this class and it works fine. So I decided to separate the job configuration from autowired beans and infracstruture beans configuration and create another 2 configuration classes Beans and MysqlInfrastructureConfiguration.
But now I am having problems to run my job. I'm receiving a NullPointerException when the application try to use autowired fields indicating that the autowired is not working.
I put a breakpoint in the methods that create autowired beans to make sure that they are being called and really are, so I cannot realize what can be the problem.
java.lang.NullPointerException: null
at br.com.alexpfx.supermarket.batch.tasklet.StartCrawlerTasklet.execute(StartCrawlerTasklet.java:27) ~[classes/:na]
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406) ~[spring-batch-core-3.0.6.RELEASE.jar:3.0.6.RELEASE]
Main job configuration class:
#Configuration
#EnableBatchProcessing
public class CrawlerJobConfiguration {
#Autowired
private InfrastructureConfiguration infrastructureConfiguration;
#Autowired
private StepBuilderFactory steps;
#Autowired
Environment environment;
#Bean
public Job job(JobBuilderFactory jobs) {
Job theJob = jobs.get("job").start(crawlerStep()).next(processProductStep()).build();
((AbstractJob) theJob).setRestartable(true);
return theJob;
}
#Bean
public Step crawlerStep() {
TaskletStep crawlerStep = steps.get("crawlerStep").tasklet(crawlerTasklet()).build();
crawlerStep.setAllowStartIfComplete(true);
return crawlerStep;
}
#Bean
public Step processProductStep() {
TaskletStep processProductStep = steps.get("processProductStep")
.<TransferObject, Product>chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
processProductStep.setAllowStartIfComplete(true);
return processProductStep;
}
private Tasklet crawlerTasklet() {
return new StartCrawlerTasklet();
}
private ItemProcessor<TransferObject, Product> processor() {
return new ProductProcessor();
}
private ItemReader<TransferObject> reader() {
return new ProductItemReader();
}
private ItemWriter<Product> writer() {
return new HibernateProductsItemWriter();
}
}
Beans configuration class:
#Configuration
#EnableBatchProcessing
public class Beans {
#Bean
public Crawler crawler() {
return new RibeiraoCrawler(new UserAgentFactory());
}
#Bean
public ProductBo productBo() {
return new ProductBoImpl();
}
#Bean
public ProductDao productDao() {
return new ProductDaoImpl();
}
#Bean
public CrawlerListener listener() {
CrawlerListener listener = new RibeiraoListener();
return listener;
}
#Bean
public ProductList getProductList() {
return new ProductList();
}
}
MysqlInfrastructureConfiguration:
#Configuration
#EnableBatchProcessing
#PropertySource("classpath:database.properties")
#EnableJpaRepositories(basePackages = {"br.com.alexpfx.supermarket.domain"})
public class MysqlInfrastructureConfiguration implements InfrastructureConfiguration {
#Value("${jdbc.url}")
String url;
#Value("${jdbc.driverClassName}")
String driverClassName;
#Value("${jdbc.username}")
String username;
#Value("${jdbc.password}")
String password;
#Bean
#Override
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
#Bean
#Override
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
transactionManager.setDataSource(getDataSource());
return transactionManager;
}
#Bean
#Override
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(getDataSource());
em.setPackagesToScan(new String[]{"br.com.alexpfx.supermarket.domain"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
em.afterPropertiesSet();
return em.getObject();
}
private Properties additionalJpaProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("current_session_context_class", "thread");
return properties;
}
}
tasklet:
public class StartCrawlerTasklet implements Tasklet {
#Autowired
private Crawler crawler;
#Autowired
private CrawlerListener listener;
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
crawler.setListener(listener);
crawler.setStopCondition(new TimeLimitStopCondition(1, TimeUnit.MINUTES));
crawler.crawl();
return RepeatStatus.FINISHED;
}
}
StartCrawlerTasklet use the autowired annotation, so it should be a bean as well. So change your code :
private Tasklet crawlerTasklet() {
return new StartCrawlerTasklet();
}
to a bean definition:
#Bean
public Tasklet crawlerTasklet() {
return new StartCrawlerTasklet();
}

Resources