0

This is the first time I can't find a solution to my problem on stackoverflow:

I'm trying to create an executable-jar standalone application with annotation-based autowiring, but I can't get the runnable jar file (exported from Eclipse as runnable JAR file) to do it's job. It does run as Java application from Eclipse directly, just not as standalone app via console > java -jar test.jar.

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'handler': Injection of autowired dependen
cies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.example.serv
ice.UserService com.example.controller.TestHandler.userService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionEx
ception: No qualifying bean of type [com.example.service.UserService] found for dependency: expected at least 1 bean which qualifies as auto
wire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBe
anPostProcessor.java:288)

I tried to avoid such problems by following the pattern 'Initialize autowiring manually' described on this blog: http://sahits.ch/blog/?p=2326 to no avail. Note that I'm also using Hibernate JPA without a persistence.xml (therefore <tx:annotation-driven/> ) - and don't get obstructed by the two databases I use, it works fine when running from within Eclipse.

Main.java:

public class Main {
    @Autowired
    private TestRunner testRunner;

    public Main() {
        final ApplicationContext context = new ClassPathXmlApplicationContext("app-context.xml");
        AutowireCapableBeanFactory acbFactory = context.getAutowireCapableBeanFactory();
        acbFactory.autowireBean(this);
    }

    public static void main(String[] args) throws Exception {
        Main main = new Main();

        main.testRunner.run();
    }
}

TestRunner.java:

@Component
public class TestRunner {
    @Autowired
    Handler handler;

    public void run() {
        handler.doTest();
    }
}

TestHandler.java (groupId and groupName will be set in a later stage):

@Controller
public class TestHandler implements Handler {
    private static Log syslog = LogFactory.getLog(TestHandler.class);

    @Autowired
    private UserService userService;

    @Autowired
    private GroupService groupService;

    private Long groupId;
    private String groupName;

    public void doTest() {
        if (groupId != null) {
            List<User> users = userService.loadUsersByGroupId(groupId);
            syslog.debug("Amount of users found: " + users.size());
        } else {
            syslog.debug("groupId is null");
        }

        if (groupName != null) {
            List<Group> groups = groupService.loadGroupsByName(groupName);
            syslog.debug("Amount of groups found: " + groups.size());
        } else {
            syslog.debug("groupName is null");
        }

    }

    public void setGroupId(Long groupId) {
        this.groupId = groupId;
    }

    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
}

UserServiceImpl.java:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserDAO userDAO;

    @Override
    public List<User> loadUsersByGroupId(long id) {
        return userDAO.findAllByGroupId(id);
    }

...

UserDAOImpl.java:

@Repository
@Transactional("db1")
public class UserDAOImpl implements UserDAO {

    @PersistenceContext(unitName="entityManagerDb1")
    private EntityManager em;

    @SuppressWarnings("unchecked")
    @Override
    public List<User> findAllByGroupId(long id) {
        Query query = em.createQuery("select distinct u from User u " +
                "where u.groupId = :groupId");
        query.setParameter("groupId", id);

        return query.getResultList();
    }

    ...

app-context.xml (uses annotation-configured JPA, uses two dbs):

<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
        "
        >

    <context:annotation-config/>

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

    <!-- db1 -->
    <bean id="dataSourceDb1" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
      <property name="driverClass" value="com.mysql.jdbc.Driver" />
      <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/db1?characterEncoding=UTF-8" />
      <property name="user" value="root" />
      <property name="password" value="root" />
      <property name="minPoolSize" value="5" />
      <property name="maxPoolSize" value="10" />
      <property name="maxStatements" value="0" />
      <property name="preferredTestQuery" value="SELECT 1" />
      <property name="idleConnectionTestPeriod" value="600" />
    </bean>

     <bean id="entityManagerFactoryDb1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="entityManagerDb1"/>
         <property name="dataSource" ref="dataSourceDb1"/>
        <property name="packagesToScan">
            <list>
                <value>com.example.domain.db1</value>
            </list>
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="false"/>
                <property name="generateDdl" value="true"/>
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
            </bean>
        </property>

        <property name="jpaProperties">
            <props>
               <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
               <prop key="hibernate.connection.show_sql">true</prop>
               <prop key="hibernate.hbm2ddl.auto">validate</prop>
               <prop key="hibernate.use_outer_join">true</prop>
               <prop key="hibernate.connection.characterEncoding">UTF-8</prop>
               <prop key="hibernate.connection.useUnicode">true</prop>
            </props>
        </property>

