Aspect Oriented Programming (Droidcon Paris 2015 BarCamp)

Aspect Oriented Programming (Droidcon Paris 2015 BarCamp)

Add magic to your code and remove boilerplate

Cf95f93e78f6d6dd0630049396f723c6?s=128

Xavier Gouchet

November 09, 2015
Tweet

Transcript

  1. 1.

    Aspect Oriented Programming Add magic to your code and remove

    boilerplate Droidcon Paris 2015 Barcamp
  2. 2.

    About... Xavier Gouchet Android software engineer (Tools, architecture, core, …)

    @xgouchet on Github, StackOverflow, Twitter, … speakerdeck.com/xgouchet
  3. 4.

    It’s NOT ... ◎ A new programming language ◎ Something

    to replace OOP ◎ The solution to all your problems !
  4. 5.

    It is a tool to ... ◎ Enhance modularity ◎

    Separate cross cutting concerns ◎ Focus on business logic ◎ Reduce noise in the source code
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 15.

    .class ul { display: inline;} #id p {font-style: italic;} a:hover

    {color: #FF4630; } input[type=text] {font-family: monospace;} CSS + HTML
  12. 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... <Elements/> markup nodes in the source code, defined by... Selectors rules and patterns
  13. 17.

    Vocabulary Advices behaviors applied to... JoinPoints positions in the source

    code, defined by... PointCuts rules and patterns Attributes behaviors/appearance applied to... <Elements/> markup nodes in the source code, defined by... Selectors rules and patterns
  14. 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()); } }
  15. 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()); } }
  16. 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()); } }
  17. 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()); } }
  18. 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()); } }
  19. 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()); } }
  20. 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()); } }
  21. 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; } }
  22. 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; } }
  23. 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; } }
  24. 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; } }
  25. 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(); } } }
  26. 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(); } } }
  27. 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(); } } }
  28. 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; } }
  29. 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; } }
  30. 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(); } } }
  31. 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(); } } }
  32. 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(); } } }
  33. 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
  34. 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); } }
  35. 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); } }
  36. 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() { // ... } }
  37. 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(); } } }
  38. 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(); } }); } }
  39. 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
  40. 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"; }
  41. 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; } }
  42. 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()); } }
  43. 54.

    Things to remember ◎ Singleton by default ◎ Inheritance, abstracts,

    generics, inner classes… ◎ Android flavors ◎ AOP is real programming
  44. 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); } }
  45. 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); } }
  46. 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
  47. 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' }