jsf & cdi - dreamteam @work
DESCRIPTION
Dank CDI-Spezifikation lassen sich JSF-basierte Webanwendungen deutlich eleganter und leichtgewichtiger programmieren. JSF-spezifische Workarounds können durch schichtenneutrale Lösungen ersetzt werden. Die Session zeigt anhand eines Migrationsszenarios, wie in wenigen Schritten aus einer Old-School-JSF-Anwendung eine State of the Art JSF-&-CDI-Anwendung werden kann – Überraschungen inklusive.Speaker: Lars Röwekamp19.04.2012 | 8:30 - 9:45 Uhr | JAX, MainzTRANSCRIPT
JSF & CDI: Dreamteam @WorkLars Röwekamp | CIO New Technologies
JSF & CDI: Dreamteam @WorkLars Röwekamp | CIO New Technologies
@mobileLarson@_openknowledge
Der „Klassiker“
Der „Klassiker“
TX
Java EE Web Framework
Java EE Service Framework
Java EE Persistence Framework
Der „Klassiker“
Wo liegt das Problem?
Heterogene Lösungen
eigene DI, Validierung, LifeCycle
Der „Klassiker“
eigene DI, Validierung, LifeCycle
eigene DI, Validierung, LifeCycle
Technology drivesBusiness
Der „Klassiker“
TX
UC: „Current User“ anzeigen
Der „Klassiker“
TX
Einen JSF ManagedBean
Controller, bitte.
Und für mich einen EJB Service.
Enitity X mit ID Y,wenn möglich.
UC: „Current User“ anzeigen
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
private User user; // plus getter and setter
@ManagedProperty(value=“#{authenticationController}“) private authenticationControllerMB authenticationController;
@EJB private UserService userService
@EJB private MailService mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
private User user; // plus getter and setter
@ManagedProperty(value=“#{authenticationController}“) private authenticationControllerMB authenticationController;
@EJB private UserService userService
@EJB private MailService mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
String basiertes IoC
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
private User user; // plus getter and setter
@ManagedProperty(value=“#{authenticationController}“) private authenticationControllerMB authenticationController;
@EJB private UserService userService
@EJB private MailService mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
String basiertes IoC
Infrastrutur Injection
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
private User user; // plus getter and setter
@ManagedProperty(value=“#{authenticationController}“) private authenticationControllerMB authenticationController;
@EJB private UserService userService
@EJB private MailService mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
String basiertes IoC
Infrastrutur Injection
Technology Injection
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
private User user; // plus getter and setter
@ManagedProperty(value=“#{authenticationController}“) private authenticationControllerMB authenticationController;
@EJB private UserService userService
@EJB private MailService mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
String basiertes IoC
Infrastrutur Injection
String basierte Navi
Technology Injection
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
...
public String create() { if (userService.checkForFreeUsername(user.username)) { userService.create(user); mailService.sendWelcomeMail(user);
User loggedInUser = authenticationController.getLoggedInUser(); if (User.ROLE_TRAINEE.equals(loggedInUser.getRole()) { trackingService.trackAction(TrackAction.USER_CREATED, user); } return “USER_CREATED“; // or userSuccessfulCreated.xhtml } else { return “DUPLICATE_USERNAME“; // or errorDuplicateUserName.xhtml } } } String basierte Navi
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
...
public String create() { if (userService.checkForFreeUsername(user.username)) { userService.create(user); mailService.sendWelcomeMail(user);
User loggedInUser = authenticationController.getLoggedInUser(); if (User.ROLE_TRAINEE.equals(loggedInUser.getRole()) { trackingService.trackAction(TrackAction.USER_CREATED, user); } return “USER_CREATED“; // or userSuccessfulCreated.xhtml } else { return “DUPLICATE_USERNAME“; // or errorDuplicateUserName.xhtml } } }
Monster UseCase
String basierte Navi
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable{
private User user;
... public String askForDeleteConfirmation(User userToDelete) { user = userToDelete; return “USER_READY_FOR_DELETE“; // or userDeleteConfirmation.xhtml }
public String deleteAfterConfirmation() { userService.delete(user); ... // do some more stuff, e.g. sending an email, tracking, ... user = null; return “USER_DELETED“; // or userSuccessfulDeleted.xhtml }
}String basierte Navi
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable{
private User user;
... public String askForDeleteConfirmation(User userToDelete) { user = userToDelete; return “USER_READY_FOR_DELETE“; // or userDeleteConfirmation.xhtml }
public String deleteAfterConfirmation() { userService.delete(user); ... // do some more stuff, e.g. sending an email, tracking, ... user = null; return “USER_DELETED“; // or userSuccessfulDeleted.xhtml }
}
Session statt „Conversation“
Scope Mismatch
String basierte Navi
We needHelp!
Der „Klassiker“ revisted
Der „Klassiker“ revisted
Der „Klassiker“ revisted
CDI 1.0
CDI 1.0 ...aus 10.000 m
CDI Kickstart
JSR-299 Context & Dependency Injection for the Java EE platform
uses JSR-330 Dependency Injection for Java
CDI für Java EE
Java SE Java EE
> DI / IoC lite „Java EE without EJB“
> DI / IoC advanced LifeCycle Management und Scoping
> DI / IoC eXtreme Typensicherheit und lose Koppelung
> DI / IoC open Extension-Mechanismus
CDI Features
CDI Kickstart> LifeCycle Management & Scoping
> Scope sensitive Injection> Automatisches Cleanup
> Built-in Scopes> Request, Conversation, Session, Application
> Built-in Pseudo Scopes> Dependant, Singleton
Request Scope
@RequestScopedclass MyBeanA
@RequestScopedclass MyBeanB
@RequestScopedclass MyModel
@RequestScopedclass MyModel
@Inject
@Inject@RequestScopedclass MyBeanB
@Inject
Request 2
Request 1
Session Scope
Request 2
@RequestScopedclass MyBeanA
@RequestScopedclass MyBeanB
@SessionScopedclass MyModel
@Inject
Request 1
@RequestScopedclass MyBeanB
@Inject
@Inject
Session
@SessionScopedclass MyBeanA
@Inject@RequestScopedclass MyModel
Spezialfall
@SessionScopedclass MyBeanA
Proxy forclass MyModel
@Inject
@RequestScopedclass MyModel
Injection Target Contextual Reference Contextual Instance
businessMethod() Lookup or create
return
businessMethod()
returnreturn
Spezialfall
Dependent Scope (Default)
@RequestScopedclass MyBeanA
@SessionScopedclass MyBeanB
@Dependentclass MyModel
@Dependentclass MyModel
@Inject
@Inject
Request 1
> KEIN Proxy und Default!
CDI Kickstart> Typesafe Injection
> @Inject und #{...} via Type und Name > @Qualifer als zusätzliche Metadaten> @Inject @Current User loggedInUser
> @Stereotype als „Meta Annotation“> @Model entspricht @Named @RequestScoped
> @Alternative als „Switch“
CDI Kickstart> Typesafe Injection
> @Inject und #{...} via Type und Name > @Qualifer als zusätzliche Metadaten> @Inject @Current User loggedInUser
> @Stereotype als „Meta Annotation“> @Model entspricht @Named @RequestScoped
> @Alternative als „Switch“
CDI Kickstart @SessionScoped public class AuthenticationController
implements Serializable {
private User authenticatedUser;
public String authenticate() { ... }
public User getAuthenticatedUser() { return authenticatedUser; } }
CDI Kickstart @SessionScoped public class AuthenticationController
implements Serializable {
private User authenticatedUser;
public String authenticate() { ... }
public User getAuthenticatedUser() { return authenticatedUser; } }
@Produces @Current User
@Produces @Current @RequestScoped
CDI Kickstart @SessionScoped public class AuthenticationController
implements Serializable {
private User authenticatedUser;
public String authenticate() { ... }
public User getAuthenticatedUser() { return authenticatedUser; } }
Aufruf: @Inject @Current User
@Produces @Current User
@Produces @Current @RequestScoped
CDI Kickstart @SessionScoped public class AuthenticationController
implements Serializable {
private User authenticatedUser;
public String authenticate() { ... }
public User getAuthenticatedUser() { return authenticatedUser; } }
Aufruf: @Inject @Current User
@Produces @Current User
@Produces @Current @RequestScoped @Named(“loggedInUser“)
Aufruf: #{loggedInUser}
CDI Kickstart
@Qualifier@Target({FIELD, PARAMETER, METHOD, TYPE})@Retention(RUNTIME)public @interface Current { }
Self-made Qualifier
> Interceptors > Realisierung orthogonaler Aufgaben („AOP“)> Around Advice unterbricht normalen Ablauf> Caching, Security, Transactions, Performance
> Events & Observer> CDI erlaubt Injection einer Event Source> Event Source „feuert“ eigenes Event> Observer „hört“ auf Event
CDI Kickstart> Lose Koppelung
JSF & CDI: Dreamteam @Work
JSF & CDI: Dreamteam @Work
Refactoring - Step 1
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable {
private User user; // plus getter and setter
@ManagedProperty(value=#{authenticationController}) private authenticationControllerMB authenticationController;
@EJB private Stateless userService
@EJB private Stateless mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
String basiertes IoC
Infrastrutur Injection
Scope Mismatch
String basierte Navi
Technology Injection
> via @Inject „technologieneutrale“ Injection> via @Qualifier und „Type“ typesafe Injection> via @Named Zugriff aus EL
CDI @Work> Refactoring - Step 1: CDI only
Der „Klassiker“ refactored @Named(“userController“) @SessionScoped public class UserController implements Serializable {
private User user; // plus getter and setter
@Inject @Current User private User loggedInUser;
@Inject private UserService userService
@Inject private MailService mailService
... // some more services needed public String create() { .. }
public String askForDeleteConfirmation() { ... }
public String deleteAfterConfirmation() { ... }
}
String basierte Navi
Scope Mismatch
JSF & CDI: Dreamteam @Work
JSF & CDI: Dreamteam @Work
Refactoring - Step 2
Der „Klassiker“
<html ...> <h:body> Vorname: <h:outputText value="#{userController.user.firstname}"/> Name: <h:outputText value="#{userController.user.lastname}"/>
</h:body> </html>
Infrastruktur Injection in der View
> via @Named und @Produces Zugriff aus EL
CDI @Work> Refactoring - Step 2: View
Der „Klassiker“ refactored @Named(“userController“) @SessionScoped public class UserController implements Serializable {
private User user;
@Produces @ResquestScoped @Named("selectedUser") public getUser() { return user; } ... }
Der „Klassiker“ refactored
<html ...> <h:body> Vorname: <h:outputText value="#{selectedUser.firstname}"/> Name: <h:outputText value="#{selectedUser.lastname}"/>
</h:body> </html>
fachliche Injection in der View
JSF & CDI: Dreamteam @Work
JSF & CDI: Dreamteam @Work
Refactoring - Step 3
Der „Klassiker“ @Named(“userController“) @SessionScoped public class UserController implements Serializable {
... public String create() { if (userService.checkForFreeUsername(user.username)) { userService.create(user); mailService.sendWelcomeMail(user);
User loggedInUser = authenticationController.getLoggedInUser(); if (User.ROLE_TRAINEE.equals(loggedInUser.getRole()) { trackingService.trackAction(TrackAction.USER_CREATED, user); } return “USER_CREATED“; // or userSuccessfulCreated.xhtml } else { return “DUPLICATE_USERNAME“; // or errorDuplicateUserName.xhtml } } }
Monster UseCase
String basierte Navi
> via BeanValidation Logik-Validierung (sorry, OT)> via Event und @Observes UseCase Splittung
CDI @Work> Refactoring - Step 3: Split UseCase
Der „Klassiker“ refactored @Named(“userController“) @SessionScoped public class UserController implements Serializable {
private User user; ... public String create() { userService.create(user); // main use case ... // trigger sub use cases return “USER_CREATED“; // userSuccessfulCreated.xhtml } }
OT: Double usernamecheck via BeanValidation
Der „Klassiker“ refactored @Named(“userController“) @SessionScoped public class UserController implements Serializable {
@Inject @Created Event<User> userCreatedEventSource;
private User user; ... public String create() { userService.create(user); // main use case userCreatedEventSource.fire(user); // trigger sub use cases return “USER_CREATED“; // userSuccessfulCreated.xhtml } }
OT: Double usernamecheck via BeanValidation
Loosly coupled UseCase via Event
Der „Klassiker“ refactored public class MailService {
public void sendWelcomeMail(@Observes @Created User newUser) { ... // do some work }
... }
Event Consumer
public class TrackingService {
@Inject @Current User loggedInUser;
public void trackUserCreated(@Observes @Created User newUser) { if (User.ROLE_TRAINEE.equals(loggedInUser.getRole()) { this.trackAction(TrackAction.USER_CREATED, user); } } ... }
Event Consumer
Der „Klassiker“ refactored public class MailService {
public void sendWelcomeMail(@Observes(during == AFTER_SUCCESS) @Created User newUser) { ... // do some work }
... }
Event Consumer
public class TrackingService {
@Inject @Current User loggedInUser;
public void trackUserCreated(@Observes(during == AFTER_SUCCESS) @Created User newUser) { if (User.ROLE_TRAINEE.equals(loggedInUser.getRole()) { this.trackAction(TrackAction.USER_CREATED, newUser); } } ... }
Event Consumer
JSF & CDI: Dreamteam @Work
JSF & CDI: Dreamteam @Work
Refactoring - Step 4
Der „Klassiker“
Der „Klassiker“
TX
Der „Klassiker“TX
> via @Interceptor UseCase Transaktion
CDI @Work> Refactoring - Step 4:
Der „Klassiker“ refactored @Named(“userController“) @SessionScoped public class UserController implements Serializable {
...
@Transactional public String create() { userService.create(user); // main use case userCreatedEventSource.fire(user); // Created Event return “USER_CREATED“; // Navigation } }
Transactional UseCase
Der „Klassiker“ refactored @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Transactional { public TransactionalType value() ! default TransactionalType.REQUIRED; }
Transactional Annotation
Der „Klassiker“ refactored @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface Transactional { public TransactionalType value() ! default TransactionalType.REQUIRED; }
? Transactional Annotation
Der „Klassiker“ refactored @Transactional @Interceptor public class TransactionInterceptor {
@Inject private UserTransaction utx;
@AroundInvoke public Object applyTransaction(InvocationContext ic) throws Throwable {
... // implement utx.begin() ic.proceed(); // call original method ... // implement utx.commit()
} }
Transactional Interceptor
JSF & CDI: Dreamteam @Work
JSF & CDI: Dreamteam @Work
Refactoring - Step 5
Der „Klassiker“ @ManagedBean(name=“userController“) @SessionScoped public class UserControllerMB implements Serializable{
private User user;
... public String askForDeleteConfirmation(User userToDelete) { user = userToDelete; return “USER_READY_FOR_DELETE“; // or userDeleteConfirmation.xhtml }
public String deleteAfterConfirmation() { userService.delete(user); ... // do some more stuff, e.g. sending an email, tracking, ... user = null; return “USER_DELETED“; // or userSuccessfulDeleted.xhtml }
}
Session statt Conversation
Scope Mismatch
> via @ConversationScoped Wizard
CDI @Work> Refactoring - Step 4:
CDI @Work
@RequestScopedclass MyBeanA
@ConversationSopedclass MyWizard
@Inject Conversation conv; // inside „start“ Methode conv.begin();
// inside „end“ Methode conv.end();
@RequestScopedclass MyBeanB
@Inject
@Inject
Der „Klassiker“ refactored @Named(“userController“) @ConversationScoped public class UserController implements Serializable{
private User user;
@Inject Conversation conversation; ... public String askForDeleteConfirmation(User userToDelete) { conversation.begin(); user = userToDelete; return “USER_READY_FOR_DELETE“; // or userDeleteConfirmation.xhtml }
public String deleteAfterConfirmation() { userService.delete(user); ... // do some more stuff, e.g. fire “user deleted“ CDI event conversation.end(); return “USER_DELETED“; // or userSuccessfulDeleted.xhtml }
}
Der „Klassiker“ refactored
TX
Der „Klassiker“ refactored
TX
Der „Klassiker“ refactoredTX
Mis
Pie ces
sing
Bessere Conversationen
Built-in Transaktionen
weitere Scopes
typesafe Navigation
und vieles mehr ...
Missing Pieces