    </bean>

    <!-- Configure transaction manager for JPA -->
    <bean id="transactionManagerDb1" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoryDb1"/>
        <qualifier value="db1"/>
    </bean>

    <!-- db2 -->
    <bean id="dataSourceDb2" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
      <property name="driverClass" value="com.mysql.jdbc.Driver" />
      <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/db2?characterEncoding=UTF-8" />
      <property name="user" value="root" />
      <property name="password" value="root" />
      <property name="minPoolSize" value="5" />
      <property name="maxPoolSize" value="10" />
      <property name="maxStatements" value="0" />
      <property name="preferredTestQuery" value="SELECT 1" />
      <property name="idleConnectionTestPeriod" value="600" />
    </bean>

     <bean id="entityManagerFactoryDb2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="entityManagerDb2"/>
         <property name="dataSource" ref="dataSourceDb2"/>
        <property name="packagesToScan">
            <list>
                <value>com.example.domain.db2</value>
            </list>
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="false"/>
                <property name="generateDdl" value="true"/>
                <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
            </bean>
        </property>

        <property name="jpaProperties">
            <props>
               <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
               <prop key="hibernate.connection.show_sql">true</prop>
               <prop key="hibernate.hbm2ddl.auto">validate</prop>
               <prop key="hibernate.use_outer_join">true</prop>
               <prop key="hibernate.connection.characterEncoding">UTF-8</prop>
               <prop key="hibernate.connection.useUnicode">true</prop>
            </props>
        </property>

    </bean>

    <bean id="transactionManagerDb2" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoryDb2"/>
        <qualifier value="db2"/>
    </bean>

    <tx:annotation-driven />

</beans>

It drives me crazy that running it inside Eclipse just works as expected. Is the Eclipse export feature somewhat limited? Any hint to another framework for creating runnable jar files?

Felix B.
  • 1
  • 1
  • 2

2 Answers2

1

If it is running from eclipse and not running from the exported jar file, then it is a problem with the export

In the export window there is a checkbox saying Add directory entries that should be checked for component-scan to work

Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
  • This option is not available for executable jars. I did select "Package required libs into jar", and when checking the jar file everything seems to be in place: my folder structure with compiled classes, all required libs and a MANIFEST.MF with classpath and Main-class set correctly. – Felix B. Jun 06 '13 at 15:17
1

When you run it inside Eclipse all the Spring dependencies and others are loaded into your classpath, but when you export you application as a jar, only your classes get exported, not the dependencies classes.

There are generally two way you can achieve a standlone jar. You can create an 'uber jar' (see Is it possible to create an "uber" jar containing the project classes and the project dependencies as jars with a custom manifest file?) where all you dependency jars got expanded into one single jar. However this method could be risky because if the jars have same file names (eg: same config file inside META-INF) they can overwrite each other

The other more appropriate method is to use maven-dependency-plugin (see dependency:copy) to copy all your dependency into a folder "dependencies". And then run your jar with

java -cp "myjar.jar;dependencies/*" org.mycompany.MainClass

This isn't really a standalone jar, but more a standalone folder. The downside is everytime you add/remove the dependencies (eg: from maven) you have to do the same to your dependencies folder

Community
  • 1
  • 1
gerrytan
  • 40,313
  • 9
  • 84
  • 99
  • The Eclipse export "Export as executable JAR file..." works fine with apps that use "explicit" DI via -configurations in context.xml. There's an option to include all dependent libs and it automatically generates a corresponding MANIFEST.MF. But I'd like to achieve a config with annotation-config alone. The project I'm working on uses Apache Ivy and Ant for dependency and build management, Maven is a bit "overkill" for the small and seldomly maintained app... – Felix B. Jun 06 '13 at 15:22
  • If 'Export as executable JAR' only give you 1 jar file, then most likely Eclipse extracted all dependencies into one single jar, and you risk running into 'multiple jar overwriting each other' problem. Many spring jars have properties file with same name under META-INF. Try not using executable jar – gerrytan Jun 06 '13 at 23:01
  • Nope, it's more sophisticated - like a .WAR file, but executable without an app server. See http://fjep.sourceforge.net/ But I think I'm going to try maven-plugins now, e.g. http://code.google.com/p/onejar-maven-plugin/ – Felix B. Jun 07 '13 at 08:08