Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Aspect Oriented Programming (Droidcon Paris 2015 BarCamp)

Aspect Oriented Programming (Droidcon Paris 2015 BarCamp)

Add magic to your code and remove boilerplate

Xavier Gouchet

November 09, 2015
Tweet

More Decks by Xavier Gouchet

Other Decks in Programming

Transcript

  1. Aspect Oriented
    Programming
    Add magic to your code and remove boilerplate
    Droidcon Paris 2015 Barcamp

    View full-size slide

  2. About...
    Xavier Gouchet
    Android software engineer
    (Tools, architecture, core, …)
    @xgouchet on Github, StackOverflow, Twitter, …
    speakerdeck.com/xgouchet

    View full-size slide

  3. 1.
    Introduction
    What is it ? What is it not ?

    View full-size slide

  4. It’s NOT ...
    ◎ A new programming language
    ◎ Something to replace OOP
    ◎ The solution to all your problems !

    View full-size slide

  5. It is a tool to ...
    ◎ Enhance modularity
    ◎ Separate cross cutting concerns
    ◎ Focus on business logic
    ◎ Reduce noise in the source code

    View full-size slide

  6. 2.
    Aspects Oriented
    Programming
    The general idea

    View full-size slide

  7. void foo() {
    Log.v("Tag", "foo");
    beginTransaction();
    try {
    bar();
    // ...
    setTransactionSuccessful();
    } catch (Exception e) {
    reportError(e);
    } finally {
    endTransaction();
    }
    Log.v("Tag", "foo done");
    }
    Before / After

    View full-size slide

  8. void foo() {
    Log.v("Tag", "foo");
    beginTransaction();
    try {
    bar();
    // ...
    setTransactionSuccessful();
    } catch (Exception e) {
    reportError(e);
    } finally {
    endTransaction();
    }
    Log.v("Tag", "foo done");
    }
    Before / After

    View full-size slide

  9. void foo() {
    Log.v("Tag", "foo");
    beginTransaction();
    try {
    bar();
    // ...
    setTransactionSuccessful();
    } catch (Exception e) {
    reportError(e);
    } finally {
    endTransaction();
    }
    Log.v("Tag", "foo done");
    }
    Before / After

    View full-size slide

  10. void foo() {
    Log.v("Tag", "foo");
    beginTransaction();
    try {
    bar();
    // ...
    setTransactionSuccessful();
    } catch (Exception e) {
    reportError(e);
    } finally {
    endTransaction();
    }
    Log.v("Tag", "foo done");
    }
    Before / After

    View full-size slide

  11. void foo() {
    Log.v("Tag", "foo");
    beginTransaction();
    try {
    bar();
    // ...
    setTransactionSuccessful();
    } catch (Exception e) {
    reportError(e);
    } finally {
    endTransaction();
    }
    Log.v("Tag", "foo done");
    }
    Before / After

    View full-size slide

  12. void foo() {
    Log.v("Tag", "foo");
    beginTransaction();
    try {
    bar();
    // ...
    setTransactionSuccessful();
    } catch (Exception e) {
    reportError(e);
    } finally {
    endTransaction();
    }
    Log.v("Tag", "foo done");
    }
    Before / After

    View full-size slide

  13. @LogMethod
    @SafeTransaction
    void foo() {
    bar();
    // ...
    }
    Before / After

    View full-size slide

  14. Cross-cutting
    concerns
    Logging, performances,
    security, transaction,
    validation, serialization,
    cache, notification,
    multithreading, ...

    View full-size slide

  15. .class ul { display: inline;}
    #id p {font-style: italic;}
    a:hover {color: #FF4630; }
    input[type=text] {font-family: monospace;}
    CSS + HTML

    View full-size slide

  16. .class ul { display: inline;}
    #id p {font-style: italic;}
    a:hover {color: #FF4630; }
    input[type=text] {font-family: monospace;}
    CSS + HTML
    Attributes
    behaviors/appearance applied to...

    markup nodes in the source code,
    defined by...
    Selectors
    rules and patterns

    View full-size slide

  17. Vocabulary
    Advices
    behaviors applied to...
    JoinPoints
    positions in the source code,
    defined by...
    PointCuts
    rules and patterns
    Attributes
    behaviors/appearance applied to...

    markup nodes in the source code,
    defined by...
    Selectors
    rules and patterns

    View full-size slide

  18. 4.
    Hello AOP
    Let’s write an aspect

    View full-size slide

  19. Bytecode
    weaving
    AspectJ

    View full-size slide

  20. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  21. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  22. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  23. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  24. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  25. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  26. Hello World
    @interface TraceLog {}
    public class MyClass {
    @TraceLog
    public void foo() {
    // ...
    }
    }
    @Aspect
    public class TraceLogAspect {
    @Pointcut("execution(@TraceLog * *(..))")
    public void traceLogMethod(){}
    @Before("traceLogMethod())")
    public void advice(JoinPoint jp) {
    Log.v("AspectTag", "> " + jp.getSignature());
    }
    }

    View full-size slide

  27. 5.
    Concrete examples
    Beyond the logging “Hello world”

    View full-size slide

  28. Performance Monitoring
    @Aspect
    public class PerformanceMonitorAspect {
    @Pointcut("execution(@Monitor * *(..))")
    public void monitoredMethod(){}
    @Around("monitoredMethod())")
    public Object advice(ProceedingJoinPoint pjp) {
    long start = System.nanoTime();
    Object result = pjp.proceed();
    long end = System.nanoTime();
    Log.i("Monitored", jp.toString + " : "
    + (end - start) + "ns");
    return result;
    }
    }

    View full-size slide

  29. Performance Monitoring
    @Aspect
    public class PerformanceMonitorAspect {
    @Pointcut("execution(@Monitor * *(..))")
    public void monitoredMethod(){}
    @Around("monitoredMethod())")
    public Object advice(ProceedingJoinPoint pjp) {
    long start = System.nanoTime();
    Object result = pjp.proceed();
    long end = System.nanoTime();
    Log.i("Monitored", jp.toString + " : "
    + (end - start) + "ns");
    return result;
    }
    }

    View full-size slide

  30. Performance Monitoring
    @Aspect
    public class PerformanceMonitorAspect {
    @Pointcut("execution(@Monitor * *(..))")
    public void monitoredMethod(){}
    @Around("monitoredMethod())")
    public Object advice(ProceedingJoinPoint pjp) {
    long start = System.nanoTime();
    Object result = pjp.proceed();
    long end = System.nanoTime();
    Log.i("Monitored", jp.toString + " : "
    + (end - start) + "ns");
    return result;
    }
    }

    View full-size slide

  31. Memoization
    @Aspect
    public class MemoizationAspect {
    @Pointcut("execution(@Memoize * *(..))")
    public void memoizedMethod() {}
    @Around("memoizedMethod()")
    public Object advice(ProceedingJoinPoint pjp) {
    Object cached = getCached(pjp);
    if (cached == null) {
    cached = pjp.proceed();
    cacheResult(pjp, cached);
    }
    return cached;
    }
    }

    View full-size slide

  32. Database Transaction
    @Aspect
    public class SQLiteTransactionAspect {
    @Pointcut("execution(@SQLiteTransaction * *(..))")
    public void transactionMethod(){}
    @Around("transactionMethod() && args(db)")
    public void advice(ProceedingJoinPoint jp,
    SQLiteDatabase db) {
    db.beginTransaction();
    try {
    jp.proceed();
    db.setTransactionSuccessful();
    } catch (SQLiteException e) {
    Log.e("SQLite", "Error at" + jp.toString(), e);
    } finally {
    db.endTransaction();
    }
    }
    }

    View full-size slide

  33. Database Transaction
    @Aspect
    public class SQLiteTransactionAspect {
    @Pointcut("execution(@SQLiteTransaction * *(..))")
    public void transactionMethod(){}
    @Around("transactionMethod() && args(db)")
    public void advice(ProceedingJoinPoint jp,
    SQLiteDatabase db) {
    db.beginTransaction();
    try {
    jp.proceed();
    db.setTransactionSuccessful();
    } catch (SQLiteException e) {
    Log.e("SQLite", "Error at" + jp.toString(), e);
    } finally {
    db.endTransaction();
    }
    }
    }

    View full-size slide

  34. Database Transaction
    @Aspect
    public class SQLiteTransactionAspect {
    @Pointcut("execution(@SQLiteTransaction * *(..))")
    public void transactionMethod(){}
    @Around("transactionMethod() && args(db)")
    public void advice(ProceedingJoinPoint jp,
    SQLiteDatabase db) {
    db.beginTransaction();
    try {
    jp.proceed();
    db.setTransactionSuccessful();
    } catch (SQLiteException e) {
    Log.e("SQLite", "Error at" + jp.toString(), e);
    } finally {
    db.endTransaction();
    }
    }
    }

    View full-size slide

  35. Validation
    @interface Validation { String regexp() default ".*";
    }
    public class UserAccount {
    @Validation(regexp = "[a-zA-Z]{6,}")
    public void setUserName(String userName) {
    mUserName = userName;
    }
    @Validation(regexp = "^(?=.*\d)(?=.*[A-Z]).{8,}$")
    public void setPassword(String password) {
    mPassword = password;
    }
    }

    View full-size slide

  36. Validation
    @interface Validation { String regexp() default ".*";
    }
    public class UserAccount {
    @Validation(regexp = "[a-zA-Z]{6,}")
    public void setUserName(String userName) {
    mUserName = userName;
    }
    @Validation(regexp = "^(?=.*\d)(?=.*[A-Z]).{8,}$")
    public void setPassword(String password) {
    mPassword = password;
    }
    }

    View full-size slide

  37. Validation
    @Aspect
    public class ValidationAspect {
    @Before("execution(@Validation * *(..))
    && args(input) && @annotation(v)")
    public void advice(JoinPoint jp,
    String input,
    Validation v) {
    Pattern pattern = Pattern.compile(v.regexp());
    Matcher matcher = pattern.matcher(input);
    if (!matcher.matches()) {
    throw new InvalidInputException();
    }
    }
    }

    View full-size slide

  38. Validation
    @Aspect
    public class ValidationAspect {
    @Before("execution(@Validation * *(..))
    && args(input) && @annotation(v)")
    public void advice(JoinPoint jp,
    String input,
    Validation v) {
    Pattern pattern = Pattern.compile(v.regexp());
    Matcher matcher = pattern.matcher(input);
    if (!matcher.matches()) {
    throw new InvalidInputException();
    }
    }
    }

    View full-size slide

  39. Validation
    @Aspect
    public class ValidationAspect {
    @Before("execution(@Validation * *(..))
    && args(input) && @annotation(v)")
    public void advice(JoinPoint jp,
    String input,
    Validation v) {
    Pattern pattern = Pattern.compile(v.regexp());
    Matcher matcher = pattern.matcher(input);
    if (!matcher.matches()) {
    throw new InvalidInputException();
    }
    }
    }

    View full-size slide

  40. What about the stack trace ?
    com.example.InvalidInputException
    at com.example.ValidationAspect.advice(ValidationAspect.java:35)
    at com.example.MyClass.setPassword(UserAccount.java:83)
    at com.example.test.UserAccount.onSubmit(UserAccount.java:42)
    ... 5 more

    View full-size slide

  41. Undo / Redo
    interface IUndoableAction {
    public void undo();
    public void redo();
    }
    class FieldAction implements IUndeoableAction {
    Field mField; Object mTarget, mOldValue, mNewValue;
    public FieldAction(Object target, Field field,
    Object oldValue, Object newValue) {
    mField = field;
    mTarget = target;
    mOldValue = oldValue;
    mNewValue = newValue;
    }
    public void undo(){ field.set(target, oldValue); }
    public void redo(){ field.set(target, newValue); }
    }

    View full-size slide

  42. Undo / Redo
    @Aspect
    public class UndoActionAspect {
    @Pointcut("within(com.example.model.*)")
    public void withinModelClass() {}
    @Before("withinModelClass() && set(public * *)
    && args(newValue) && target(target)")
    public void advice(JoinPoint jp,
    Object target,
    Object newValue) {
    Field field = getField(jp, target);
    Object oldValue = field.get(target);
    UndoManager.getInstance()
    .record(new FieldAction(target, field,
    oldValue, newValue);
    }
    }

    View full-size slide

  43. Gatekeeping
    enum Role { DEV, BETA, PUBLIC }
    @interface GateKeep { Role[] allowed(); }
    public class HomeActivity {
    @GateKeep(allowed = {DEV, BETA})
    public void setupMaterialUI() {
    // ...
    }
    @GateKeep(allowed = {DEV})
    public void startJukeboxPlayer() {
    // ...
    }
    }

    View full-size slide

  44. Gatekeeping
    @Aspect
    public class GateKeepAspect {
    @Around("execution(@GateKeep * *(..)) &&
    @annotation(gk)")
    public void advice(ProceedingJoinPoint jp,
    Gatekeep gk) {
    Role userRole = UserService.getInstance()
    .getCurrentUser()
    .getRole();
    if (Arrays.asList(gate.allowedRoles())
    .contains(userRole)) {
    jp.proceed();
    }
    }
    }

    View full-size slide

  45. Multithreading
    @Aspect
    public class UIThreadAspect {
    final Handler mMainHandler =
    new Handler(Looper.
    getMainLooper());
    @Around("execution(@RunOnUIThread void *(..))")
    public void advice(final ProceedingJoinPoint jp) {
    if (Looper.myLooper() == Looper.getMainLooper())
    {
    jp.proceed();
    } else {
    mMainHandler.post(new Runnable() {
    public void run() {
    jp.proceed();
    }
    });
    }
    }

    View full-size slide

  46. and more...
    Retry failed request
    Read/write locks
    Boilerplate code

    View full-size slide

  47. 6.
    More than just
    advices

    View full-size slide

  48. @Aspect
    @DeclarePrecedence("around1, before2, after3,
    around4, before5, after6")
    public class OrderingAspect {
    // Applied to the same pointcut, the result will be
    // around1 {
    // before2
    // around4 {
    // before5
    // joinpoint
    // after3
    // }
    // }
    // after4
    }
    Ordering advices

    View full-size slide

  49. Compile time checks
    @Aspect
    public class CoreCheckAspect {
    @Pointcut("within(com.example.core.*)")
    public void withinCorePackage() {}
    @Pointcut("call(* com.example.ui..*..*(..))")
    public void callToUIMethod() {}
    @DeclareError("withinCorePackage() &&
    callToUIMethod()")
    static final String SEP_OF_CONCERNS =
    "Core classes should not call UI methods";
    }

    View full-size slide

  50. Mixins
    interface INamed {
    String getName();
    }
    class NamedImpl implements INamed {
    private String mName;
    public NamedImpl(Object namedObject) {
    mName = namedObject.toString();
    }
    public String getName() {
    return mName;
    }
    }

    View full-size slide

  51. Mixins
    @Aspect
    public class NamedAspect {
    @DeclareMixin("com.example..*")
    public static INamed makeNamed(Object namedObject)
    {
    return new NamedImpl(namedObject);
    }
    }
    class Foo {
    public void hello (){
    Log.i("Hello", "My name is "
    + ((INamed) this).getName());
    }
    }

    View full-size slide

  52. 5.
    General advices
    no pun intended...

    View full-size slide



  53. “Forever in debt
    to your priceless advice”
    Nirvana

    View full-size slide

  54. Things to remember
    ◎ Singleton by default
    ◎ Inheritance, abstracts, generics,
    inner classes…
    ◎ Android flavors
    ◎ AOP is real programming

    View full-size slide


  55. “It’s a kind of magic”
    Queen

    View full-size slide

  56. Wizardry or Witchcraft
    @Aspect
    public class PreventNPEAspect {
    @Pointcut("within(com.example.model.*)")
    public void withinModelClass() {}
    @Around("withinModelClass() && execution(* *(..))")
    public void advice(ProceeJoinPoint jp) {
    Object[] args = jp.getArgs();
    for (int i = 0; i < args.length; i++) {
    if (args[i] == null) {
    args[i] = "";
    }
    }
    jp.proceed(args);
    }
    }

    View full-size slide

  57. Wizardry or Witchcraft
    @Aspect
    public class PreventNPEAspect {
    @Around("execution(@PreventStringNPE * *(..))")
    public void advice(ProceeJoinPoint jp) {
    Object[] args = jp.getArgs();
    for (int i = 0; i < args.length; i++) {
    if (args[i] == null) {
    args[i] = "";
    }
    }
    jp.proceed(args);
    }
    }

    View full-size slide

  58. Errors to avoid
    ◎ Pokeball : gotta catch’em all
    Pointcuts must be as precise as possible
    ◎ Octopuss : arms reaching everywher
    Pointcuts matching unrelated joinpoints
    ◎ Asp’ception : because aspect is code too
    Pointcuts matching the advice itself
    ◎ Aspector Gadget : single responsibility
    SOLID principles apply to AOP as well

    View full-size slide

  59. Thanks ! Any question ?
    We’re hiring
    deezer.com/jobs
    speakerdeck.com/xgouchet

    View full-size slide

  60. A.
    Addendum
    Gradle script

    View full-size slide

  61. The easy way
    buildscript {
    dependencies {
    classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:
    0.9.14'
    }
    }
    apply plugin: 'com.uphyca.android-aspectj'
    dependencies {
    compile 'org.aspectj:aspectjrt:1.8.1'
    }

    View full-size slide