Problem
I have a class which uses Spring @CacheEvict annotations and embedded Spring Expression Language. When I allow this class to be compiled automatically by Eclipse, everything works fine. However, when I compile with an Ant task (either through Eclipse or from the command line), the resulting .class file doesn't work, and throws an Exception which seems like a red herring.
My question: How can I configure the Ant build so it generates working .class artifacts (so other developers can build my project without requiring Eclipse)? The configuration between Eclipse and Ant seem the same, but I must be missing some property somewhere.
Versions
- Ant (bundled with Eclipse): org.apache.ant_1.8.3.v20120321-1730
- Eclipse: Juno Service Release 1, 20120920-0800
- JDK: 1.7.0_17
- JUnit: 4.10 (in /lib/ of project)
- O/S: Windows 7 64-bit
- Spring: 3.2.2
Supporting Files
Because the problem is hard to describe without the project in front of you, I have boiled down my project to the bare minimum files needed to reproduce the issue:
/src/sample/SampleAction.java: Contains the method using the @CacheEvict annotation and SpEL.
package sample;
import org.springframework.cache.annotation.CacheEvict;
public class SampleAction {
@CacheEvict(value = "sampleCache", allEntries = true, condition = "#string.bytes != null")
public void triggerCache(String string) {
System.out.println("triggerCache(" + string + ")");
}
}
/src/sample/SampleActionTest.java: A unit test which works with Eclipse artifacts but fails with Ant artifacts.
package sample;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"application.xml"})
public class SampleActionTest {
@Autowired
private SampleAction sampleAction;
@Test
public void testCacheMethod() {
sampleAction.triggerCache("Definitely not a null string.");
}
}
/src/sample/application.xml: Spring definition file for the caching and the action class.
<cache:annotation-driven />
<bean id="sampleAction" class="sample.SampleAction" />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="sampleCache" />
</set>
</property>
</bean>
/.settings/org.eclipse.jdt.core.prefs: Eclipse compiler settings (1.6 via 1.7.0_17):
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
/build.xml: The Ant build which cleans, compiles, and runs the unit test that fails.
<path id="classpath.main">
<fileset dir="lib" includes="*.jar" />
</path>
<path id="classpath.test">
<path refid="classpath.main" />
<pathelement location="output/classes" />
</path>
<target name="compileFailure" description="Cleans, compiles, and runs a unit test, which fails.">
<delete quiet="true" dir="output/classes" />
<mkdir dir="output/classes" />
<copy tofile="output/classes/sample/application.xml" file="src/sample/application.xml" />
<javac srcdir="src" destdir="output/classes"
classpathref="classpath.main" source="1.6" target="1.6" includeantruntime="false" />
<junit printsummary="yes">
<classpath refid="classpath.test" />
<formatter type="plain" usefile="false"/>
<test name="sample.SampleActionTest" outfile="result" />
</junit>
</target>
/lib/*.jar: Supporting libraries:
com.springsource.org.aopalliance-1.0.0.jar
commons-logging-1.1.3.jar
junit-4.10.jar
spring-aop-3.2.2.jar
spring-beans-3.2.2.jar
spring-context-3.2.2.jar
spring-context-support-3.2.2.jar
spring-core-3.2.2.jar
spring-expression-3.2.2.jar
spring-test-3.2.2.jar
This stub project is also available as a ZIP archive, including supporting JAR files for Spring and JUnit: Minimal Eclipse Project
Steps to Reproduce
- Open the "compilerDebug" project in Eclipse.
- Allow Eclipse to automatically build the classes in the "sample" package. Either do a Project Clean/Build or open the Java files, edit the whitespace, and resave them. When compiled with Eclipse, output/classes/sample/SampleAction.class is 911 bytes.
- Right-click on the "SampleActionTest" and choose "Run As... JUnit Test". Test passes successfully.
- Open the build.xml file in the Ant view. Right-click on the "compileFailure" target and choose "Run As... Ant Build". Test fails with "EL1007E:(pos 0): Field or property 'bytes' cannot be found on null" stack trace. When compiled with Ant, output/classes/sample/SampleAction.class is 694 bytes.
Other Observations
- I have tried adding compiler="org.eclipse.jdt.core.JDTCompilerAdapter" to the "javac" task, in order to force Ant to use the Eclipse compiler, but the unit test still fails after this change. SampleAction.class is 696 bytes here.
- If I remove the condition from the @CacheEvict annotation, the unit test passes with both compilers.
- Setting source/target levels to 1.5 or 1.7 has no effect.
- Forcing the Ant task to "run in the same JRE as workspace" has no effect.
- Downloading a standalone Ant distribution (1.9.1) and running the Ant build completely isolated from Eclipse has no effect.
- Replacing the @CacheEvict annotation with declarative XML configuration (using aspectjtools) has no effect.
Thanks in advance for helping to prevent my inevitable insanity from troubleshooting.
Update
See answer provided below. Ant was not explicitly setting debug mode to true when compiling with javac. SpEL requires the extra debugging information to be included in the class files in order to correctly handle the annotations. Setting debug to true immediately corrected the issue.
<javac debug="true" [...] />