Accéder au contenu principal

Gwt: requestFactory et spring

Avant la version 2.1, il n'y avait que les RPC pour communiquer avec le serveur en Gwt. Depuis cette version, un nouveau api est disponible, Request Factory.

Cet api permet de n'envoyer que les informations nécessaires au client et au serveur. Il y a économie de bande passante. Au niveau du code, beaucoup de changement sont à prévoir. J'ai passé beaucoup de temps à essayer d'utiliser cet api avec Spring.

La base de donnée h2, spring 3.1 ainsi que gwt 2.4 ont été utilisé pour cet exemple.

L'interface graphique comporte deux boutons, un pour charger toutes les données de la base de donnée et l'autre pour y ajouter un élément.




Le fichier web.xml

<web -app="-app" version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemalocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
 
    <context -param="-param">
        <param -name="-name" />contextConfigLocation
        <param -value="-value" />/WEB-INF/applicationContext.xml
    </context>
    <listener>
        <listener -class="-class">org.springframework.web.context.ContextLoaderListener</listener>
    </listener>
    <servlet>
        <servlet -name="-name">requestFactoryServlet</servlet>
        <servlet -class="-class">com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet>
    </servlet>
    <servlet -mapping="-mapping">
        <servlet -name="-name">requestFactoryServlet</servlet>
        <url -pattern="-pattern">/gwtRequest</url>
    </servlet>
    <welcome -file-list="-file-list">
        <welcome -file="-file">welcomeGWT.html</welcome>
    </welcome>
</web>


Dans le fichier applicationContext.xml, je n'ai que

<context:annotation -config="-config">
<context:component -scan="-scan" base-package="org.calibra">


<bean id="propertyConfigurer"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
          p:location="/WEB-INF/jdbc.properties" />

    <bean id="dataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource"
      p:driverClassName="${jdbc.driverClassName}"
      p:url="${jdbc.url}"
      p:username="${jdbc.username}"
      p:password="${jdbc.password}" />


Dans le fichier Main.gwt.xml, il faut ajouter

<inherits name="javax.validation.Validation"/>
<inherits name='com.google.web.bindery.requestfactory.RequestFactory'/>

Dans le package Domain

public class Account {
    private Long id;
    private String username;
    private Integer version;

    ....
}

Dans le package server


La classe AccountLocator est nécessaire afin de ne pas mettre ces opérations directement dans la classe Account.

public class AccountLocator extends Locator<Account, Long> {

    @Autowired
    private AccountDAO accountDAO;

    @Override
    public Account create(Class<? extends Account> clazz) {
        return new Account();
    }

    @Override
    public Account find(Class<? extends Account> clazz, Long id) {

        if (accountDAO == null) {
            HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
            ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
            accountDAO =context.getBean(AccountDAO.class);
        }
        return accountDAO.findAccount(id);
    }

    @Override
    public Class<Account> getDomainType() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Long getId(Account domainObject) {
        return domainObject.getId();
    }

    @Override
    public Class<Long> getIdType() {
        return Long.class;
    }

    @Override
    public Object getVersion(Account domainObject) {
        return domainObject.getVersion();
    }


La classe SpringServiceLocator est nécessaire afin d'avoir accès au bean.

public class SpringServiceLocator implements ServiceLocator {
    @Override
    public Object getInstance(Class<?> clazz) {
        HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
        ServletContext servletContext = request.getSession().getServletContext();
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        return context.getBean(clazz);
    }
}



Dans le package server.service

public interface AccountService {
    public Account findAccount(Long id);
    public List<Account> loadAllAccounts();
    public void save(Account account);
    public void delete(Account account);
}


 @Service
public class AccountServiceImpl implements AccountService{
  
    @Autowired
    private AccountDAO accountDAO;

    @Override
    public void save(Account account){
        accountDAO.save(account);
    }

    @Override
    public List<Account> loadAllAccounts() {
        return accountDAO.loadAllAccounts();
    }

    @Override
    public Account findAccount(Long id) {
        return accountDAO.findAccount(id);
    }

    @Override
    public void delete(Account account) {
        accountDAO.delete(account);
    }
}



Dans le package server.dao

@Repository
public class AccountDAOImpl implements AccountDAO {

    protected JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccount(Long id) {
        StringBuffer sb = new StringBuffer();
        sb.append("select id, username, version from account where id = ?");
        List<Account> accountList = jdbcTemplate.query(sb.toString(),
                new Object[]{id}, mapper);

        if (accountList != null && accountList.size() > 0) {
            return accountList.get(0);
        }
        return null;
    }

    @Override
    public List<Account> loadAllAccounts() {

        StringBuffer sb = new StringBuffer();
        sb.append("select id, username, version from account");
        List<Account> accountList = jdbcTemplate.query(sb.toString(), mapper);
        return accountList;
    }

    @Override
    public void save(Account account) {
        if (account.getVersion() == null) {
            account.setVersion(0);
            jdbcTemplate.update("insert into account(username, version) values(?, ?)",
                    account.getUsername(), account.getVersion());
        } else {
            account.setVersion(account.getVersion() + 1);
            jdbcTemplate.update("update account(username, version) set username=?, version=? where id=?",
                    account.getUsername(), account.getVersion(), account.getId());
        }
    }

    @Override
    public void delete(Account account) {
        jdbcTemplate.update("delete from account where id = ?", account.getId());
    }

    @Autowired
    public void setDataSource(DataSource datasource) {
        this.jdbcTemplate = new JdbcTemplate(datasource);
    }
    private static ParameterizedRowMapper<Account> mapper = new ParameterizedRowMapper<Account>() {
        @Override
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
            Account account = new Account();
            account.setId(rs.getLong("id"));
            account.setUsername(rs.getString("username"));
            account.setVersion(rs.getInt("version"));
            return account;
        }
    };
}


public interface AccountDAO {  
    public Account findAccount(Long id);
    public List<Account> loadAllAccounts(); 
    public void save(Account account);
    public void delete(Account account);
}

 

 Dans le package shared


public interface AccountRequestFactory extends RequestFactory {
    AccountRequest accountRequest();
}


@Service(locator = SpringServiceLocator.class, value =AccountService.class)
public interface AccountRequest extends RequestContext {

    Request<AccountProxy> findAccount(Long id);
    Request<List<AccountProxy>> loadAllAccounts();
    Request<Void> save(AccountProxy accountProxy);
    Request<Void> delete(AccountProxy accountProxy);
}


@ProxyFor(value=Account.class, locator = AccountLocator.class)
public interface AccountProxy extends EntityProxy{
    public Long getId();
    public String getUsername();
    public Integer getVersion();
   
    public void setUsername(String userName);
    public void setId(Long id);
    public void setVersion(Integer version);
 }


Dans le package client

public class MainEntryPoint implements IsWidget, EntryPoint {

    private final AccountRequestFactory requestFactory = GWT.create(AccountRequestFactory.class);
    AccountRequest request;
    AccountProxy newAccount;

    @Override
    public Widget asWidget() {

        Button loadAllButton = new Button("Load all Account");
        final ValueListBox<AccountProxy> accountListBox = new ValueListBox<AccountProxy>(new Renderer<AccountProxy>() {
            @Override
            public String render(AccountProxy object) {
                return object.getUsername();
            }

            @Override
            public void render(AccountProxy object, Appendable appendable) throws IOException {
                appendable.append(object.getUsername());
            }
        });

        loadAllButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                request = requestFactory.accountRequest();
                Request<List<AccountProxy>> loadAllAccounts = request.loadAllAccounts();

                loadAllAccounts.fire(new Receiver<List<AccountProxy>>() {
                    @Override
                    public void onSuccess(List<AccountProxy> response) {
                        accountListBox.setAcceptableValues(response);
                        Window.alert("account ajouté");
                    }
                });
            }
        });

        Button addButton = new Button("Add account");
        addButton.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                request = requestFactory.accountRequest();
                AccountProxy accountProxy = request.create(AccountProxy.class);
                accountProxy.setUsername("joe");

                request.save(accountProxy).fire(new Receiver<Void>() {
                    @Override
                    public void onSuccess(Void response) {
                        Window.alert("account ajouté");
                    }
                });
            }
        });

        VerticalPanel vp = new VerticalPanel();
        vp.setSpacing(10);
        vp.add(loadAllButton);
        vp.add(accountListBox);
        vp.add(addButton);
        return vp;
    }

    @Override
    public void onModuleLoad() {
        requestFactory.initialize(new SimpleEventBus());
        RootPanel.get().add(asWidget());
    }
}


Liste de jar présent dans WEB-INF/lib/




Les sources de cet exemples est disponible ici.
Ces jar ne sont pas inclus dans les sources.

Commentaires

Posts les plus consultés de ce blog

Toshiba AC100 et Android 4

Toshiba AC100 est un smartbook. Toshiba est la seule grosse entreprise qui a commercialisé un smartbook, malgré que plusieurs autres en aient présenté. Un smartbook est similaire à un netbook (taille) en plus d'avoir des caractéristiques souvent proche à un mobile (GPS, 3g, processeur ARM, autonomie). Le Toshiba AC100 utilisait le système d'exploitation Android en version 2.1. Toshiba a effectué une mise à jour Android 2.2. Il a arrêté la commercialisation et le support de cette machine. L'appareil ayant un bon rapport qualité/prix, la communauté le prit en charge. Il est possible d'installer une version beaucoup plus récente d'Android telle que la 4.1. La rom alternative Cyanogen a été utilisée pour ce portage. Les procédures citées ont été faites sous Linux.  Sous Windows, il faut télécharger une version de nvflash pour ce système. Préparation Télécharger les fichiers: cm_ac100-ota-eng.cm10-beta2.zombah.zip recovery.ac100-cm10_beta2.img via ce...

Spring data jpa et Hibernate’s Warning “firstResult/maxResults specified with collection fetch”

Nous verrons dans cet article comment corrigé le problème  Hibernate’s Warning “firstResult/maxResults specified with collection fetch” qui peut survenir lorsqu'il y a un fetch d'effectuer et qu'il y a du paging. Nous utiliserons spring data jpa. Détail structure Imaginons une relation 1 à plusieurs Lorsqu'on obtient un parent, on désire obtenir aussi sa liste d'enfants. En sql, il y aurait une jointure à effectuer. Il est question d'une recherche avancé où on pourrait recherche par plusieurs critères. Spécification Création d'une spécification pour construire une requête dynamique @Component public class ParentSpecification {     public Specification<Parent> recherche(RechercheParent recherche) {         return (root, cq, cb) -> {             Join<Parent, enfant> enfantJoin;              List<Predicate> rootPredicates = new ArrayList<>();       ...

Migration d’une application Spring Boot 2.2 vers Spring Boot 4.0 : retour d’expérience concret

 En 2019, nous avons développé une application métier basée sur : Java 8 Spring Boot 2.2.7 Gradle 6.6.1 Thymeleaf Bootstrap 4.3.1 Environ 40 000 lignes de code Java Fin 2025, voici un retour d’expérience concret sur la migration vers : Java 25 Spring Boot 4.0.1 Gradle 9.2.1 La partie thymeleaf, n'a pas été touché, il y a quelques warning concernant l'obsolescence de certain commande tel que th:include. Une migration plus rapide que prévu Un point important : en moins de 30 minutes, l’application démarrait sur la nouvelle stack. Évidemment, "ça démarre" ne veut pas dire "tout fonctionne", mais ce premier jalon est très révélateur : la base Spring Boot reste solide les outils de migration sont matures la dette technique était maîtrisée Les changements majeurs rencontrés De javax.* à jakarta.* C’est probablement le changement le plus visible. Tous les imports javax.* doivent être migrés ve...