Application creates multiple CDI-Beans by mistake - java

i'm absolutely baffled. I have a standard, old school EJB-CDI-JSF-Hibernate JavaEE Application, running on a JBoss AS 7.1. Furthermore my structure is an absolut standard structure. I have JSF-Sites, which access my CDI Beans. The CDI Beans are holding a reference to one of my entities, the EJBs are used as DAOs. One of my beans is this CommissionController. It works absolutely fine. Only one instance is created for one user.
package controller;
import java.io.Serializable;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import lombok.Data;
import lombok.extern.java.Log;
import entities.Commission;
#SessionScoped
#Data
#Log
#Named
public class CommissionController implements Serializable {
private static final long serialVersionUID = -8452887234021054225L;
#Inject
private UserController userController;
#Inject
private CartController cartController;
private Commission commission;
#PostConstruct
public void init() {
commission = new Commission();
log.info("new CommissionController instance");
}
public void makeNewCommission() {
commission.setCart(cartController.getCart());
commission.setOrderDate(new Date());
commission.setOrderer(userController.getUser());
cartController.clearCart();
log.info(commission.toString());
}
}
Now i have a different bean, but it think it's the excact same structure:
package controller;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import lombok.Data;
import lombok.extern.java.Log;
import entities.Article;
import entities.Cart;
#SessionScoped
#Data
#Log
#Named
public class CartController implements Serializable {
private static final long serialVersionUID = 649140288918816488L;
#Inject
private UserController userController;
#Inject
private ArticleController articleController;
private Cart cart;
public void addToCart(Article article) {
cart.getArticleList().add(article);
cart.setSum(cart.getSum() + article.getPrice());
}
public void clearCart() {
cart.getArticleList().clear();
cart.setSum(0.0);
}
#PreDestroy
public void destroy() {
log.info("bean destroyed");
}
#PostConstruct
public void init() {
cart = new Cart();
log.info("new CartController instance");
}
public void removeFromCart(Article article) {
cart.getArticleList().remove(article);
cart.setSum(cart.getSum() - article.getPrice());
}
}
I can't explain it to myself, but the second bean is instanciated again and again and I can't store anything in it, because it get always a reference to another bean.
Please, help me to bring light in this mystery. If you need additional information, I would love to give it to you. Maybe, I have a big lack of understanding but for now, I can't help myself.

Gimby gave me the answer to this question. In fact, JBoss AS 7.1.0 Community Edition is outdated. With WildFly 8.0.0 Final, everything worked perfectly fine. So, if you're working with the fantastic possibilities of CDI, try WildFly instead of JBoss AS 7.1.0 Final Community.

Related

Spring Boot: Redis CRUD Repository findById or findAll always returns Optional.empty (null)

