I have problem autowiring session scoped bean into an Aspect.
My aspect looks like this:
#Aspect
public class AuditAspect {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private AuditService auditService;
#Autowired
private SessionData sessionData;
#AfterReturning(value = "#annotation(fasthiAudit) && execution(* *(..))")
public void audit(JoinPoint joinPoint, FasthiAudit fasthiAudit) {
final String className = joinPoint.getTarget().getClass().getName();
final String methodName = joinPoint.getSignature().getName();
try {
UserId userId = sessionData.getUserId();
TenantId tenantId = sessionData.getTenantId();
} catch (Exception e) {
logger.error("Could not log audit entry for method name: " + methodName + " in class " + className, e);
}
}
}
My SessionData bean is session scoped and looks like this:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionData {
private UserId userId;
private TenantId tenantId;
public UserId getUserId() {
return userId;
}
public void setUserId(UserId userId) {
this.userId = userId;
}
public TenantId getTenantId() {
return tenantId;
}
public void setTenantId(TenantId tenantId) {
this.tenantId = tenantId;
}
}
In the aspect, the AuditService is autowired in okay and the SessionData is not null but it throws an Exception like
Method threw 'org.springframework.beans.factory.BeanCreationException' exception. Cannot evaluate se.reforce.fasthi.core.infrastructure.SessionData$$EnhancerBySpringCGLIB$$26c0d5bb.toString()
I have added a ContextLoaderListener to expose the request like this:
event.getServletContext().addListener(new RequestContextListener());
It works fine to autowire in the SessionData bean as a proxy in other singelton beans but the problem occurs in the aspect
What am I missing?
Thank you
/Johan
I found the problem after a few days of headache. The problem was my Vaadin integration (that I forgot to mention in my question). The #Push annotation on my vaadin UI did something confusing with the servlet so that spring didn't recognize the session scoped beans. I solved this my changing the annotation to:
#Push(transport= Transport.WEBSOCKET_XHR)
That was it, now the session scoped beans work perfectly together with the singelton beans
Related
I am using Hibernate #Filter with Spring Data to add specific "where" clause for every query in my project. The problem is that it works as long as I use #Transactional annotation for my 'findAll' method. Is there any way to avoid using #Transactional? Why is it important here?
Here is the filter config:
#FilterDef(name = "testFilter",
parameters = #ParamDef(name = "company_id", type = "long"),
defaultCondition = "company_id=:companyId")
#Filter(name = "testFilter")
#EntityListeners(EmployeeListener.class)
#NoArgsConstructor
public class Employee {//basic fields}
Repository:
#Repository
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, UUID> {
}
Aspect that enables the filter:
#Aspect
#Component
public class TestAspect {
private final SecurityAspect securityAspect;
private final Logger logger;
#PersistenceContext
private EntityManager entityManager;
#Autowired
public TestAspect(SecurityAspect securityAspect, Logger logger) {
this.securityAspect = securityAspect;
this.logger = logger;
}
#Around("execution(* org.springframework.data.repository.CrudRepository+.findAll(..))")
public Object aroundFindAllTenantAware(ProceedingJoinPoint joinPoint) {
Session session = this.entityManager.unwrap(Session.class);
session.enableFilter("testFilter")
.setParameter("companyId", this.securityAspect.getCompanyId());
Object retValue = null;
try {
retValue = joinPoint.proceed();
} catch (Throwable throwable) {
logger.error(throwable.getMessage(), throwable);
} finally {
session.disableFilter("testFilter");
}
return retValue;
}
}
And, finally, my service:
#Service
#Transactional
public class EmployeeApiServiceImpl {
private final EmployeeRepository repository;
#Autowired
public EmployeeApiServiceImpl(EmployeeRepository repository) {
this.employeeRepository = employeeRepository; }
#Override
public Response listProfiles(SecurityContext securityContext) {
List<Employee> employees = repository.findAll();
return Response.ok().entity(employees).build();
}
Without #Transactional annotation, it does not work. When I am debugging the aspect I can see that the filter was enabled but the query did not change. When I put the annotation, everything works fine. But I don't get why it is happening. This annotation is not supposed to be on read methods, plus in every tutorial everything works without it, but not in my case.
After some research I found a solution - using TransactionTemplate class. So I just need to put my code from aspect into method 'execute' and it will do the same as #Transactional annotation. But in this case, I don't have to annotate each method in each service as Transactional. These articles helped me a lot:
https://www.baeldung.com/spring-programmatic-transaction-management
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring
transactionTemplate.execute(status -> {
Session session = null;
Object result = null;
try {
session = entityManager.unwrap(Session.class);
session.enableFilter(Constants.FILTER_NAME)
.setParameter(Constants.PARAMETER_NAME, this.securityAspect.getCompanyId());
result = joinPoint.proceed();
} catch (Throwable throwable) {
logger.error(throwable.getMessage(), throwable);
} finally {
if (session != null) {
session.disableFilter(Constants.FILTER_NAME);
}
}
return result;
});
The problem is the EntityManager proxy that is injected by Spring which only supports certain operations without a transaction. You'll need a custom proxy implementation if you want support for this. See org.springframework.orm.jpa.SharedEntityManagerCreator.SharedEntityManagerInvocationHandler#invoke
I successfully autowired beans and sent emails in my previous code. The code is as follow:
public class HomeController{
#Autowired
private MailConstructor mailConstructor;
#Autowired
private JavaMailSender mailSender;
...
String token = UUID.randomUUID().toString();
String appUrl = "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
SimpleMailMessage newEmail = mailConstructor.constructResetTokenEmail(appUrl, request.getLocale(), token, user);
mailSender.send(newEmail);
...
}
Now I want to send the email in a new thread and I wrote my code referring to this link:Why does Autowiring not function in a thread?
My new code is as follow:
public class EmailUtility {
#Autowired
private JavaMailSender mailSender;
public static SimpleMailMessage constructNormalEmail(String text, String subject, String toAddress, String fromAddr) {
SimpleMailMessage email = new SimpleMailMessage();
email.setTo(toAddress);
email.setSubject(subject);
email.setText(text);
email.setFrom(fromAddr);
return email;
}
public class SendEmail implements Runnable{
private String subject;
private String text;
private String toAddr;
private String fromAddr;
public SendEmail(String subject, String text, String toAddr, String fromAddr){
this.subject = subject;
this.text = text;
this.toAddr = toAddr;
this.fromAddr = fromAddr;
}
#Override
public void run() {
SimpleMailMessage email = constructNormalEmail(text, subject, toAddr, fromAddr);
mailSender.send(email);
}
}
}
But I got the following error:
exception in thread "Thread-5" java.lang.NullPointerException
Both this error and the message presented by IntelliJ Could not autowire. No beans of 'JavaMailSender' type found indicates that the autowire fails.
What may be the cause of this error and how can I autowire a bean in a thread? Any suggestion will be appreciated.
=============================UPDATE=====================================
Solved this problem with the help of these guys. The problem is that I create the EmailUtility myself.
MY previous code is as follow:
//Wrong Version
Thread sendEmailThread = new Thread(new EmailUtility().new SendEmail(subject, text, toAddress, fromAddress));
If I let Spring autowires the EmailUtility, then the code works:
//Correct Version
#Component
public class EmailUtility {
......
}
public class HomeController{
#Autowired
private EmailUtility emailUtility;
...
Thread sendEmailThread = new Thread(emailUtility.new SendEmail(subject, text, toAddress));
...
}
EmailUtility most probably is not created by Spring, since it is not marked as #Service, #Component, #Controller, #Inject, etc. thats why Spring framework has no chance to inject (autowire) your implementation of JavaMailSender into EmailUtility instance.
Spring does some magic with classes named by a certain pattern, therefore your *Controller classes might be recognized by Spring as beans, but not *Utility class.
Most likely your EmailUtility is not a spring managed bean. Consider adding annotation #Component to EmailUtility or declare a corresponding bean in your configuration class.
I want to get some log4j2 properties from my configuration bean but problem is Spring initializes this beans after log4j2 starts. Is there any way to initialize this bean before log4j2 provider?
<NoSql name="elasticsearchTimeAppender">
//I will set below values from Spring conf. bean
<Mongo cluster="" host="" port="" index="" type="" flushInterval=""/>
</NoSql>
Here is my provider;
#Plugin(name = "Mongo", category = "Core", printObject = true)
public class MongoProvider implements NoSqlProvider<MongoConnection> {
....
host = MyConfiguration.DatabaseName; //I am getting null value here because Spring initializes after provider completed.
}
Here is my configuration bean;
#Component
public class MyConfiguration {
public static String DatabaseName;
#PostConstruct
private void setStaticFields() {
MyConfiguration.DatabaseName= databaseName;
}
#Value("${mydbconf.name}")
private String databaseName;
public String getDatabaseName() {
return DatabaseName;
}
public void setDatabaseName(String DatabaseName) {
MyConfiguration.DatabaseName = DatabaseName;
}
}
I have a Wicket Session class as follows
public class IASession extends AuthenticatedWebSession {
private static final long serialVersionUID = 3529263965780210677L;
#SpringBean
private UserService userService;
public IASession(Request request) {
super(request);
}
#Override
public boolean authenticate(String username, String password) {
// Get the user
UserDetailsDTO user = userService.findByEmail(username);
if(null != user && user.getPassword().equals(password))
return true;
else
return false;
}
#Override
public Roles getRoles() {
Roles roles = new Roles();
roles.add("SIGNED_IN");
return roles;
}
}
In this class, I am trying to autowire Spring service using wicket-spring annnotation #SpringBean. But when I am trying to login, it giving me error.
Last cause: null
WicketMessage: Method onFormSubmitted of interface org.apache.wicket.markup.html.form.IFormSubmitListener targeted at [StatelessForm [Component id = login-form]] on component [StatelessForm [Component id = login-form]] threw an exception
Wicket is unable to autowire the userService spring bean and that is why it's null.
What can I do to fix this?
Since the Session is not a Component or Behavior you'll have to overwrite the constructor and call Injector.get.inject(this). See the SpringComponentInjector doc.
public IASession(Request request) {
super(request);
Injector.get().inject(this);
}
I'm developing a web application with spring. I've had no problem autowiring and using database #Service classes. Now I'm trying to read a global property file and provide the values to all classes that need them. The solution I've come up with so far seem to be overly complicated (too many classes - AppConfig, ServerConfig iface, ElasticServerConfig) for such a trivial task but I could live with it if it worked.
my applicationContext.xml contains
<context:component-scan base-package="my.package" />
AppConfig.java:
package my.package.configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
}
ServerConfig.java:
public interface ServerConfig {
String getUrl();
String getUser();
String getPassword();
}
ElasticSearchConfig.java:
package my.package.configuration;
#Component(value = "elasticServerConfig")
public class ElasticServerConfig implements ServerConfig {
private static final Logger LOGGER = LogManager.getLogger(ElasticServerConfig.class);
private String url;
private String user;
private String password;
#Autowired
public ElasticServerConfig(final Environment env) {
this.url = env.getProperty("elastic_server.url");
this.user = env.getProperty("elastic_server.user");
this.password = env.getProperty("elastic_server.password");
LOGGER.debug("url=" + url + "; user=" + user + "; password=" + password); // this works!
}
#Override
public final String getUrl() {
return url;
}
#Override
public final String getUser() {
return user;
}
#Override
public final String getPassword() {
return password;
}
}
When the web application boots, the ElasticServerConfig constructor prints out the correct url/user/pwd as read from application.properties. However an instance of ElasticServerConfig is not injected into a Search object:
package my.package.util;
public class Search {
#Autowired
#Qualifier("elasticServerConfig")
private ServerConfig elasticServerConfig;
public final List<Foobar> findByPatternAndLocation() {
if (elasticServerConfig == null) {
LOGGER.error("elasticServerConfig is null!");
}
// and i get a NullPointerException further on
// snip
}
}
You have to register the Search class as a Spring Bean and take it from the Spring context when you want to use it. It's important to get the bean from the spring context. If you create an object of that class with new, Spring has no way to know about that class and mange it's dependencies.
You can get get a bean from the Spring context by #Autowire it somewhere or by accessing an instance of the context and use the getBean method:
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
ctx.getBean...
}
}
Either use #Component annotation on the class and make sure that the class is in package thats under my.package
or register it in the configuration class
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
#Bean
public Search search(){
return new Search();
}
}