12

I'm trying to use automatic dependency injection via Spring's @Configurable annotation w/ @Resource on the fields needing injection. This involved some setup, like passing spring-agent.jar to my JVM. For the full details see here.

It works... mostly. When my Tomcat is booting up, I see the AspectJ init messages, my User objects automatically get FileService references, etc.

The problem is that sometimes it just doesn't happen. It appears to be completely random; sometimes I boot up and the dependencies are not injected, sometimes they are. I previously had trouble with @Transactional being on my User because it created a conflict, I believe with proxies. I am using JPA, so my User is marked with @Entity, so my best guess right now is that this is creating a conflict. I've read you can't auto proxy a proxy. To offset the conflict, I followed some notes I found online about excluding CGLIB and javassist which Hibernate (my JPA impl) uses.

Clues:

  • It's all or nothing. All of my @Configurable instances have been injected or none of them.
  • Reloading (reinstantiating) the Entity from the DB doesn't appear to help; it's either working or not.
  • Rebooting Tomcat any number of time also won't even fix it. The only thing that appears to roll the dice again is a redeploy. In other words, if I redeploy it may work.

How can I figure out what is going wrong? Is anyone using @Configurable with JPA? Why isn't my dependencyCheck = true throwing an error when dependencies are not actually injected?

Entity

@Entity
@Configurable(dependencyCheck = true)
@NamedQueries( { @NamedQuery(name = "User.findAll", query = "SELECT user FROM User user"),
    @NamedQuery(name = "User.findByEmail", query = "SELECT user FROM User user WHERE user.email = :email") })
