d'AspectJ. Je trouve ce paradigme de développement d'une rare élégance et il est aujourd'hui suffisamment outillé (intégration aux IDEs, intégration à Maven...) pour pouvoir parsemer quelques aspects de ci de là.
Voilà donc quelques problématiques Swing récurrentes qu'il est possible d'outiller avec AOP.
L'objectif est simple : reproduire l'IOC au niveau de l'IHM et notamment dans les actions (en fait les ActionListener
branchés sur les boutons). Dès lors on souhaite injecter une implémentation du service (définie par exemple comme un bean Spring) sur toute propriété qui aurait l'annotation custom @Inject
.
Par exemple, injecter le service myService
dans l'action suivante (on peut faire mieux mais dans cet exemple, le bean est injecté sur la base du nom et non du type) :
public class MyActionListener implements ActionListener{
@Inject
private MyService myService
...
}
L'aspect suivant permet l'injection du Bean Spring nommé myService
public aspect SpringInjectAspect {
pointcut fieldsInjection() : within(com.mypackage..*)
execution(public void java.awt.event.ActionListener+.actionPerformed(..));
before() : fieldsInjection() {
Class currentClass = thisJoinPoint.getThis().getClass();
Object currentObject = thisJoinPoint.getThis();
//to inject bean spring even in super class property
while (!currentClass.equals(ActionListener.class)) {
injectBean(currentClass, currentObject);
//for te next step
currentClass = currentClass.getSuperclass();
}
}
private void injectBean(Class currentClass, Object currentObject) {
Field[] fields = currentClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Annotation associatedAnnotation = fields[i].getAnnotation(Inject.class);
if (associatedAnnotation != null) {
try {
//get the bean by the field name
Object implementationObject = SpringHelper.getApplicationContext().getBean(
fields[i].getName());
try {
fields[i].setAccessible(true);
fields[i].set(currentObject, implementationObject);
}
catch (IllegalArgumentException e) {
... do what you need to do
}
catch (NoSuchBeanDefinitionException e) {
//log the fact the asked bean do not exist
throw e;
}
}
}
}
}
L'utilisation d'un mécanisme de binding type jGoodies peut-être intéressant mais quoiqu'il en soit, il passe souvent par l'écriture de code supplémentaire notamment dans les setter :
public void setBooleanValue(boolean newValue) {
boolean oldValue = booleanValue;
booleanValue = newValue;
changeSupport.firePropertyChange("booleanValue", oldValue, newValue);
}
Dans cet exemple, il est nécessaire de rajouter dans chaque setter, l'appel à la méthode fireProperyChange
en précisant nouvelle et ancienne valeur et surtout le nom de la propriété (ce qui bien entendu sera source d'erreur puisque ce code sera copié-collé de setter en setter...).
Là encore AOP nous aide en permettant d'enrichir un setter "classique" (i.e. qui ne fait que mettre à jour la propriété). Ainsi le setter se définit classiquement :
public void setBooleanValue(boolean newValue) {
booleanValue = newValue;
}
et l'aspect suivant se charge du reste :
public aspect jGoodiesBindingAspect {
pointcut propertyEnhancement() : within(com.mypackage..*)
&& execution (public void com.jgoodies.binding.beans.Model+.set*(..))
void around() : propertyEnhancement() {
Object[] args = thisJoinPoint.getArgs();
String propertyName = thisJoinPoint.getSignature().getName().substring(3);
Object oldValue = null;
try {
Method propertyGetter = thisJoinPoint.getThis().getClass()
.getMethod("get" + propertyName);
oldValue = propertyGetter.invoke(thisJoinPoint.getThis());
Object newValue = args[0];
proceed();
// if no error occurs
Method firePropertyChangeMethod = com.jgoodies.binding.beans.Model.class.getDeclaredMethod(
"firePropertyChange", String.class, Object.class, Object.class);
firePropertyChangeMethod.setAccessible(true);
propertyName = org.apache.commons.lang.StringUtils.uncapitalize(propertyName);
firePropertyChangeMethod.invoke(thisJoinPoint.getThis(), propertyName, oldValue, newValue);
}
catch (NoSuchMethodException
...do what you need to do
}
}
Dans ce cas, le pointcut est défini sur toutes les méthodes qui commencent par set
de n'importe quelle classe héritant de com.jgoodies....Model
.
Il s'agit là encore d'une problématique hyper classique : comment rendre "inaccessible" (comprendre non modifiable, non utilisable) des fonctions (des boutons, des menus...) d'une IHM Swing. On parle donc ici de "griser" (ie. myComponent.setEnabled(...)
) les composants graphiques soumis à une habilitation.
La première étape consiste à définir une annotation permettant de définir les rôles sur un JComponent
. Rien de génial là dedans. Il est possible de l'utiliser ainsi et de définir que myComboBox
est accessible pour les utilisateurs ayant les rôles "consultation-level-1" et "consultation-level-2"
@Authorize(roles={"consultation-level-1, consultation-level-2"})
private JComponent myComboBox;
L'aspect suivant prend en charge le "grisage/dégrisage" des widgets graphiques suivant le rôle
public aspect AuthorizationAspect {
public pointcut authorized_gui() : within(com.mypackage..*)
&& set(@Authorize * *..*.*);
after() : authorized_gui() {
String pointCutKind = thisJoinPoint.getKind();
if (JoinPoint.FIELD_SET.equals(pointCutKind)) {
java.lang.reflect.Field field = ((FieldSignature) thisJoinPoint.getSignature()).getField();
try {
field.setAccessible(true);
Object theField = field.get(thisJoinPoint.getThis());
if (theField instanceof JComponent) {
Authorize authorization = field.getAnnotation(Authorize.class);
String[] roles = authorization.roles();
//ask your security context holder the role the user have
((JComponent) theField).setEnabled(SecurityContextHolder.getInstance()
.isUserInRole(roles));
}
}
catch (IllegalAccessException e) {
throw new TechnicalException(e);
}
}
}
}
La subtilité dans cet aspect réside dans le set(@Authorize * *..*.*)
qui permet d'attraper l'instanciation (et non pas l'appel au constructeur) de toutes les propriétés qui ont l'annotation spécifiée.
Voilà donc quelques problématiques qu'il est possible de régler de façon assez simple, élégante et peu intrusive sur le code grâce à AOP. D'autres utilisations existent. La plus courante est certainement de gérer de façon automatique toutes les exceptions non typées - qui dans le cas d'une IHM donne typiquement lieu à l'affichage (dans une popup ou non) d'un message du type "une erreur a eu lieu, veuillez contacter votre administrateur". D'autres utilisations encore peu répandues visent à valider certaines règles de codage propre à votre projet (via l'utilisation de declare warning
et declare error
).
Et vous, voyez vous d'autres problématiques qu'il serait intéressant de gérer de la sorte?