I'm in the process of upgrading the spring framework version used in our webapp from 3.1.4 to 4.1.8. With the new Spring version, A few of our unit tests are failing because #Autowired is no longer working. This is one of the failing tests:
#ContextConfiguration(locations={"/math-application-context.xml"})
public class MathematicaMathServiceTest extends JavaMathServiceTest{
#Autowired
private KernelLinkPool mathematicalKernelPool;
protected static String originalServiceType = System.getProperty("calculation.math.service.type");
#AfterClass
public static void unsetMathServiceType(){
System.clearProperty("calculation.math.service.type");
}
#BeforeClass
public static void setMathServiceType(){
System.setProperty("calculation.math.service.type","Mathematica");
}
#Test
public void testMathematicaService() throws Exception{
try {
acquireKernelAndExecute(0);
Assert.assertEquals(0, mathematicalKernelPool.getBorrowingThreadsCount());
} catch(UnsatisfiedLinkError e) {
System.out.println("Mathematica not installed. Skipping test");
}catch(Exception ex){
if (!ExceptionFormatter.hasCause(ex, MathServiceNotConfiguredException.class)){throw ex;}
if (System.getProperty(MathService.SERVICE_CONFIGURED_SYSTEM_VARIABLE) != null){
throw ex;
}
logger.error("Cannot execute test. Math service is not configured");
}
}
}
This is the KernelLinkPool class:
public class KernelLinkPool extends GenericObjectPool implements InitializingBean{
private static final int RETRY_TIMEOUT_MS = 5000;
private static final long STARTUP_WAIT_TIME_MS = 10000;
private boolean mathematicaConfigured = true;
private PoolableObjectFactory factory;
// ensures that multiple requests from the same thread will be given the same KernelLink object
private static ThreadLocal<KernelLink> threadBoundKernel = new ThreadLocal<KernelLink>();
// holds the number of requests issued on each thread
private static ThreadLocal<Integer> callDepth = new ThreadLocal<Integer>();
private long maxBorrowWait;
private Integer maxKernels;
private boolean releaseLicenseOnReturn;
private Logger logger = LoggerFactory.getLogger(this.getClass());
// (used only for unit testing at this point)
private Map<String,Integer> borrowingThreads = new ConcurrentHashMap<String,Integer>();
public KernelLinkPool(PoolableObjectFactory factory) {
super(factory);
this.factory = factory;
this.setMaxWait(maxBorrowWait);
}
#Override
public Object borrowObject() throws Exception{
return borrowObject(this.maxBorrowWait);
}
public Object borrowObject(long waitTime) throws Exception {
long starttime = System.currentTimeMillis();
if (!mathematicaConfigured){
throw new MathServiceNotConfiguredException();
}
try{
if (callDepth.get() == null){
callDepth.set(1);
}else{
callDepth.set(callDepth.get()+1);
}
KernelLink link = null;
if (threadBoundKernel.get() != null){
link = threadBoundKernel.get();
}else{
//obtain kernelLink from object pool
//retry when borrowObject fail until
//maxBorrowWait is reached
while(true){
try{
logger.debug("Borrowing MathKernel from object pool");
link = (KernelLink) super.borrowObject();
break;
}catch(KernelLinkCreationException ex){
long timeElapsed = System.currentTimeMillis() - starttime;
logger.info("Failed to borrow MathKernel. Time elapsed [" + timeElapsed + "] ms", ex);
if(timeElapsed >= waitTime){
logger.info("Retry timeout reached");
throw ex;
}
Thread.sleep(RETRY_TIMEOUT_MS);
}
}
logger.debug("borrowed [" + link + "]");
threadBoundKernel.set(link);
}
borrowingThreads.put(Thread.currentThread().getName(),callDepth.get());
return link;
}catch(Exception ex){
logger.error("Failed to acquire Mathematica kernel. Borrowing threads [" + borrowingThreads + "]");
throw ex;
}
}
public void returnObject(Object obj) throws Exception {
callDepth.set(callDepth.get()-1);
if (callDepth.get() <= 0){
threadBoundKernel.set(null);
borrowingThreads.remove(Thread.currentThread().getName());
if (releaseLicenseOnReturn){
// will destroy obj
super.invalidateObject(obj);
}
else{
// will park obj in the pool of idle objects
super.returnObject(obj);
}
}else{
borrowingThreads.put(Thread.currentThread().getName(),callDepth.get());
}
}
#Override
public void afterPropertiesSet() throws Exception {
try{
if (maxKernels == 0){
List<KernelLink> links = new ArrayList<KernelLink>();
while (true){
try{
links.add((KernelLink)factory.makeObject());
}catch(KernelLinkCreationException ex){
break;
}
}
if(links.isEmpty()){
logger.warn("No available Mathematica license!");
mathematicaConfigured = false;
return;
}
for (KernelLink link : links){
factory.destroyObject(link);
}
logger.info("Detected number of available Mathematica license = [" + links.size() + "]");
setMaxActive(links.size());
setMaxIdle(links.size());
}else{
if(maxKernels < 0){
logger.info("Set number of Mathematica license to no limit");
}else{
logger.info("Set number of Mathematica license to [" + maxKernels + "]");
}
setMaxActive(maxKernels);
setMaxIdle(maxKernels);
}
Object ob = borrowObject(STARTUP_WAIT_TIME_MS);
returnObject(ob);
mathematicaConfigured = true;
}catch(Throwable ex){
logger.warn("Mathematica kernel pool could not be configured: ", ex.getMessage());
mathematicaConfigured = false;
}
}
public int getBorrowingThreadsCount() {
return borrowingThreads.size();
}
public Integer getMaxKernels() {
return maxKernels;
}
public void setMaxKernels(Integer maxKernels) {
this.maxKernels = maxKernels;
}
public boolean isMathematicaConfigured(){
return mathematicaConfigured;
}
public boolean isReleaseLicenseOnReturn() {
return releaseLicenseOnReturn;
}
public void setReleaseLicenseOnReturn(boolean releaseLicenseOnReturn) {
this.releaseLicenseOnReturn = releaseLicenseOnReturn;
}
public long getMaxBorrowWait() {
return maxBorrowWait;
}
public void setMaxBorrowWait(long maxBorrowWait) {
this.maxBorrowWait = maxBorrowWait;
}
}
The tests are failing with this exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.etse.math.wolfram.KernelLinkPool] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
This is the math-application-context file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<beans profile="unitTest,integratedTest,activeServer">
<bean class="org.springframework.jmx.export.MBeanExporter"
lazy-init="false">
<property name="registrationBehaviorName" value="REGISTRATION_IGNORE_EXISTING" />
<property name="beans">
<map>
<entry key="etse.math:name=MathematicalKernelFactory"
value-ref="mathematicalKernelFactory" />
<entry key="etse.math:name=MathematicalKernelPool" value-ref="mathematicalKernelPool" />
</map>
</property>
</bean>
<bean id="mathService" class="com.etse.math.MathServiceFactoryBean">
<property name="mathServiceType" value="${calculation.math.service.type}"/>
<property name="mathematicaService" ref="mathematicaService"/>
</bean>
<bean id="mathematicaService" class="com.etse.math.wolfram.MathematicaService">
<property name="kernelPool" ref="mathematicalKernelPool" />
<property name="minParallelizationSize" value="${calculation.mathematica.kernel.parallel.batch.size}" />
</bean>
<bean id="mathematicalKernelPool" class="com.etse.math.wolfram.KernelLinkPool"
destroy-method="close">
<constructor-arg ref="mathematicalKernelFactory" />
<property name="maxKernels" value="${calculation.mathematica.max.kernels}" />
<property name="maxBorrowWait"
value="${calculation.mathematica.kernel.borrow.max.wait}" />
<property name="releaseLicenseOnReturn"
value="${calculation.mathematica.kernel.release.license.on.return}" />
</bean>
<bean id="mathematicalKernelFactory" class="com.etse.math.wolfram.KernelLinkFactory">
<property name="debugPackets" value="false" />
<property name="linkMode" value="launch" />
<property name="mathematicaKernelLocation" value="${calculation.mathematica.kernel.location}" />
<property name="mathematicaLibraryLocation" value="${calculation.mathematica.library.location}" />
<property name="mathematicaAddOnsDirectory" value="${calculation.mathematica.addons.directory}" />
<property name="linkProtocol" value="sharedMemory" />
</bean>
</beans>
<beans profile="passiveServer,thickClient,tools">
<bean id="mathService" class="com.etse.math.DummyMathService"/>
</beans>
I also tried using the application context to load the bean, but that failed with the following exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'mathematicalKernelPool' is defined
If I remove the autowired field, the test fails with a NoSuchBeanDefinitionException for another bean (mathService) that is loaded via the application context in a super class. So it appears that the application context from math-application-context is not loaded for some reason. Any idea of what could be happening here? Thank you.
UPDATE:
I took a look at the beans defined in the application context and confirmed that none of the beans defined in math-application-context are present. The application context contains only beans defined in another context file loaded by the super class. Why would it fail to load math-application-context?
At this point I would honestly get rid of the XML config and go total annotation/code based. Create a Config class and have it create any beans you need to be autowired.
It was a profile issue. The super class to the test was using:
#ProfileValueSourceConfiguration(TestProfileValueSource.class)
to set the profile, but it was not working. After removing that annotation I added:
#ActiveProfiles(resolver=TestProfileValueSource.class) and now its working again.
Related
Java 9 and 11 often make it necessary to use a custom classloader. So I created a BootstrapClassloader that preloads my classpaths, and I use Thread.currentThread().setContextClassLoader(classloader) to make it the default.
I soon caught Spring reverting to the original application classloader when loading contexts with new ClassPathXmlApplicationContext().
No problem I thought, and wrote a variant I called BootstrapClassLoader to try and force Spring to use my classloader. But Spring still ignores my classloader.
For example, I verified that aopalliance-1.0.jar is in the classloader's classpath and can manually load MethodInterceptor. But if I have a context with a standard DBCP2 datasource bean (depends on aopalliance) and it throws a ClassNotFoundException for org.aopalliance.intercept.MethodInterceptor.
Can anyone see a flaw in the code below, or is there perhaps some special way to register the classloader so that Spring will always use it in the application?
Datasource snippet:
<bean id="msSqlDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
<property name="validationQuery" value="select 1" />
</bean>
<bean id="dbEmailDataSource" parent="msSqlDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" lazy-init="true">
<property name="url" value="" />
<property name="username" value="" />
<property name="password" value="" />
</bean>
Test application code:
public static void main(String[] args) {
try {
Bootstrap.init();
for(String urlName : Bootstrap.getClasspathUrlNames()) {
if(StringUtils.containsIgnoreCase(urlName, "aopalliance")) {
System.out.println(urlName);
}
}
ConfigurableApplicationContext ctx = new BootstrappedApplicationContext("DbEmailContext.xml");
ctx.close();
}catch(Throwable t) {
t.printStackTrace();
}
try {
Class clazz = Bootstrap.getClassLoader().loadClass("org.aopalliance.intercept.MethodInterceptor");
System.out.println("manually loaded: "+clazz.getName());
}catch(Throwable t) {
t.printStackTrace();
}
}
BootstrappedApplicationContext (similar to ClassPathXmlApplicationContext):
public class BootstrappedApplicationContext extends AbstractXmlApplicationContext {
private URLClassLoader classloader;
private Resource[] configResources;
public BootstrappedApplicationContext() {
super();
this.classloader = Bootstrap.getClassLoader();
super.setClassLoader(this.classloader);
}
public BootstrappedApplicationContext(ApplicationContext parent) {
this();
setParent(parent);
}
public BootstrappedApplicationContext(String configLocation)
throws BeansException {
this(new String[] {configLocation}, true, null);
}
public BootstrappedApplicationContext(String... configLocations)
throws BeansException {
this(configLocations, true, null);
}
public BootstrappedApplicationContext(String[] configLocations, ApplicationContext parent)
throws BeansException {
this(configLocations, true, parent);
}
public BootstrappedApplicationContext(String[] configLocations, boolean refresh)
throws BeansException {
this(configLocations, refresh, null);
}
public BootstrappedApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
this(parent);
setConfigLocations(configLocations);
if(refresh) {
refresh();
}
}
public BootstrappedApplicationContext(String path, Class<?>clazz)
throws BeansException {
this(new String[] {path}, clazz);
}
public BootstrappedApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
this(paths, clazz, null);
}
public BootstrappedApplicationContext(String[] paths, Class<?> clazz, ApplicationContext parent)
throws BeansException {
this(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for (int i = 0; i < paths.length; i++) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
refresh();
}
#Override
public ClassLoader getClassLoader() {
if(classloader == null) {
classloader = Bootstrap.getClassLoader();
}
super.setClassLoader(classloader);
return classloader;
}
public void setClassLoader(URLClassLoader classloader) {
this.classloader = classloader;
super.setClassLoader(classloader);
}
#Override
protected Resource[] getConfigResources() {
return this.configResources;
}
}
This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 6 years ago.
I am a newbie to Spring and I am trying to load properties file using Spring framework, I am able to successfully load all the properties from junit test but when I am trying to implement the unit test as function it throws NPE-
my junit class (which is working as expected)
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles(profiles = "test")
#ContextConfiguration("classpath:spring/xml-config-context.xml")
public class GlueTestPropertiesTest2 extends TestCase {
#Autowired
GenericEnv env;
#Autowired
WebPropertiesLoader wpl;
#Test
public void testAppProperties() {
System.out.println("Running MiniConfigSpringPropertiesTest ...");
System.out.println("Environment : " + env.toString());
System.out.println("Database Properties: " + wpl.toString());
}
}
My implementation class (Which is exhibiting NPE) :
#ActiveProfiles(profiles = "test")
#ContextConfiguration("classpath:spring/xml-config-context.xml")
public class GlueTestProperties {
#Autowired
GenericEnv env;
#Autowired
WebPropertiesLoader wpl;
public static void main(String[] args) {
GlueTestProperties gp = new GlueTestProperties();
gp.callme();
}
private void callme(){
System.out.println("Running ConfigSpringPropertiesTest ...");
System.out.println("Environment : " + env.toString());
System.out.println("Database Properties: " + wpl.toString());
}
}
WebPropertiesLoader bean :
#Component
public class WebPropertiesLoader {
#Value("${bank.ease.login.url}")
public String easeLoginUrl;
#Value("${bank.browser.name}")
public String browserName;
#Value("${bank.browser.version}")
public String browserVersion;
#Value("${webdriver.chrome.driver}")
public String chromeDriver;
#Value("${webdriver.ie.driver}")
public String ieDriver;
#Value("${bank.web.feature.location}")
public String webFeatureLocation;
#Value("${bank.web.test.location}")
public String webTestLocation;
#Value("${bank.event.log}")
public String eventLog;
#Value("${bank.epoxy.backend}")
public String epoxyBackend;
#Value("${bank.epoxy.host}")
public String epoxyHost;
#Value("${bank.epoxy.port}")
public String epoxyPort;
#Value("${bank.epoxy.debug}")
public String epoxyDebug;
#Value("${bank.epoxy.implicitWait}")
public String epoxyImplicitWait;
#Value("${bank.epoxy.timeout}")
public String epoxyTimeOut;
#Value("${bank.epoxy.default.url}")
public String epoxyDefaultURL;
#Value("${bank.sassy.url}")
public String sassyUrl;
#Value("${bank.transite.url}")
public String transiteUrl;
#Value("${bank.transite.login.url}")
public String transiteLoginUrl;
public String getBrowserName() {
return browserName;
}
public String getBrowserVersion() {
return browserVersion;
}
public String getChromeDriver() {
return chromeDriver;
}
public String getEpoxyDefaultURL() {
return epoxyDefaultURL;
}
public String getSassyUrl() {
return sassyUrl;
}
public String getTransiteUrl() {
return transiteUrl;
}
public String getTransiteLoginUrl() {
return transiteLoginUrl;
}
public String getIeDriver() {
return ieDriver;
}
public String getWebFeatureLocation() {
return webFeatureLocation;
}
public String getWebTestLocation() {
return webTestLocation;
}
public String getEventLog() {
return eventLog;
}
public String getEpoxyBackend() {
return epoxyBackend;
}
public String getEpoxyHost() {
return epoxyHost;
}
public String getEpoxyPort() {
return epoxyPort;
}
public String getEpoxyDebug() {
return epoxyDebug;
}
public String getEpoxyImplicitWait() {
return epoxyImplicitWait;
}
public String getEpoxyTimeOut() {
return epoxyTimeOut;
}
public String getEaseLoginUrl() {
return easeLoginUrl;
}
#Override
public String toString() {
return "Ease application Default Properties [browserName=" + browserName + ", browserVersion=" + browserVersion
+ ", chromeDriver=" + chromeDriver + ", ieDriver=" + ieDriver + ", webFeatureLocation="
+ webFeatureLocation + ", webTestLocation=" + webTestLocation + ", eventLog=" + eventLog
+ ", epoxyBackend=" + epoxyBackend + ", epoxyHost=" + epoxyHost + ", epoxyPort=" + epoxyPort
+ ", epoxyDebug=" + epoxyDebug + ", epoxyImplicitWait=" + epoxyImplicitWait + ", epoxyTimeOut="
+ epoxyTimeOut + ", epoxyDefaultURL=" + epoxyDefaultURL + ", easeLoginUrl=" + easeLoginUrl + "]";
}
}
Test env bean :
#Component
public class TestEnv implements GenericEnv {
private String envName = "test";
#Value("${profile.name}")
private String profileName;
public String getEnvName() {
return envName;
}
public void setEnvName(String envName) {
this.envName = envName;
}
public String getProfileName() {
return profileName;
}
public void setProfileName(String profileName) {
this.profileName = profileName;
}
#Override
public String toString() {
return "TestEnv [envName=" + envName + ", profileName=" + profileName
+ "]";
}
}
Context xml used :
?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- scans for annotated classes in the com.company package -->
<context:component-scan base-package="com.glue.commons" />
<!-- enables annotation based configuration -->
<!-- <context:annotation-config /> -->
<beans profile="dev">
<!-- allows for ${} replacement in the spring xml configuration from the
application-default.properties, application-dev files on the classpath -->
<context:property-placeholder
location="classpath:properties/application-web-default.properties, classpath:properties/application-web-dev.properties"
ignore-unresolvable="true" />
<!-- scans for annotated classes in the com.env.dev package -->
<context:component-scan base-package="com.glue.env.dev" />
</beans>
<beans profile="test">
<!-- allows for ${} replacement in the spring xml configuration from the
application-default.properties, application-test files on the classpath -->
<context:property-placeholder
location="classpath:properties/application-web-default.properties, classpath:properties/application-web-qa2.properties"
ignore-unresolvable="true" />
<!-- scans for annotated classes in the com.env.test package -->
<context:component-scan base-package="com.glue.env.test" />
</beans>
<beans profile="prod">
<!-- allows for ${} replacement in the spring xml configuration from the
application-default.properties, application-prod files on the classpath -->
<context:property-placeholder
location="classpath:properties/application-web-default.properties, classpath:properties/application-web-prod.properties"
ignore-unresolvable="true" />
<!-- scans for annotated classes in the com.env.prod package -->
<context:component-scan base-package="com.glue.env.prod" />
</beans>
<beans profile="dev">
<!-- allows for ${} replacement in the spring xml configuration from the
application-default.properties, application-dev files on the classpath -->
<context:property-placeholder
location="classpath:properties/application-api-default.properties, classpath:properties/application-api-dev.properties"
ignore-unresolvable="true" />
<!-- scans for annotated classes in the com.env.dev package -->
<context:component-scan base-package="com.glue.env.dev" />
</beans>
<beans profile="test">
<!-- allows for ${} replacement in the spring xml configuration from the
application-default.properties, application-test files on the classpath -->
<context:property-placeholder
location="classpath:properties/application-api-default.properties, classpath:properties/application-api-qa2.properties"
ignore-unresolvable="true" />
<!-- scans for annotated classes in the com.env.test package -->
<context:component-scan base-package="com.glue.env.test" />
</beans>
<beans profile="prod">
<!-- allows for ${} replacement in the spring xml configuration from the
application-default.properties, application-prod files on the classpath -->
<context:property-placeholder
location="classpath:properties/application-api-default.properties, classpath:properties/application-api-prod.properties"
ignore-unresolvable="true" />
<!-- scans for annotated classes in the com.env.prod package -->
<context:component-scan base-package="com.glue.env.prod" />
</beans>
Thanks in advance, please forgive me if I've made some silly mistake, but I need your help.
If you use the main method to create it then spring won't know anything about this class so it won't autowire any classes - you get nulls in all fields annotated with #Autowired.
Your JUnit is working correctly because it is instantiated with spring aware junit runner.
I am able to get the properties to load. Issue was that as #krzyk pointed out my implementation class was not spring aware hence it was not able to load the profile from my context xml and to make it aware of my profile bean I had to pass vm argument through my main class, following code change and approach helped me :
my implementation class look like this now:
#ContextConfiguration("classpath:spring/xml-config-context.xml")
public class GlueTestProperties {
private GlueTestProperties() {
}
private static ApplicationContext context;
public static GenericEnv getEnv() {
return getContext().getBean(TestEnv.class);
}
public static WebPropertiesLoader getWebProperties() {
return getContext().getBean(WebPropertiesLoader.class);
}
public static ApiPropertiesLoader getApiProperties() {
return getContext().getBean(ApiPropertiesLoader.class);
}
private static ApplicationContext getContext() {
if (null == context) {
init();
}
return context;
}
private static synchronized void init() {
context = new ClassPathXmlApplicationContext("spring/xml-config-context.xml");
}
}
I am passing following as vm argument : -Dspring.profiles.active=test
Or second approach would be you have to take out
<context:property-placeholder
location="classpath:properties/application-web-default.properties, classpath:properties/application-web-qa2.properties"
ignore-unresolvable="true" />
from your <bean/> and place it out side so that it will be visible for main class and you do not have to pass vm arguments here.
I managed to configure and schedule a Quartz job using JobStoreTX persistent store in Spring Boot ( version 4.2.5 ). Here is how I schedule the job.
First :
public class MyJob implements Job{
#Autowired
IService service;
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
service.doSomething();
}
}
#Autowired seems like it wont work in a Quartz job implementation because it wont be instantiated by Spring. Hence, im facing the famous JavaNullPointerException.
Second, in order to get hold of Spring-managed beans in a Quartz job, I used org.springframework.scheduling.quartz.SchedulerFactoryBean to manage the Quartz lifecycle :
public class MyJob implements Job{
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
ApplicationContext applicationContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
IService service= applicationContext.getBean(IService.class);
service.getManualMaxConfig();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
And then :
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
The sad news is that im also facing JavaNPE.
I also try these suggestions, in vain ..
LINK
Whats wrong with what im doing?
Update 1 :
Before trying to inject service, i tried to pass some Params as #ritesh.garg suggests.
public class MyJob implements Job{
private String someParam;
private int someParam2;
public void setSomeParam(String someParam) {
this.someParam = someParam;
}
public void setSomeParam2(int someParam2) {
this.someParam2 = someParam2;
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("My job is running with "+someParam+' '+someParam2);
}
}
And my jobBean.xml looks like :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
<bean id="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.quartz.service.MyJob"/>
<property name="jobDataAsMap">
<map>
<entry key="someParam" value="some value"/>
<entry key="someParam2" value="1"/>
</map>
</property>
</bean>
</beans>
I dont know why, but the parameters arent passed and it prints :
My job is running with null 0
Ps : I imported the jobBean.xml into Application.java . So i dont know what am i missing ?
Update 2 : Here is my detailed code :
#Component
public class JobScheduler{
Timer timer = new Timer();
#PostConstruct
public void distributeAutomaticConf(){
try {
timer.schedule(new ServiceImpl(), 10000);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Service Impl :
#Transactional
#Component
public class ServiceImpl extends TimerTask implements IService{
#Override
public void run() {
final SchedulerFactory factory = new StdSchedulerFactory();
Scheduler scheduler = null;
try {
scheduler = factory.getScheduler();
final JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setName("My job executed only once.. ");
jobDetail.setJobClass(MyJob.class);
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity("trigger_", "group_")
.build();
scheduler.start();
scheduler.scheduleJob(jobDetail, trigger);
System.in.read();
if (scheduler != null) {
scheduler.shutdown();
}
} catch (final SchedulerException e) {
e.printStackTrace();
} catch (final IOException e) {
e.printStackTrace();
}
}
}
MyJob :
public class MyJob extends QuartzJobBean{
#Autowired
IService service;
#Override
protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException { SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
service.doSomething();
}
}
jobBean.xml :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="applicationContextSchedulerContextKey" value="applicationContext" />
</bean>
<bean id="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.quartz.service.MyJob"/>
<property name="jobDataAsMap">
<map>
<entry key="someParam" value="some value"/>
<entry key="someParam2" value="1"/>
</map>
</property>
</bean>
</beans>
quartz.properties :
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
#org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.dataSource.myDS.driver = org.postgresql.Driver
org.quartz.dataSource.myDS.URL = jdbc:postgresql://localhost:5432/myDB
org.quartz.dataSource.myDS.user = admin
org.quartz.dataSource.myDS.password = admin
org.quartz.dataSource.myDS.maxConnections = 10
org.quartz.scheduler.skipUpdateCheck=true
console :
java.lang.NullPointerException: null
at com.quartz.service.MyJob.executeInternal(MyJob.java:27) ~[classes/:na]
at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:113) ~[spring-context-support-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.2.1.jar:na]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [quartz-2.2.1.jar:na]
2016-06-05 11:35:16.839 ERROR 25452 --- [eduler_Worker-1] org.quartz.core.ErrorLogger : Job (DEFAULT.My job executed only once.. threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-2.2.1.jar:na]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [quartz-2.2.1.jar:na]
Caused by: java.lang.NullPointerException: null
at com.quartz.service.MyJob.executeInternal(MyJob.java:27) ~[classes/:na]
at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:113) ~[spring-context-support-3.1.2.RELEASE.jar:3.1.2.RELEASE]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.2.1.jar:na]
... 1 common frames omitted
I have experienced the same problem in past. My understanding on this issue is that beans instantiated in spring context cannot be injected in quartz context simply by using #Autowired annotation.
I managed to solve it by using setter based dependency injection. But the same is mentioned in the "LINK" you have added in the original post.
Pasting the relevant information from the link:
Update: Replaced implements Job with extends QuartzJobBean
public class MyJob extends QuartzJobBean {
private String someParam;
private int someParam2;
public void setSomeParam(String someParam) {
this.someParam = someParam;
}
public void setSomeParam2(String someParam2) {
this.someParam2 = someParam2;
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("My job is running with "+someParam+' '+someParam2);
}
}
Here, someParam and someParam2 are being injected via setter dependency injection. Now the other part that makes this complete is to pass someParam and someParam2 in jobDataAsMap
<bean id="myJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.my.MyJob"/>
<property name="jobDataAsMap">
<map>
<entry key="someParam" value="some value"/>
<entry key="someParam2" value="1"/>
</map>
</property>
</bean>
In your case, it would be a value-ref="IserviceBeanId", instead of 'value' in entry. I would be surprised as well as curious, if this did not/does not work for you.
I fix my problem implementing "InitializingBean" in my job;
public class MyJob extends QuartzJobBean implements InitializingBean {
private String someParam;
private int someParam2;
public void setSomeParam(String someParam) {
this.someParam = someParam;
}
public void setSomeParam2(String someParam2) {
this.someParam2 = someParam2;
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("My job is running with "+someParam+' '+someParam2);
}
#Override
public void afterPropertiesSet() throws Exception {
}
}
The correct way from the most of the examples I've seen is to make your Job interface implementation a #Component
#Component
public class MyJob implements Job{
#Autowired IService service;
#Override
public void execute(JobExecutionContext context) throws JobExecutionException{
service.doSomething();
}
}
We can use JobDataMap to pass the objects.
example: here restTemplate is Autowired.
JobDataMap newJobDataMap = new JobDataMap();
newJobDataMap.put("restTemplate", restTemplate);
JobDetail someJobDetail = JobBuilder
.newJob(QuartzJob.class)
.withIdentity(jobName, GROUP)
.usingJobData(newJobDataMap)
.build();
The following managed operation exists in the project:
#ManagedOperation(description = "Some description")
#ManagedOperationParameters({
#ManagedOperationParameter(name = "key", description = "Some description"),
})
public void foo(String key) {
// some logic
}
Also there is a property which can be used in Spring context by surrounding it with dollar sign and square brackets:
"${some.property.key}"
Is it possible to use the value of aforementioned property key in the managed operation annotation description? Something like:
#ManagedOperationParameter(name = "key",
description = "Some description, please note that the key is ${some.property.key}")
Not out-of-the-box, but it's pretty easy to customize...
public class CustomAttributeSource extends AnnotationJmxAttributeSource implements EmbeddedValueResolverAware {
private StringValueResolver embeddedValueResolver;
#Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
#Override
public ManagedAttribute getManagedAttribute(Method method) throws InvalidMetadataException {
ManagedAttribute managedAttribute = super.getManagedAttribute(method);
if (this.embeddedValueResolver != null) {
managedAttribute
.setDescription(this.embeddedValueResolver.resolveStringValue(managedAttribute.getDescription()));
}
return managedAttribute;
}
#Override
public ManagedOperation getManagedOperation(Method method) throws InvalidMetadataException {
ManagedOperation managedOperation = super.getManagedOperation(method);
if (this.embeddedValueResolver != null) {
managedOperation
.setDescription(this.embeddedValueResolver.resolveStringValue(managedOperation.getDescription()));
}
return managedOperation;
}
}
Then...
<bean class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter">
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="foo.CustomAttributeSource" />
</property>
</bean>
</property>
</bean>
Background
Our application uses Jedis-2.2.1 and connects to Redis-2.6, here's how I get jedis resource :
protected static JedisWrapper getRedisUserWrite(String UDID) {
if (redisUserWritePools.get(0) == null) init();
int hash = hash(UDID);
Jedis jedis = redisUserWritePools.get(hash).getResource();
jedis.select(dbs.get("redisUserWritePools" + hash));
return new JedisWrapper(jedis, redisUserWritePools.get(hash));
}
And this is my JedisWrapper(Unify the management of resources):
public class JedisWrapper {
private Jedis jedis;
private JedisPool pool;
public JedisWrapper(Jedis jedis, JedisPool pool) {
this.jedis = jedis;
this.pool = pool;
}
public Jedis get(){
return this.jedis;
}
public void returnResource() {
if(null != this.jedis){
this.pool.returnResource(this.jedis);
}
}
public void returnBrokenResource() {
if(null != this.jedis) {
this.pool.returnBrokenResource(this.jedis);
}
this.jedis = null;
}
}
JedisWrapper is the container if Jedis instance, here's how I use it :
private static void cacheSDKIDs(String UDID, String[] SDKIDs) {
JedisWrapper wrapper = getRedisUserWrite(UDID);
try {
if (SDKIDs != null) {
wrapper.get().del(UDID);
wrapper.get().sadd(UDID, SDKIDs);
}
} catch (JedisConnectionException e) {
e.printStackTrace();
wrapper.returnBrokenResource();
}catch (Exception e) {
e.printStackTrace();
} finally {
wrapper.returnResource();
}
}
Note that, SKDIDs maybe very large(e.g. could reach the maximum of 8KB).
Here's the problem
Every time I restart our application, all redis connections are normal, but several hours
later, the Could not get a resource from the pool Exception comes out. And frequency become higher and higher, then all the connections to Redis are disconnected and can create new connection.
Here's my configuration :
<bean id = "redisConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxActive" value="400" />
<property name="maxIdle" value="100" />
<property name="minIdle" value="20" />
<property name="maxWait" value="4000" />
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true" />
</bean>
Exception Stacktrace:
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:40)
at com.xxxice.redis.BaseRedis.getRedisUserWrite(BaseRedis.java:158)
at com.xxx.service.redis.DeviceRedis.cacheSDKIds(DeviceRedis.java:128)
at com.xxx.redis.DeviceRedis.cacheDevice(DeviceRedis.java:65)
at com.xxx.service.DeviceService.update(DeviceService.java:88)
at com.xxx.controller.Devices.update(Devices.java:25)
... 32 more
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1174)
at redis.clients.util.Pool.getResource(Pool.java:38)
... 37 more
In your JedisWrapper, the Jedis is created as a class variable which gets instantiated once. Please declare it inside the methods getJedis, then the problem will be solved
check whether you have permission to access redis through code