Spring - Instantiating beans results in infinite recursion and (ironic) StackOverflow exception. How to fix? - spring

When I launch my application, for some reason not apparent to me it is waiting until it instantiates the SchedulerFactoryBean to instantiate the jtaTransactionManager bean. When it does this, Spring goes into an infinite recursion starting from resulting in a StackOverflow exception.
After tracing hte code, I see no circular dependency - transaction manager is not dependent in any way on the SchedulerAccessor
In the stack view image at the bottom, the Proxy$98 class is some enhancement of org.springframework.scheduling.quartz.SchedulerAccessor
Edit 1: Update
What is happening is that the SchedulerFactoryBean is being initialized in the preInstantiateSingletons() method of the bean factory. The transaction manager is not a singleton, so it is not pre-initialized. When Spring goes through the advisements, it tries to initialize the bean, but the advisement leads it back to the same pathway.
Edit 2: Internals (or infernals)
The spring class org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration implements the transactionManager attribute as a LazyProxy.
This is executed well before the initialization code constructs the actual TransactionManager bean. At some point, the class needs to invoke a transaction within the TransactionManager context, which causes the spring container to try to instantiate the bean. Since there is an advice on the bean proxy, the method interceptor in the SimpleBatchConfiguration class tries to execute the getTransaction() method, which in turn causes the spring container to try to instantiate the bean, which calls the intergceptor, which tries to execute the getTransaction() method ....
Edit 3: #EnableBatchProcessing
I use the word "apparent" a lot here because it's guesswork based on the failure modes during startup.
There is (apparently) no way to configure which transaction manager is being used in the #EnableBatchProcessing annotation. Stripping out the #EnableBatchProcessing has eliminated the recursive call, but left me with an apparent circular dependency.
For some unknown reason, even though I have traced and this code is called exactly once, it fails because it thinks the bean named "configurer" is already in creation:
#Bean({ "configurer", "defaultBatchConfigurer" })
#Order(1)
public BatchConfigurer configurer() throws IOException, SystemException {
DefaultBatchConfigurer result = new DefaultBatchConfigurer(securityDataSource(), transactionManager());
return result;
}
The code that initiates the recursion is:
protected void registerJobsAndTriggers() throws SchedulerException {
TransactionStatus transactionStatus = null;
if (this.transactionManager != null) {
transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
}
AppInitializer Startup Code:
#Override
public void onStartup(ServletContext container) throws ServletException {
Logger logger = LoggerFactory.getLogger(this.getClass());
try {
// DB2XADataSource db2DataSource = null;
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(DatabaseConfig.class);
rootContext.register(SecurityConfig.class);
rootContext.register(ExecutionContextConfig.class);
rootContext.register(SimpleBatchConfiguration.class);
rootContext.register(MailConfig.class);
rootContext.register(JmsConfig.class);
rootContext.register(SchedulerConfig.class);
rootContext.refresh();
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
}
}
Construction of jtaTransactionManager bean in DatabaseConfig
#Bean(destroyMethod = "shutdown")
#Order(1)
public BitronixTransactionManager bitronixTransactionManager() throws IOException, SystemException {
btmConfig();
BitronixTransactionManager bitronixTransactionManager = TransactionManagerServices.getTransactionManager();
bitronixTransactionManager.setTransactionTimeout(3600); // TODO: Make this configurable
return bitronixTransactionManager;
}
#Bean({ "transactionManager", "jtaTransactionManager" })
#Order(1)
public PlatformTransactionManager transactionManager() throws IOException, SystemException {
JtaTransactionManager mgr = new JtaTransactionManager();
mgr.setTransactionManager(bitronixTransactionManager());
mgr.setUserTransaction(bitronixTransactionManager());
mgr.setAllowCustomIsolationLevels(true);
mgr.setDefaultTimeout(3600);
mgr.afterPropertiesSet();
return mgr;
}
Construction of SchedulerFactoryBean in SchedulerConfig
#Autowired
#Qualifier("transactionManager")
public void setJtaTransactionManager(PlatformTransactionManager jtaTransactionManager) {
this.jtaTransactionManager = jtaTransactionManager;
}
#Bean
#Order(3)
public SchedulerFactoryBean schedulerFactoryBean() {
Properties quartzProperties = new Properties();
quartzProperties.put("org.quartz.jobStore.driverDelegateClass",
delegateClass.get(getDatabaseType()));
quartzProperties.put("org.quartz.jobStore.tablePrefix", getTableSchema()
+ ".QRTZ_");
quartzProperties.put("org.quartz.jobStore.class",
org.quartz.impl.jdbcjobstore.JobStoreCMT.class.getName());
quartzProperties.put("org.quartz.scheduler.instanceName",
"MxArchiveScheduler");
quartzProperties.put("org.quartz.threadPool.threadCount", "3");
SchedulerFactoryBean result = new SchedulerFactoryBean();
result.setDataSource(securityDataSource());
result.setNonTransactionalDataSource(nonJTAsecurityDataSource());
result.setTransactionManager(jtaTransactionManager);
result.setQuartzProperties(quartzProperties);
return result;
}

There were several impossibly convoluted to figure out steps to a resolution. I ended up monkeying it until it worked because the exception messages were not information.
In the end, here is the result:
refactored packaging so job/step scoped and global scoped beans were in different packages, so context scan could capture the right beans in the right context easily.
Cloned and modified org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration to acquire the beans I wanted for my application
Took out the #EnableBatchProcessing annotation. Since I was already initializing less automagically, everything was initializing twice which created confusion
Cleaned up the usage of datasources - XA and non-XA
Use the #Primary annotation to pick out the correct (Biting tongue here - no way to tell the framework which of several datasources to use without implicitly telling it that in case of questions always use "this one"? Really???)

Related

Spring Batch/Data JPA application not persisting/saving data to Postgres database when calling JPA repository (save, saveAll) methods

I am near wits-end. I read/googled endlessly so far and tried the solutions on all the google/stackoverflow posts that have this similiar issue (there a quite a few). Some seemed promising, but nothing has worked for me yet; though I have made some progress and I am on the right track I believe (I'm believing at this point its something with the Transaction manager and some possible conflict with Spring Batch vs. Spring Data JPA).
References:
Spring boot repository does not save to the DB if called from scheduled job
JpaItemWriter: no transaction is in progress
Similar to the aforementioned posts, I have a Spring Boot application that is using Spring Batch and Spring Data JPA. It reads comma delimited data from a .csv file, then does some processing/transformation, and attempts to persist/save to database using the JPA Repository methods, specifically here .saveAll() (I also tried .save() method and this did the same thing), since I'm saving a List<MyUserDefinedDataType> of a user-defined data type (batch insert).
Now, my code was working fine on Spring Boot starter 1.5.9.RELEASE, but I recently attempted to upgrade to 2.X.X, which I found, after countless hours of debugging, only version 2.2.0.RELEASE would persist/save data to database. So an upgrade to >= 2.2.1.RELEASE breaks persistence. Everything is read fine from the .csv, its just when the first time the code flow hits a JPA repository method like .save() .saveAll(), the application keeps running but nothing gets persisted. I also noticed the Hikari pool logs "active=1 idle=4", but when I looked at the same log when on version 1.5.9.RELEASE, it says active=0 idle=5 immediately after persisting the data, so the application is definitely hanging. I went into the debugger and even saw after jumping into the Repository calls, it goes into almost an infinite cycle through the Spring AOP libraries and such (all third party) and I don't believe ever comes back to the real application/business logic that I wrote.
3c22fb53ed64 2021-05-20 23:53:43.909 DEBUG
[HikariPool-1 housekeeper] com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Pool stats (total=5, active=1, idle=4, waiting=0)
Anyway, I tried the most common solutions that worked for other people which were:
Defining a JpaTransactionManager #Bean and injecting it into the Step function, while keeping the JobRepository using the PlatformTransactionManager. This did not work. Then I also I tried using the JpaTransactionManager also in the JobRepository #Bean, this also did not work.
Defining a #RestController endpoint in my application to manually trigger this Job, instead of doing it manually from my main Application.java class. (I talk about this more below). And per one of the posts I posted above, the data persisted correctly to the database even on spring >= 2.2.1, which further I suspect now something with the Spring Batch persistence/entity/transaction managers is messed up.
The code is basically this:
BatchConfiguration.java
#Configuration
#EnableBatchProcessing
#Import({DatabaseConfiguration.class})
public class BatchConfiguration {
// Datasource is a Postgres DB defined in separate IntelliJ project that I add to my pom.xml
DataSource dataSource;
#Autowired
public BatchConfiguration(#Qualifier("dataSource") DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
#Primary
public JpaTransactionManager jpaTransactionManager() {
final JpaTransactionManager tm = new JpaTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
#Bean
public JobRepository jobRepository(PlatformTransactionManager transactionManager) throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("POSTGRES");
return jobRepositoryFactoryBean.getObject();
}
#Bean
public JobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository);
return simpleJobLauncher;
}
#Bean(name = "jobToLoadTheData")
public Job jobToLoadTheData() {
return jobBuilderFactory.get("jobToLoadTheData")
.start(stepToLoadData())
.listener(new CustomJobListener())
.build();
}
#Bean
#StepScope
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(maxThreads);
threadPoolTaskExecutor.setThreadGroupName("taskExecutor-batch");
return threadPoolTaskExecutor;
}
#Bean(name = "stepToLoadData")
public Step stepToLoadData() {
TaskletStep step = stepBuilderFactory.get("stepToLoadData")
.transactionManager(jpaTransactionManager())
.<List<FieldSet>, List<myCustomPayloadRecord>>chunk(chunkSize)
.reader(myCustomFileItemReader(OVERRIDDEN_BY_EXPRESSION))
.processor(myCustomPayloadRecordItemProcessor())
.writer(myCustomerWriter())
.faultTolerant()
.skipPolicy(new AlwaysSkipItemSkipPolicy())
.skip(DataValidationException.class)
.listener(new CustomReaderListener())
.listener(new CustomProcessListener())
.listener(new CustomWriteListener())
.listener(new CustomSkipListener())
.taskExecutor(taskExecutor())
.throttleLimit(maxThreads)
.build();
step.registerStepExecutionListener(stepExecutionListener());
step.registerChunkListener(new CustomChunkListener());
return step;
}
My main method:
Application.java
#Autowired
#Qualifier("jobToLoadTheData")
private Job loadTheData;
#Autowired
private JobLauncher jobLauncher;
#PostConstruct
public void launchJob () throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException
{
JobParameters parameters = (new JobParametersBuilder()).addDate("random", new Date()).toJobParameters();
jobLauncher.run(loadTheData, parameters);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Now, normally I'm reading this .csv from Amazon S3 bucket, but since I'm testing locally, I am just placing the .csv in the project directory and reading it directly by triggering the job in the Application.java main class (as you can see above). Also, I do have some other beans defined in this BatchConfiguration class but I don't want to over-complicate this post more than it already is and from the googling I've done, the problem possibly is with the methods I posted (hopefully).
Also, I would like to point out, similar to one of the other posts on Google/stackoverflow with a user having a similar problem, I created a #RestController endpoint that simply calls the .run() method the JobLauncher and I pass in the JobToLoadTheData Bean, and it triggers the batch insert. Guess what? Data persists to the database just fine, even on spring >= 2.2.1.
What is going on here? is this a clue? is something funky going wrong with some type of entity or transaction manager? I'll take any advice tips! I can provide any more information that you guys may need , so please just ask.
You are defining a bean of type JobRepository and expecting it to be picked up by Spring Batch. This is not correct. You need to provide a BatchConfigurer and override getJobRepository. This is explained in the reference documentation:
You can customize any of these beans by creating a custom implementation of the
BatchConfigurer interface. Typically, extending the DefaultBatchConfigurer
(which is provided if a BatchConfigurer is not found) and overriding the required
getter is sufficient.
This is also documented in the Javadoc of #EnableBatchProcessing. So in your case, you need to define a bean of type Batchconfigurer and override getJobRepository and getTransactionManager, something like:
#Bean
public BatchConfigurer batchConfigurer(EntityManagerFactory entityManagerFactory, DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Override
public PlatformTransactionManager getTransactionManager() {
return new JpaTransactionManager(entityManagerFactory);
}
#Override
public JobRepository getJobRepository() {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(getTransactionManager());
// set other properties
return jobRepositoryFactoryBean.getObject();
}
};
}
In a Spring Boot context, you could also override the createTransactionManager and createJobRepository methods of org.springframework.boot.autoconfigure.batch.JpaBatchConfigurer if needed.

Spring Batch not finding global singleton scoped beans in web application - what is wrong?

I have a spring web application which registers multiple spring batch jobs for lengthy background processing at launch time. As near as I can tell, the spring-batch contexts are not aware of the singletons declared in the root AnnotationConfigWebApplicationContext bean, so multiple copies of complex-to-initialize beans are being instantiated for every job both at initial application load time and again at execution time.
I have determined that in AbstractBeanFactory.doGetBean() the bean is being correctly identified as a singleton, but for some reason the bean factory for the job is unaware of the bean factory for the parent context.
I have refactored some of the beans as Application scoped, but the application scope bean is (apparently) not legal in the spring batch context.
Either I am grossly misunderstanding something about spring scopes, or there is something out of kilter with my initialization of the spring-batch elements (code below). I am leaning towards both, as the one would lead to the other.
As I understand Spring scopes, I should see something like this, with each child scope being able to see singletons defined in the parent scope:
AnnotationConfigWebApplicationContext (web application context)
|
v
ResourceXmlApplicationContext (1 per registered job)
|
v
ResourceXmlApplicationContext (1 per registered step)
Initialization code in question:
#Component("mySingletonScopedBean")
#Scope(value = "singleton", proxyMode = ScopedProxyMode.DEFAULT)
#Order(1)
public class MySingletonScopedBean {
// getters, setters, etcetera
}
// EDIT: Added in response to comment below
#Autowired
public ApplicationContext applicationContext;
#Bean
public ClasspathXmlApplicationContextsFactoryBean classpathXmlApplicationContextsFactoryBean () throws IOException
{
String resourcePath = somePath "*.xml";
logger.trace("classpathXmlApplicationContextsFactoryBean() :: {} ", resourcePath);
Resource[] resources = applicationContext.getResources(resourcePath);
ClasspathXmlApplicationContextsFactoryBean bean = new ClasspathXmlApplicationContextsFactoryBean ();
bean.setApplicationContext(applicationContext);
bean.setResources(resources);
return bean;
}
#Bean
public AutomaticJobRegistrar automaticJobRegistrar() throws IOException, Exception {
ClasspathXmlApplicationContextsFactoryBean c = classpathXmlApplicationContextsFactoryBean ();
AutomaticJobRegistrar automaticJobRegistrar = new AutomaticJobRegistrar();
automaticJobRegistrar.setApplicationContext(applicationContext);
automaticJobRegistrar.setApplicationContextFactories(c.getObject());
automaticJobRegistrar.setJobLoader(jobLoader());
return automaticJobRegistrar;
}
#Bean
public JobLoader jobLoader() {
DefaultJobLoader jobLoader = new DefaultJobLoader(jobRegistry(), stepRegistry());
return jobLoader;
}
#Bean
public StepRegistry stepRegistry() {
MapStepRegistry stepRegistry = new MapStepRegistry();
return stepRegistry;
}
#Bean
public JobRegistry jobRegistry() {
JobRegistry jobRegistry = new MapJobRegistry();
return jobRegistry;
}
Edit: Closing
The whole spring environment initialization was messed up.
I'm saving my rants about the "clean" injection model and what a pain it is to figure things out when they break for a massive blog post after this project.

Dynamic bean update once it has instantiated during the Spring Boot start up

I am setting up a bean during the spring boot application startup. I am trying to update the bean using a rest endpoint. The end point in the controller calls the updatePoints(). When I retrieve the data using GET point it still has only the data that was instantiated during the startup. It does not have the updated data inside the bean.
#Component
public class DynamicEntry{
private Map<String, DynamicPoint> dynamicPoints = new HashMap<>();
private DefaultListableBeanFactory beanFactory;
#Autowired
public DynamicEntry(DefaultListableBeanFactory beanFactory){
this.beanFactory = beanFactory;
}
#PostConstruct
void loadPoints(){
//load the dynamicPoints after the spring boots up
}
void updatePoints(String point){
try {
if (!dynamicPoints.containsKey(point)) {
DynamicPoint dynamicPoint = new DynamicPoint(point);
beanFactory.registerSingleton(point, dynamicPoint);
dynamicPoints(point, dynamicPoint);
}
} catch (Exception | Error e) {
e.printStackTrace();
}
}
#Bean
public Map<String, DynamicPoint> dynamicPoints() {
return dynamicPoints;
}
}
You can try refreshing with ConfigurableApplicationContext.refresh():
beanFactory.registerSingleton(point, dynamicPoint);
beanFactory.refresh();
but there will be side effects, e.g. recreation of existing singletons which may lead to your application downtime or response not being sent back to client.
What you are trying to achieve is very non standard. By design singleton bean definitions are processed during startup. You should rethink your approach and don't create singleton beans on the fly. Maybe you need a request or session scoped bean?

InvalidDataAccessApiUsageException: Executing an update/delete query Spring XML to Java config

I'm trying to convert spring xml configuration to java configuration. This works perfectly through XML configuration. However, it throws the following exception if I use java config initializer. This happens when it tries to run JQL. The application starts properly though (with all JPA mapping initialized).
org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:410) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:216) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:417) [spring-orm-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.dao.support.ChainedPersistenceExceptionTranslat
Following is my persistence initializer class. Bit of reading suggested me this is related to transactions are not being started properly. I've put debug points to each of these methods but transactionManager method never gets executed during server startup or any later time. I'm not sure what am I doing wrong :(. Same code based works perfectly when persistence is initialized through persistence.xml.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "au.mypkg")
public class DatabaseConfig {
#Bean(name = "dataSource")
#Primary
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:jboss/datasources/mydb");
}
#PersistenceContext(unitName = "persistenceUnit")
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Exception {
..........
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
final JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManagerName(JBOSS_TRANSACTION_MANANGER);
return transactionManager;
}
Error occurs when accessing this method on Dao
public void updateById(final Long id) {
final String sqlQuery = "UPDATE testtable w SET w.LAST_ACCESSED = :date WHERE w.testtable_ID = :testid";
final Query query = dao.createNativeQuery(sqlQuery);
query.setParameter("date", new Date());
query.setParameter("testid", id);
query.executeUpdate();
}
I had the some problem and I resolved it by just adding #Transactional annotation on the service method that perform delete or update.
In my case it was a method that call a repository method which execute a delete by jpql like this by I think it can solve you problem too:
#Modifying
#Query("delete from OtherPayment otherPayment " +
"where otherPayment.otherPaymentParam.id = :otherPaymentParamId")
void deleteByOtherPaymentParamId(#Param("otherPaymentParamId") Long otherPaymentParamId);
Finally figured out what was going on with it. The reason this wasn't hitting the debug point was, EnableTransactionManagement imposes to auto-configure transactions for you. So if your transaction manager configured with default name which is in my case, it wouldn't try to call my method to configure transactions. The only way to get around with this is to use a different name for your transaction manager and pass that ref as a parameter on enableJPARepositories annotation.If you use one of the default names, it wouldn't make this call.
#Primary
#Bean(name = "myRealTransactionManager")
public PlatformTransactionManager transactionManager() {
final JtaTransactionManager transactionManager = new JtaTransactionManager();
transactionManager.setTransactionManagerName(JBOSS_TRANSACTION_MANANGER);
return transactionManager;
}
.. and then
#EnableJpaRepositories(basePackages = "au.mypkg", transactionManagerRef = "myRealTransactionManager"
The other problem was I have had used setdatasource as opposed to setJtaDataSource on LocalContainerEntityManagerFatoryBean.

Spring is ignoring #Transactional annotations in Apache Shiro Realm class

I am using Spring for IOC and transaction management, and am planning to use Apache Shiro as the security library.
Whenever I want to check a user's permissions, I call subject.isPermitted("right"), whereupon Shiro checks for the permission using a datastore. Within these calls, a database connection is established, and I have annotated the method with #Transactional. However, I always receive an error that there is no Hibernate session bound to the thread whenever I execute the permission check.
The method is in the Realm class. I defined a custom Shiro Realm class:
#Component
public class MainRealm extends AuthorizingRealm {
#Autowired
protected SysUserDao userDao;
#Transactional
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
...
final SysUser user = this.userDao.findByUsername(un);
...
return authInfo;
}
#Transactional
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
...
permissions = this.userDao.getAccessRights(un);
...
return authInfo;
}
}
Apache Shiro uses a Servlet Filter, so I have the following defined in web.xml:
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
I am using programmatic configuration for Spring. Here is my App Config class:
#Configuration //Replaces Spring XML configuration
#ComponentScan(basePackages = "com.mycompany")
#EnableTransactionManagement //Enables declarative Transaction annotations
public class SpringAppConfig {
#Bean
public DataSource sqlServerDataSource() throws Exception {...}
#Bean
#Autowired
public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {...}
#Bean
public AnnotationSessionFactoryBean getSessionFactory() throws Exception {...}
#Bean
public static PersistenceExceptionTranslationPostProcessor exceptionTranslation() {...}
#Bean
#Autowired
public DefaultWebSecurityManager securityManager(MainRealm mainRealm) {
final HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(shiroHash);
hcm.setHashIterations(shiroIter);
hcm.setStoredCredentialsHexEncoded(shiroHexEncoded);
mainRealm.setCredentialsMatcher(hcm);
final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
sm.setRealm(mainRealm);
return sm;
}
#Bean
#Autowired
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
final ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(securityManager);
return filter;
}
/**
* This method needs to be static due to issues defined here:<br>
* https://issues.apache.org/jira/browse/SHIRO-222
*/
#Bean
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
LifecycleBeanPostProcessor lbpp = new LifecycleBeanPostProcessor();
return lbpp;
}
#Bean
#DependsOn("lifecycleBeanPostProcessor")
public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
#Bean
#Autowired
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager secMan) {
AuthorizationAttributeSourceAdvisor advBean = new AuthorizationAttributeSourceAdvisor();
advBean.setSecurityManager(secMan);
return advBean;
}
}
To summarize the issue, I believe my MainRealm class is being wired properly (it has an #Autowired dependency to a DAO object and I verified that it is not null) with the exception of the #Transactional annotation. Due to this, I cannot directly call user.isPermitted("") as it prompts an error: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.
Would like to ask for help in checking whether I missed anything in my Spring configuration.
In the meantime I have hacked over this issue by calling the user.isPermitted("") function within a method in my Service class that's correctly bound by a #Transactional.
EDIT When I checked the logs for Spring initialization I can see this:
Bean 'mainRealm' of type [class com.x.security.MainRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
According to this SO answer it means MainRealm isn't being postprocessed by the transaction manager therefore any #Transactional annotations get ignored. If so, how can I correct this?
EDIT 2 According to this SO question: "In other words, if I write my own BeanPostProcessor, and that class directly references other beans in the context, then those referenced beans will not be eligible for auto-proxying, and a message is logged to that effect." I just checked ShiroFilterFactoryBean and it is in fact a BeanPostProcessor. And the problem is it requires a SecurityManager instance that in turn requires a MainRealm instance. So both beans are autowired and are thus ineligible for proxying. I feel like I'm closer to the solution but I still can't resolve it.
The root cause of the issue is in fact due to the following:
All BeanPostProcessors and their directly referenced beans will be instantiated on startup... Since AOP auto-proxying is implemented as a BeanPostProcessor itself, no BeanPostProcessors or directly referenced beans are eligible for auto-proxying (and thus will not have aspects 'woven' into them.
Referenced SO question is here.
I have resolved this issue by decoupling the Realm bean creation from the SecurityManager bean creation.
The relevant change is from the following code:
#Bean
#Autowired
public DefaultWebSecurityManager securityManager(MainRealm mainRealm) {
final HashedCredentialsMatcher hcm = new HashedCredentialsMatcher(shiroHash);
hcm.setHashIterations(shiroIter);
hcm.setStoredCredentialsHexEncoded(shiroHexEncoded);
mainRealm.setCredentialsMatcher(hcm);
final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
sm.setRealm(mainRealm);
return sm;
}
to the following code:
#Bean
public DefaultWebSecurityManager securityManager() {
final DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
//sm.setRealm(mainRealm); -> set this AFTER Spring initialization so no dependencies
return sm;
}
Then I use a ServletContextListener which listens to when the Spring context initialization completes and I have the both the MainRealm and SecurityManager beans. Then I just plug one bean inside another.
#Override
public void contextInitialized(ServletContextEvent sce) {
try {
//Initialize realms
final MainRealm mainRealm = (MainRealm)ctx.getBean("mainRealm");
final DefaultWebSecurityManager sm = (DefaultWebSecurityManager)ctx.getBean("securityManager");
sm.setRealm(mainRealm);
} catch (Exception e) {
System.out.println("Error loading: " + e.getMessage());
throw new Error("Critical system error", e);
}
}
#Transactional annotation can be used before :
An interface def
An interface method
A class def
A PUBLIC method of a class
As explained in the documentation
The fact that your method is protected must be the reason of your problem, and your service method was maybe declared as public which would explained why it worked in that case

Resources