public abstract class User extends BaseModel {

private static final long serialVersionUID = 7881431079061750040L;

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Long id;

@Column(unique = true, nullable = false)
private String email;

@Basic(optional = false)
private String password;

@Resource
private transient UserEmailer userEmailer;

@Resource
private transient FileService fileService;

...

aop.xml

<!DOCTYPE aspectj PUBLIC
    "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver options="-verbose">
        <include within="com.myapp.domain..*" />
        <exclude within="*..*CGLIB*" />
        <exclude within="*..*javassist*" />
    </weaver>
    <aspects>
        <aspect name="org.springframework.beans.factory.aspectj.AbstractInterfaceDrivenDependencyInjectionAspect" />
    </aspects>
</aspectj>

applicationContext.xml

...

<context:spring-configured />

<context:load-time-weaver />

<context:component-scan base-package="com.myapp" />

...
Robert Campbell
  • 6,848
  • 12
  • 63
  • 93
  • 2
    I too have been experiencing these type of issues with @Configurable and @Transactional for a long while. I think it has to do with the class loader loading the classes before the spring context is initialized. See this thread: http://forum.springsource.org/showthread.php?t=68406. The sporadic nature of this bug is extremely annoying. – ghempton Jul 23 '09 at 21:38
  • 1
    Given all of the changes to Spring, is there now a better solution to this problem? – Snekse Dec 29 '11 at 15:51

6 Answers6

3

First I have to say that it is probably not a good idea to have resources, services or other beans injected into data model classes as dependencies. But that is a question of design.

To the usage of @Configurable I used it in cases where objects are instantiated from outside the Spring context - like custom tags in web applications, filters or servlets. The first way I tried to use them was by load time weaving like you do. That worked quite well but it had some drawbacks like hot code deployment while debugging did not work anymore.

I did also experience exactly the problem you describe and so I decided to switch from load time weaving to compile time. Therefor I installed the AJDT plugin in Eclipse and used the aspecjt support of Spring. That solved my problems.

Thomas Einwaller
  • 8,873
  • 4
  • 40
  • 55
  • Thanks Thomas. I have some explanation of why I designed it this way here: http://stackoverflow.com/questions/694374/how-can-i-resolve-the-conflict-between-loose-coupling-dependency-injection-and-a Basically it comes down to domain-driven design influencing me. My domain models used to be just data; they mostly represented table rows. After reading this very good book, I decided to move more towards OOP/data + behavior, root entities being responsible for their child entities, etc. A good example of where I use this is to inject the Spring-wired Factory for a child entity into his parent. – Robert Campbell May 20 '09 at 08:52
  • The book is Domain-Driven Design by Eric Evans. I learned of it by a recommendation by Martin Fowler in Patterns of Enterprise Application Architecture which is another really great book. – Robert Campbell May 27 '09 at 11:41
3

To me it sounds like an occurence of a well known bug in Spring: http://jira.springframework.org/browse/SPR-5401.

Can it be that you are trying to use Configurable in several application contexts? In this case only one of them will be subject to dependency injection. Which one wins depends on which application context is the last one to be loaded.

Solution? None :-( There are no plans to fix this issue. That is at least what the SpringSource guy told at the JAX conference in Germany in April.

jens
  • 1,763
  • 1
  • 15
  • 25
2

Can't spot anything obvious, so just a suggestion - have you tried using compile-time weaving? Hopefully that would lead to consistent results at runtime, although it can be a bit more hassle during development.

James
  • 391
  • 1
  • 4
  • I would also suggest using compile-time weaving for production deploys. Also prevents issues related to someone forgetting to set the aspect-j weaver JDK parameter. . . . compile-time weaving is great for integration tests. – Jasper Blues May 29 '12 at 02:54
1

When the injection doesn't work, for whatever reason, there is no way for code to do any dependency check, so there is now error thrown.

Otherwise I can't see anything here that would indicate the random failure. Can you extract a simplified example to check?

Jörn Zaefferer
  • 5,665
  • 3
  • 30
  • 34
  • Hi Jörn, thanks for the comment. I will try a simplified example when I get home. I need to update this question, because I've since switched from using the spring-agent.jar to a smaller-scoped TomcatInstrumentableClassLoader, see section 6.8.4.6.2. Tomcat of the Spring reference document. Oddly, this change appears to lower the frequency of the problem occuring, but doesn't fix it entirely. – Robert Campbell May 14 '09 at 13:00
  • Is there a reason why you are using @Configurable over @Component? – Rich Kroll May 14 '09 at 16:14
  • According to the documentation they'd both accomplish the same thing here. I use @Component for DI injection on Spring-managed beans, while I use @Config for DI injection on non-Spring-managed beans. So even though they'd both work, I use the different ones to hint as to the Spring status of a bean. – Robert Campbell May 30 '09 at 09:42
1

It sounds like your deploy process is suspect. Can you do a deployment that works, then copy that off to a directory. Then do another deployment until you get one that doesn't work. (In either order) Then finally, use a tool like beyond compare to compare the two deployment directory structures and files, and see if there are any differences.

Good luck, nothing like a seemingly random problem to kill some productivity.

Nathan Feger
  • 19,122
  • 11
  • 62
  • 71
  • 1
    That's a really good idea I never considered. I'll try it out tonight. BTW, it's funny that I started using @Configurable to INCREASE my productivity; before I was doing manual DI at certain points (like Factory, Repo, etc) and I was chasing down missed areas where the domain object is instantiated but never injected. Right now I'd say @Cofig is +1 for elegance, -1 for productivity compared to the orig solution – Robert Campbell May 20 '09 at 08:48
  • Yeah, I'm on a project now, where I'm using a set of new technologies. They rarely improve productivity until you make it over the hump. I haven't even tried to face spring's annotated properties. Too many new things, and the xml works everytime. – Nathan Feger May 20 '09 at 12:44
0

I found the reason; because custom beans register without order. If your bean was used before the Spring bean org.springframework.context.config.internalBeanConfigurerAspect was loaded, then @Configurable autowire will not work. And my solution is like below:

@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED)
@EnableSpringConfigured
@ComponentScan(basePackages = "zhibo")
public class AppConfig extends WebMvcConfigurationSupport {

    @Bean
    public DemoProcessor demoProcessor() {
        return new DemoProcessor();
    }
}


public class DemoProcessor implements BeanPostProcessor,BeanDefinitionRegistryPostProcessor {

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
    Stream.of(beanDefinitionNames).forEach(System.err::println);
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    String[] beanDefinitionNames = registry.getBeanDefinitionNames();
    Map<String, BeanDefinition> zhiboBeans = new LinkedHashMap<>();
    //1. remove your beans. let spring's beans go ahead
    Stream.of(beanDefinitionNames).forEach(beanName->{
        BeanDefinition bd = registry.getBeanDefinition(beanName);
        if(bd.getBeanClassName()!=null && bd.getBeanClassName().startsWith("zhibo.")) {
            registry.removeBeanDefinition(beanName);
            zhiboBeans.put(beanName, bd);
        }
    });
    //2. register your beans again
    zhiboBeans.forEach((k,v)->registry.registerBeanDefinition(k, v));
    }
}
David Buck
  • 3,752
  • 35
  • 31
  • 35
Jack
  • 1