Русский English Тэги View Sergey Zolotaryov's profile on LinkedIn Вход
Echo2 + Spring + AspectJ
Постоянная ссылка 06-01-2008 anydoby java

Для тех, кто не знает, небольшое вступление. Echo2 - это замечательный фреймворк для создания веб-приложений на java без использования html, javascript и сервлетов. Сайт автора здесь. Разработка очень напоминает таковую для десктопных приложений на Swing, так что для тех, что знаком с desktop java, работать с Echo2 будет просто сплошным удовольствием. Естественно, начав знакомство с новой технологией, я не собирался бросать старый багаж, например, такую удобную штуку как Spring. Итак, задача этой статьи выяснить, как элегантнее всего интегрировать этих двух китов. Правда, в ходе исследования пришлось прикрутить третьего - AspectJ.

Echo2 от J2EE нужна только одна зацепка - сервлет, который создает экземпляр главного окна для каждого пользователя. Вот пример кода, который это делает:


/**
 * 
 */
package com.anydoby.catalog.admin;

import static org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext;
import nextapp.echo2.app.ApplicationInstance;
import nextapp.echo2.webcontainer.WebContainerServlet;

import org.springframework.web.context.WebApplicationContext;

/**
 * @author szolotaryov
 *
 * Nov 14, 2007
 */
public class AdminServlet extends WebContainerServlet {

	private static final long serialVersionUID = 8661042655420915943L;

	@Override
	public ApplicationInstance newApplicationInstance() {
		WebApplicationContext context = getWebApplicationContext(getServletContext());
		ApplicationInstance applicationInstance = (ApplicationInstance) context.getBean("admin");		
		return applicationInstance;
	}

}

Бин по имени admin это компонент Echo2, наследующийся от ApplicationInstance. Данный сервлет можно замапить на любой нужный нам урл, например вот так:


    <servlet>
        <servlet-name>admin</servlet-name>
        <servlet-class>
            com.anydoby.catalog.admin.AdminServlet
        </servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>admin</servlet-name>
        <url-pattern>/admin</url-pattern>
    </servlet-mapping>	

Теперь, заходя на урл /admin, пользователь попадает в своё окошко :)

Вы обратили внимание, что главное окно я создал при помощи Spring. Это был первый и единственный lookup, который нам нужно было сделать, чтобы связать Spring и Echo2. Остальные объекты, которыми управляет Spring, мы будем создавать оператором new для упрощения кода и конфигурирования. А зависимости будет разрешать Spring с помощью AspectJ и аннотации @Configurable. Вот, например, как выглядит одно из окон моего приложения, которое создается оператором new и конфигурируется Spring:


/**
 * 
 */
package com.anydoby.catalog.admin;

import nextapp.echo2.app.ApplicationInstance;
import nextapp.echo2.app.event.ActionEvent;
import nextapp.echo2.app.event.ActionListener;
import nextapp.echo2.extras.app.TabPane;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Configurable;

import com.anydoby.catalog.dao.CatalogDAO;

/**
 * @author szolotaryov
 *
 * Nov 19, 2007
 */
@Configurable(autowire = Autowire.BY_TYPE)
public class ShopsPanel extends TabPane implements ActionListener {

    private static final long serialVersionUID = 7851350631802754997L;
    
    private transient CatalogDAO catalogDAO;
    
    private AdminApp app;
    
    public final CatalogDAO getCatalogDAO() {
        return catalogDAO;
    }

    public final void setCatalogDAO(CatalogDAO catalogDAO) {
        this.catalogDAO = catalogDAO;
    }

    public ShopsPanel() {
        
    }

    @Override
    public void init() {
        super.init();
        removeAll();
        
        // initialize the window - add widgets and stuff
        // init method is invoked by Echo2 automtically when the component
        // is added to parent
    }

    public void actionPerformed(ActionEvent e) {
        // some actions
    }

    public final AdminApp getApp() {
        return app;
    }

    public final void setApp(AdminApp app) {
        this.app = app;
    }

}

Как видим, все очень просто - в компонент инжектируются DAO и ссылка на главное окно приложения - на случай, если оно понадобится. И все это делается совершенно прозрачно - через Spring autowiring. Можно, конечно, и без него, но для простых приложений так лучше - меньше конфигурационный файл.

Теперь, собственно, то, что нужно добавить в applicationContext.xml, чтобы все заработало:


<?xml version="1.0"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
           http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd
           http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd" 
           default-dependency-check="none" default-lazy-init="false">

    <aop:spring-configured></aop:spring-configured>

    <bean id="admin" class="com.anydoby.catalog.admin.AdminApp" scope="session">
        <property name="dao" ref="myDao"></property>
    </bean>

    <bean class="com.anydoby.catalog.admin.ShopsPanel" scope="prototype"/>

    .....................................
</beans>

Бин admin имеет scope="session" - для того, чтобы Spring сам следил за тем, чтобы экземпляр приложения не создавался для одного пользователя несколько раз. Если бы scope был prototype, to при каждом создании ShopsPanel бина создавался бы новый объект AdminApp, что, естественно, не позволяло бы нам общаться с главным окном. Объекты экранов (com.anydoby.catalog.admin.ShopsPanel, например) имеют scope="prototype" и конфигурируются автоматически (опять же, это лишь для удобства конфигурации, если вам это не подходит, можете описывать всю конфигурацию в xml, как обычно).

И, наконец, для того, чтобы aop:spring-configured заработал как следует, нужно подсоединить AspectJ. Это можно сделать либо подключив агент при загрузке вашего контейнера (load-time weaving), либо добавив один шаг при компиляции - static weaving. Я пользовался и первым, и вторым способом; и скажу, что проблем меньше со вторым вариантом. На нем и остановимся. Если вы используете ant, то после обычной компиляции нужно добавить вот это:


<path id="aspectj">
   <fileset dir="lib/aspectj">
      <include name="*.jar"/>
      <!-- lib dir contains AspectJ jars -->
   </fileset>
</path>

<taskdef classpathref="aspectj" resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties"/>

<target name="compile">
   <javac/>
   ...............
   <iajc inpath="${javac_output}" aspectpath="lib/spring-aspects.jar" destdir="${classes.dir}" source="1.5" verbose="true">
       <classpath>
        ..............
       </classpath>
   </iajc>

spring-aspects.jar лежит в дистрибутиве Spring. В нем два аспекта - @Configurable, и @Transactional. В данной статье мы пользуемся только первым.

Вот и все. После компиляции AspectJ нужно лишь добавить aspectjrt-x.y.z.jar в папку WEB-INF/lib и все должно заработать.

Добавить комментарий

Предыдущая статья Удобный Matcher для jMock Следующая статья Weaving аспектов при помощи Maven2