Archive

Posts Tagged ‘Dependency Injection’

Using Spring Dependency Injection outside the container

October 21, 2013 Leave a comment

This is a follow on the previous post and has led me to trying to get dependency injection to work outside the container.

This is discussed in the AOP sections of the Spring dev guide. and i thought it was clearish until i tried to get it to work and really struggled (silly me nothing is easy), so this blog is the boiled down version of what i did to get it to work.

so the way i tackled this was to extend the previous example in Gradle

Here is the revised version of the BeanConfig class, note the annotations – @EnableSpringConfigured enables the AnnotationBeanConfigurerAspect – which is required to process the @Configurable annotation later; and @EnableLoadTimeWeaving is required to process the ‘new someClass ()’ outside of the container. At one point I thought I had to set the flag (aspectjWeaving=AspectJWeaving.ENABLED) but when i got this working and commented it out it still all worked.

I define a bean to add to the context ‘diSource’


@Configuration 
@EnableSpringConfigured  // should turn on  AnnotationBeanConfigurerAspect 
@EnableLoadTimeWeaving /*(aspectjWeaving=AspectJWeaving.ENABLED)*/  // switch on for this context
class BeanConfig {
	static int i
	
	@Bean 
	WillsBean publicBean () {
		return new WillsBean ("willsPublicBean")
	}
	
	@Bean @Scope ("prototype")
	WillsBean  privateBean () {
		return new WillsBean ("willsPrivateBean", i++)
	}
	
	
	@Bean
	WillsBean publicBeanWithDI () {
		//constructor injection
		return new WillsBean (diSource())
	}

	@Bean 
	DISource diSource () {
		return new DISource()
	}

	@Bean 
	DITarget diTarget () {
		return new DITarget ()  //trigger autowired injection on field 
	}

}

class DISource {
	String name = "DI source for injection "
}

class DITarget {
	@Autowired DISource diSource
}

Next I had to provide the class I expected to be injected outside the container – like this. Note you cant use the ‘dependencyCheck = true’ flag – as there is a problem if you do with the unfulfilled dependency exception – as it cant set the metaClass. Given this is groovy i think this is an issue with the java spring logic – which gets all the properties and in the case of a groovy class that includes the metaClass – which it ought to ignore. I hope this issue will go away with the spring v4 baseline to groovy.

//setting dependencyCheck = true fails as it checks for metaClass property being set  
@Configurable (autowire=Autowire.BY_TYPE /*, dependencyCheck=false*/)
class ExtDI {
	@Autowired DISource diSource

		
	def say () {
		println "ExtDI : diSource set as " + diSource.name
	}
}

In my example i have setup a context using the standard java context – not a web context. I used the AnnotationConfigApplicationContext – which reads all the @Configuration/Beans annotations and loads the context. You can load the classes marked with @configuration as constructor parameters, see commented out example. But here i have used the scan option to scan the base directory and its children, and then do the refresh() to complete the load. You can also add then dynamically using the ‘register()’ method and do the ‘refresh()’

class Sampler {
	
	static void main (String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(/*BeanConfig.class*/)
		ctx.scan ("com.softwood")
		ctx.refresh ()


So now for the tricky bit. For all this to work you need to use AOP load time (or compile time) weaving of the classes. I haven’t got into the AOP compile time yet – so i tried the load time weaving instead

In order for this to work you must have some spring dependencies resolved and loaded. When i got this to work i was seeing all sorts of issues with ‘xlint/transactional’ warnings which i fixed by adding the spring-tx jar to the class path.

Here is the jar dependency list from my gradle.build file


...
//project variables
ext {
	spring_version = "3.1.4.RELEASE"  //use which version you like by setting this 
}

repositories {
    mavenCentral()
}

dependencies {
	compile 	'org.codehaus.groovy:groovy-all:2.1.7'
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
	compile 	"org.springframework:spring-core:${spring_version}"
	compile 	"org.springframework:spring-beans:${spring_version}"
	compile 	"org.springframework:spring-context:${spring_version}"
	compile 	"org.springframework:spring-aspects:${spring_version}"
	compile 	"org.springframework:spring-aop:${spring_version}"
	compile 	"org.springframework:spring-instrument:${spring_version}"
	compile 	"org.springframework:spring-tx:${spring_version}"
	compile 	"org.aspectj:aspectjrt:1.6.10"
	compile 	"org.aspectj:aspectjweaver:1.6.10"
	compile 	"cglib:cglib:2.2"
}

spring-aspects and spring-aop are needed for the load time weaving, along with the aspectjweaver jar

if you run all this it fails saying it cant addTransform classes to java class loader. The way to fix this is to set the ‘-javaagent:spring-instrument.xxx.RELEASE jar. (on windows i think there maybe a problem if the path to the jar has a space in the path names – so put the jar somewhere without white space in the path names)

So I had problems right here that took all day to sort. When using a gradle project i right clicked on the project and went to run as>external config – however what this is doing is setting the vm args for the gradle build – not the Sample.class your running

gradle run config

I only spotted this when when I went and created a basic groovy project – and right clicked the run as>external config and I could see the vm args for the Sampler.class – then it was clear i been setting the javaagent on the wrong thing. Once i corrected that (see below) things started to work

proj run config

The net of all this is that the load time weaving should now work for you when you call new ExtDI() outside the context, which triggers the DI via AOP.

If your doing this with tomcat you need to load a different class loader, that’s aop aware – this is outlined in the Spring documentation (I havn’t tried this yet).

Also one other point, you can change the default loadTimeWeaver like this by overriding the getLoadTimeWeaver on you config class that implements LoadTimeWeavingConfigurer.

@Configuration
/**
 * can use this form to add classTransformer 
 */
class AppConfig implements LoadTimeWeavingConfigurer {
	LoadTimeWeaver getLoadTimeWeaver () {
		InstrumentationLoadTimeWeaver ltw =  new InstrumentationLoadTimeWeaver ()
		ltw.addTransformer(new BasicTransformer())
		return ltw
	}
} 

public class BasicTransformer implements ClassFileTransformer {
	
	public BasicTransformer () {
		super ()
	}
	
	public byte[] transform (ClassLoader loader, String className, Class redefiningClass, ProtectionDomain domain, byte[] bytes) 
			throws IllegalClassFormatException {
		System.out.println ("transformer applied to class " + className)
		return bytes
	}

}

In my example the BasicTransformer just lists the classes as loaded to show it works. As Groovy is dynamic and supports AST transforms – i’m not sure i’ll ever use this to instrument my classes – but you can instrument your classes this way from a pure java perspective.

So there you have it, load time weaving, and DI enabled outside the sprint context.

You can also trigger DI in constructors but you need to add an extra flag in the annotation to do this.

@Configurable(preConstruction=true)
public class MyDomain {

  @Autowired private MyContext context;

  public MyDomain() {
    context.doSomething(this); // access the autowired context in the constructor
  }

}

Lastly you need to remember that load time weaving only applies to the context configured – and not all contexts in a chain – to comply with java security policies – so you can only instrument/inject beans in the scope of the context chain.

in the case of web apps you therefore need to be careful where you apply the @SpringConfigured flags. If your doing domain class injection do this in the parent context to the web contexts below – again there is information on this in the spring documentation – but i haven’t gone through all the nuances yet – i’ve merely got the basic version working for now

Advertisements
%d bloggers like this: