Injection

Test unitaire

Unit testing is a software testing method by which individual units of source code […​] are tested to determine whether they are fit for use.
https://en.wikipedia.org/wiki/Unit_testing
— Wikipedia

Protocole de test

Protocole de test

Unit ?

One can view a unit as the smallest testable part of an application. In OOP, a unit is often an entire interface, such as a class, but could be an individual method.
https://en.wikipedia.org/wiki/Unit_testing
— Wikipedia

Problème

public class Car {

    public CarStatus start() {
        Engine engine = new CarEngine();
        if (engine.start()) {
            return CarStatus.STARTED;
        } else {
            return CarStatus.NOT_STARTED;
        }
    }
}
model problem

new

new introduit un couplage fort entre l’appelant et l’appelé

La solution ?

model solution
public class Car {

    private final Engine engine;

    public Car(Engine engine) {
         this.engine = engine;
    }

    public CarStatus start() {
        if (engine.start()) {
            return CarStatus.STARTED;
        } else {
            return CarStatus.NOT_STARTED;
        }
    }
}

Création de Car

Car car = new Car(
     new CarEngine()
);

Contexte de test

public class DummyEngine() extends Engine{
    @Override
    public boolean start() { return true; }
}

public class Test {
    public void shouldStart() {
        Car car = new Car(
            new DummyEngine()
        );
        CarStatus status = car.start();
        // Check status
    }

Inversion of Control - IoC

IoC is a design principle in which custom-written portions of a computer program receive the flow of control from a generic framework.
https://en.wikipedia.org/wiki/Inversion_of_control
— Wikipedia

IoC en Java/Java EE

  • Design pattern

    • Factory

    • Service Locator

  • Dependency Injection

Dependency Injection - DI

Dependency injection is a technique whereby one object supplies the dependencies of another object.
https://en.wikipedia.org/wiki/Dependency_injection
— Wikipedia

DI en Java/Java EE

JSRNomFonctionnalités

250

Common Annotations for the Java Platform

@Resource

330

Dependency Injection for Java

@Inject

299

Java Contexts and Dependency Injection

Décorateurs, intercepteurs, etc.

@Resource

  • Injection de ressources du serveur d’apps

    • Permet de préciser l’emplacement de la ressource

  • Injection sur le champ

Exemple

@Stateless
public class MyComponent {

    @Resource(lookup = "java:comp/env/jdbc/myDataSource")
    private DataSource dataSource;
}

@Inject

  • Injection de n’importe quel bean (ou EJB)

    • Injection par type

  • Injection via le champ ou le constructeur

Bean

Almost every concrete Java class that has a constructor with no parameters (or a constructor designated with the annotation @Inject) is a bean.
http://docs.jboss.org/cdi/learn/userguide/CDI-user-guide.html#_what_is_a_bean
— CDI User guide

Classes "standards"

  • Activation via fichier META-INF/beans.xml

  • Crée un bean pour chaque classe sur le classpath

Scope par défaut

  • Lié au cycle de vie du bean injecté

  • @Dependent

Scopes explicites

Scope Annotation

Requête

@RequestScoped

Session

@SessionScoped

Application

@ApplicationScoped

Injection via le champ

public class Dependency {}

@WebServlet
public class MyServlet extends HttpServlet {

    @Inject
    private Dependency dependency;
}
Note
Aucune possibilité de test unitaire

Injection via le constructeur

public class Dependency {}

@WebServlet
public class MyServlet extends HttpServlet {

    @Inject
    public MyServlet(Dependency dependency) {
        // Do stuff
    }
}

Test de l’injection via le constructeur

public class TestDependency extends Dependency {}

public class Test {

    public void testServlet() {
        Dependency dep = new Dependency();
        MyServlet servlet = new MyServlet(dep);
        // Test the servlet
    }
}

Choix du bean injecté

there can be only one

Exemple d’injection ambigüe

public interface Dependency {}
public class DependencyA implements Dependency {}
public class DependencyB implements Dependency {}

@WebServlet
public class MyServlet extends HttpServlet {

    @Inject
    public MyServlet(Dependency dependency) { }
}
javax.enterprise.inject.AmbiguousResolutionException:
There is more than one Bean with type Dependency

Désambiguïsation

  • Injection par nom

  • Annotation @Named

public interface Dependency {}

@Named("A")
public class DependencyA implements Dependency {}
public class DependencyB implements Dependency {}

@WebServlet
public class MyServlet extends HttpServlet {

    @Inject
    public MyServlet(@Named("A") Dependency dep) { }
}

Faiblesse

  • Manque de typage

  • Basé sur une chaîne de caractères

    • Possibilité de créer une annotation

    • Mais toujours une injection par nom

@Qualifier

  • Permet de restreindre les candidats à l’injection

  • Via la création d’annotations

@Qualifier @Retention(RUNTIME) @Target({TYPE, PARAMETER})
public @interface ThisOne {}

public interface Dependency {}

@ThisOne
public class DependencyA implements Dependency {}
public class DependencyB implements Dependency {}

@WebServlet
public class MyServlet extends HttpServlet {

    @Inject
    public MyServlet(@ThisOne Dependency dep) { }
}

Méthodes @Produces

  • Exposer une classe externe (JDK ou librairie) en bean

  • Exposer un bean dont l’implémentation dépend du contexte

  • Exposer plusieurs beans avec la même implémentation

Exemple

public interface TaxStrategy {}
public class SwissTaxStrategy implements TaxStrategy {}
public class FrenchTaxStrategy implements TaxStrategy {}

public class TaxStrategyFactory {

    @Produces
    public TaxStrategy getTaxStrategy() {
        if ("ch".equals(Locale.getDefault().getCountry()) {
            return new SwissTaxStrategy();
        }
        return new FrenchTaxStrategy();
    }
}

public class TaxComputationServlet {
    @Inject private TaxStrategy taxStrategy;
}

Concepts avancés

@Alternative

  • Gérer de la logique métier déterminée à l’exécution

  • Spécifier des beans pour un scénario de déploiement

  • Créer des beans utilisés uniquement pour les tests

Principe

  • Un bean principal (pas annoté)

  • D’autres beans annotés avec @Alternative

Configuration du beans.xml

Bean injectéExemple

Bean principal

Bean configuré

<beans...>
 <alternatives>
  <class>
   c.f.OtherBean
  </class>
 </alternatives>
</bean>

Initialisation / nettoyage

Possibilité d’exécuter du code :

  • Juste après l’instanciation

  • Juste avant la recollection par le ramasse-miettes

Annotations

Sur une méthode :

  • @PostConstruct

  • @PreDestroy

Contraintes

  • Une méthode par annotation

  • Pas d’arguments

  • Pas d’exception checked

  • Type de retour void

  • Méthode d’instance

  • Peut être final

  • Aucune contrainte sur la visibilité

Intercepteurs

An interceptor is a class used to interpose in method invocations or lifecycle events that occur in an associated target class. The interceptor performs tasks, such as logging or auditing, that are separate from the business logic of the application and are repeated often within an application.
http://bit.ly/javaee-interceptor
— Oracle documentation

Cas d’utilisation d’intercepteur

  • Tracer l’exécution d’une méthode

  • Tracer le temps d’exécution d’une méthode

  • etc.

interceptor

Création de l’annotation

@InterceptorBinding
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface Logged { }

Création de l’implémentation

ch.frankel.LoggingInterceptor
@Interceptor
@Logged
public class LoggingInterceptor {

  @AroundInvoke
  public Object log(InvocationContext ctx) throws Exception {
    long start = System.currentTimeMillis();
    Object value = ctx.proceed();
    System.out.println(
        "[" + ctx.getMethod().getName() + "]: " +
        (System.currentTimeMillis() - start) + " ms");
    return value;
  }
}

Activation

META-INF/beans.xml
<beans>
  <interceptors>
    <class>
      ch.frankel.LoggingInterceptor
    </class>
  </interceptors>
</beans>

Utilisation

@Logged
@Stateless
public class FooService {
    public void bar() {

    }
}

Décorateurs

Decorators are outwardly similar to interceptors. However, they actually perform tasks complementary to those performed by interceptors. […​] Decorators, on the other hand, do perform business logic by intercepting business methods of beans.
http://bit.ly/javaee-decorator
— Oracle documentation

Exemple de décorateur

Appliquer un taux de TVA

Principes

  • Le décorateur doit implémenter la même interface que le bean décoré

  • Si plusieurs décorateurs s’appliquent, ils sont invoqués dans l’ordre du beans.xml

interface HasPrice {
    double getPrice();
}
public class Product implements HasPrice {

  private final String name;
  private final double price;

  public Product(String name, double price) {
    this.name = name;
    this.price = price;
  }

  @Override public double getPrice() {
    return price;
  }
}
@Decorator
public class ProductWithVat implements HasPrice {

  private final Product product;

  @Inject
  public ProductWithVat(@Delegate Product product) {
    this.product = product;
  }

  @Override public double getPrice() {
    return product.getPrice() * 1.077;
  }
}

Programmation évènementielle avec CDI

CDI implémente le pattern Observer

Principes

  • Injection du générateur d’évènements via CDI

    • Basé sur le type Event<T>

  • Aucune restriction sur le type de l'Observer

    • Méthode configurée via @Observe

  • Aucune restriction sur le type d’évènement

Exemple de code

public class Subject {

  private Event<String> event;

  @Inject
  public Subject(Event<String> event) {
    this.event = event;
  }

  public sendTimestampEvent(String message) {
    event.fire("[" + System.currentTimeMillis() + "]: "
      + message);
  }
}
public class Observer {

  public receiveEvent(@Observe String message) {
    System.out.println("Received " + message);
  }
}

Tips & tricks

  • Il est possible, mais déconseillé, de créer des messages à partir de types simples

  • Il est possible, mais déconseillé, de créer des annotations pour discriminer les évènements auxquels on veut s’abonner

  • Il est conseillé de plutôt créer des types dédiés