Hi Team,
I am using Spring Boot 2.3.12.RELEASE which internally uses Spring Data Redis 2.3.9.RELEASE as a managed dependency.
When I am trying to save an object to the Redis cache using Spring Boot CRUD repository, it is getting stored without any error and I can see the object stored via Redis Manager.
However, when I try to fetch the same object using the same id i.e. using findById() method of CRUD repository, I am unable to find it.
Moreover, when I try findAll() on the same CRUDRepository object I get Optional.empty result which is strange as findAll() should return all records present in the repository.
I have added the configuration, repository and model class codes and some screenshots below for your perusal.
Please Note: I know there are many similar questions asked on this platform related to this issue and also I tried the solutions mentioned on such questions, but that didn't work for me.
Any solutions for this issue will be really helpful.
Model Class:
package com.test.cache.entity;
import java.util.concurrent.TimeUnit;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.index.Indexed;
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
#RedisHash("OTPValidationLogCache")
public class OTPValidationLogCache {
#Id
#Indexed
private String id;
#Indexed
private int validationFailureCount;
#TimeToLive(unit = TimeUnit.MILLISECONDS)
private long expiry;
}
Repository:
package com.test.cache.repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.test.cache.entity.OTPValidationLogCache;
#Repository
public interface OTPValidationLogCacheRepository extends CrudRepository<OTPValidationLogCache, String> {
}
Redis Configuration Class:
package com.test.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import java.time.Duration;
#Configuration
#EnableRedisRepositories(basePackages = "com.test")
public class RedisConfig {
public static final long REDIS_CONNECT_TIMEOUT_SECS = 10L;
#Bean
public RedisStandaloneConfiguration redisStandaloneConfiguration() {
final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName("*******");
redisStandaloneConfiguration.setPort(6379);
redisStandaloneConfiguration.setPassword(RedisPassword.of("**********"));
//Credentials hidden for code sharing purpose.
return redisStandaloneConfiguration;
}
#Bean
public JedisConnectionFactory redisConnectionFactory() {
final JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
.connectTimeout(Duration.ofSeconds(REDIS_CONNECT_TIMEOUT_SECS))
.useSsl()
.build();
return new JedisConnectionFactory(redisStandaloneConfiguration(), jedisClientConfiguration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
return template;
}
}
Redis Manager Screenshot:
Eclipse IDE - Screenshot of Debugging Screen:
Well, I also raised a defect to spring-data-redis repository on GitHub for the same but the defect got closed by one of the maintainers of this repository without even posting any proper solution. He just gave a reference to an existing issue that was even closed without posting any solution. Here is the link to that issue.
https://github.com/spring-projects/spring-data-redis/issues/2130
Hence, while doing some research, I came across a solution that I am sharing here which worked in my case.
The solution is not to use the default CRUD repository methods implemented by Spring Boot, instead, write your own repository class having methods with your criteria to store and fetch the data from the Redis cache. That's it, now you should be able to store/fetch the data using the repository methods across your project.
I am posting an example below for reference.
Custom Repository Interface
package com.test.cache.repository;
import java.io.IOException;
import java.util.Map;
import com.test.cache.entity.OTPValidationLogCache;
public interface OTPValidationLogCacheRepository {
void save(OTPValidationLogCache customer);
OTPValidationLogCache find(Long id);
Map<?,?> findAll() throws IOException;
void update(OTPValidationLogCache customer);
void delete(Long id);
}
Custom Repository Interface Implementation
package com.test.cache.repository;
import java.io.IOException;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ScanOptions.ScanOptionsBuilder;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.test.cache.entity.OTPValidationLogCache;
import com.test.configuration.AppConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
#Repository
public class OTPValidationLogCacheRepositoryImpl implements OTPValidationLogCacheRepository {
private String key;
private RedisTemplate redisTemplate;
private HashOperations hashOperations;
private ObjectMapper objMapper;
#Autowired
public OTPValidationLogCacheRepositoryImpl(RedisTemplate redisTemplate, ObjectMapper objmapper) {
this.redisTemplate = redisTemplate;
this.objMapper = objmapper;
}
#PostConstruct
private void init() {
hashOperations = redisTemplate.opsForHash();
}
#Override
public void save(OTPValidationLogCache otpvalCache) {
hashOperations.put(key.concat(otpvalCache.getId().toString()), otpvalCache.getId(), otpvalCache);
setExpiryTime(key.concat(String.valueOf(otpvalCache.getId())), AppConfig.getUserBanDurationInSeconds());
}
#Override
public OTPValidationLogCache find(Long id) {
return (OTPValidationLogCache) hashOperations.get(key.concat(String.valueOf(id)), id);
}
#Override
public Map findAll() throws IOException {
Map<Integer, OTPValidationLogCache> values = Maps.newHashMap();
Cursor c = hashOperations.scan(OTPValidationLogCache.class, new ScanOptionsBuilder().match(key.concat("*")).build());
AtomicInteger count = new AtomicInteger(1);
c.forEachRemaining(element ->
{
values.put(count.getAndIncrement(), objMapper.convertValue(element, OTPValidationLogCache.class));
}
);
c.close();
return values;
}
#Override
public void update(OTPValidationLogCache customer) {
hashOperations.put(key, customer.getId(), customer);
}
#Override
public void delete(Long id) {
hashOperations.delete(key, id);
}
private void setExpiryTime(String key, Long timeout)
{
redisTemplate.expire(key, Duration.ofSeconds(timeout));
}
public synchronized void setKey(String key)
{
this.key = key;
}
}
Hope this helps others who may encounter this issue in the future.
Also, there is one more alternative available for this issue, that is switching to a different library provider such as Redisson, however, I have not tried it yet, so if you want, you may try and check.
You need to have the same package for your entities ,
I resolved the problem by extracting a lib and putting my entities there
You would find an explication here :
https://github.com/spring-projects/spring-data-redis/issues/2114

Jersey 3 - Configuring binding with bindFactory

Using Jersey 3.0.1, I am struggling to get binding working.
I have this binding module with the factories below:
public static class MyBinder extends AbstractBinder {
#Override
protected void configure() {
LOG.info("Attempting to configure binder");
bindFactory(DataSourceFactory.class).to(HikariDataSource.class).in(Singleton.class);
bindFactory(JooqConfigFactory.class).to(Configuration.class).in(Singleton.class);
bindFactory(DSLContextFactory.class).to(DSLContext.class).in(Singleton.class);
LOG.info("Configured binder");
}
}
public static class DataSourceFactory implements Supplier<HikariDataSource> {
#Override
public HikariDataSource get() {
...
return new HikariDataSource(config);
}
}
public static class JooqConfigFactory implements Supplier<Configuration> {
#Inject
HikariDataSource dataSource;
#Override
public Configuration get() {
...
return conf;
}
}
public static class DSLContextFactory implements Supplier<DSLContext> {
#Inject
Configuration config;
#Override
public DSLContext get() {
return DSL.using(config);
}
}
Then I have the setup for my Servlet using embedded Jetty:
public void start() throws Exception {
int port = appConfig.getProperty("http.port", 9998);
Server server = new Server(port);
ServletContextHandler ctx =
new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
ctx.setContextPath("/");
server.setHandler(ctx);
ResourceConfig config = new JerseyConfig();
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
servlet.setInitOrder(1);
ctx.addServlet(servlet, "/*");
server.start();
server.join();
}
public static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("com.sodonnell.jersey", "jersey.config.server.provider.packages");
register(new MyBinder());
}
}
And in my Rest service I simply try to inject a private instance variable:
public MyClass {
#Inject // javax.inject.Inject
private DSLContext dslContext;
}
However this dslContext is always null. I can see from the logs, that it prints the LOG.info("Configured binder"); message. However putting similar logs in my factory classes show they never get called.
Has anyone got any idea what I am missing?
EDIT
To make things simpler, I created this class:
public class SimpleClass {
private static Logger LOG = LoggerFactory.getLogger(SimpleClass.class);
public SimpleClass() {
LOG.info("Call the simple class constructor");
}
Changed my binder module:
import com.google.inject.Injector;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.Properties;
import java.util.function.Supplier;
...
// This is a nested class
public static class MyBinder extends AbstractBinder {
#Override
protected void configure() {
LOG.info("Attempting to configure binder");
bind(new SimpleClass()).to(SimpleClass.class);
}
}
Then attempted to inject just SimpleClass:
package com.sodonnell.hdfs3.rest;
import com.sodonnell.hdfs3.SimpleClass;
import com.zaxxer.hikari.HikariDataSource;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.HEAD;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import org.jooq.DSLContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
#Inject
private SimpleClass simpleClass;
...
But its still null, although I see both the log messages. There must be some fundamental setup I am missing.
Full cut down code with the SimpleClass example at:
github.com/sodonnel/jerseyBind
The answer is quite simple. You are using Jersey 3.0 which has switched to the new Jakarta naming. javax is thrown out the window - this includes javax.inject. All the javax package names have now been changed to jakarta. So to get the inject to work, the #Inject import should be
import jakarta.inject.Inject;
This change is part of the change of Java EE to Jakarta EE Starting from Jakarta EE 8 to Jakarta EE 9, all the namespacing has changed from javax to jakarta. So things like javax.servlet will now be jakarta.servlet. Weird, yes a huge breaking change with no backward compatibility.
In your case you have all the correct components to work with Jakarta (i.e. Jersey 3.0 and Jett 11), but you just need to make use of the new namespacing. Notice all the JAX-RS imports are now jakarta also.

How to retrieve the Application Context in Spring Boot 2

I have this ApplicationContextProvider class defined along with the MyApplication.java (entry point where the application is run):
package com.company.my.app;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getContext() {
return applicationContext;
}
}
Have the package restapi with two classes in it (Greeting is just a class to hold data):
package com.company.my.app.restapi;
import com.company.my.app.ApplicationContextProvider;
import io.micrometer.core.instrument.Counter;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
ApplicationContextProvider acp = new ApplicationContextProvider();
ApplicationContext context = acp.getContext();
if (context == null) LOG.info("app context is NULL");
Counter bean = context.getBean(Counter.class);
bean.increment();
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Finally the MyApplication class is:
package com.company.my.app;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.MeterBinder;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
public class MyApplication {
#Bean
public MeterBinder exampleMeterBinder() {
return (meterRegistry) -> Counter.builder("my.counter")
.description("my simple counter")
.register(meterRegistry);
}
#Configuration
public class CounterConfig {
#Bean
public Counter simpleCounter(MeterRegistry registry) {
return registry.counter("my.counter");
}
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
When I run the app and call http://localhost:8081/greeting in my browser, it crashes printing app context is NULL. How do I get the application context? I need it to retrieve the simple counter bean.
tl;dr: You don't need the context; there's a better way.
ApplicationContextAware is an artifact from much older versions of Spring, before many of the now-standard features were available. In modern Spring, if you need the ApplicationContext, just inject it like any other bean. However, you almost certainly shouldn't interact with it directly, especially for getBean, which should be replaced with injecting whatever you were getting.
In general, when you need a Spring bean, you should declare it as a constructor parameter. (If you have multiple constructors, you need to annotate one with #Autowired, but if there's only a single constructor, Spring is smart enough to know to use it.) If you're using Lombok, you can use #Value to automatically write the constructor, and Groovy and Kotlin have similar features.
In the specific case of Micrometer, which you're showing here, it is not conventional to declare individual metrics as beans because they are fine-grained tools intended to apply to specific code paths. (Some services might have 10 separate metrics to track various possible scenarios.) Instead, you inject the MeterRegistry and select the counters or other metrics that you need as part of your constructor. Here, your controller class should look like this. (I've eliminated the duplicate AtomicLong, but you could add it back in as you showed if there's a specific reason you need it.)
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final Counter counter;
public GreetingController(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("my.counter");
}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
counter.increment();
long count = (long) counter.count();
return new Greeting(count, String.format(template, name));
}
}

#Value Injection in Spring Boot 2.0.0 injects null

So. After reading the official docs and finding nothing wrong with what I am doing, I just ran out of ideas.
My application.properties:
vz.info.version=0.2.8
My properties Component
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
#Configuration
public class VZProperties {
#Value("${vz.info.version}")
#Getter
#Setter
private String apiVersion;
}
I am getting null all over the place for the apiVersion.
And Lombok does not seem to be the issue. What did I miss from the docs?
EDIT
I would like to call it like this:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Lists;
import lombok.Getter;
import java.io.Serializable;
import java.util.List;
public class VZNFCTagResponse implements Serializable{
private static final long serialVersionUID = -2824767225275894898L;
#Autowired
private VZProperties properties;
public VZNFCTagResponse(List<VZNFCTagAction> tagList){
this.tags = tagList;
}
/*...*/
#JsonProperty
public String apiVersion(){
return this.properties.getApiVersion();
}
}
And after having checked to get it running via injecting Environment, the property isn't there, either.
Your class VZNFCTagResponse is not registered as Spring Beans.
Annotate it as #Component and use DI for inject him.
The only way to avoid making it into a component and still benefit from DI is to mark it #Configurable and enable load-time weaving. Can't you just inject it into the component that creates VZNFCTagResponse and pass the version as a constructor parameter?

Bean injected in Quartz Job - NullPointerException

I have a problem, injecting a CDI Bean in a Quartz schedule Job. This is the Code of the Job-Class.
import javax.inject.Inject;
import lombok.extern.java.Log;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import com.example.controller.UserController;
#Log
public class SomethingNewScheduler implements Job {
#Inject
UserController userController;
#Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
userController.doSomething();
}
}
My UserController is defined as following:
import java.io.Serializable;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import lombok.extern.java.Log;
import de.example.dao.UserDao;
#Log
#ApplicationScoped
public class UserController implements Serializable {
private static final long serialVersionUID = 5922763136823308273L;
#EJB
private UserDao userDao;
public void example(){
userDao.getSomething();
}
}
When I try to access the userController in my SomethingNewScheduler, i get a NullPointerException. Implementing Serializable in SomethingNewScheduler didn't solve the problem.
I'm using Wildfly 8.2.0 for my Application. Accessing UserController in a RestService worked perfectly fine.
Has anyone a idea, why it doesn't work here?

Categories