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. Aspect Oriented Programming Add magic to your code and remove

    boilerplate Droidcon Paris 2015 Barcamp
  2. About... Xavier Gouchet Android software engineer (Tools, architecture, core, …)

    @xgouchet on Github, StackOverflow, Twitter, … speakerdeck.com/xgouchet
  3. 1. Introduction What is it ? What is it not

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

    to replace OOP ◎ The solution to all your problems !
  5. It is a tool to ... ◎ Enhance modularity ◎

    Separate cross cutting concerns ◎ Focus on business logic ◎ Reduce noise in the source code
  6. 2. Aspects Oriented Programming The general idea

  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
  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
  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
  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
  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
  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
  13. @LogMethod @SafeTransaction void foo() { bar(); // ... } Before

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

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

    {color: #FF4630; } input[type=text] {font-family: monospace;} CSS + HTML
  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
  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
  18. 4. Hello AOP Let’s write an aspect

  19. Bytecode weaving AspectJ

  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()); } }
  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()); } }
  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()); } }
  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()); } }
  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()); } }
  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()); } }
  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()); } }
  27. 5. Concrete examples Beyond the logging “Hello world”

  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; } }
  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; } }
  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; } }
  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; } }
  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(); } } }
  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(); } } }
  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(); } } }
  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; } }
  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; } }
  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(); } } }
  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(); } } }
  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(); } } }
  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
  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); } }
  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); } }
  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() { // ... } }
  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(); } } }
  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(); } }); } }
  46. and more... Retry failed request Read/write locks Boilerplate code

  47. 6. More than just advices

  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
  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"; }
  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; } }
  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()); } }
  52. 5. General advices no pun intended...

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

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

    generics, inner classes… ◎ Android flavors ◎ AOP is real programming
  55. “ “It’s a kind of magic” Queen ♬

  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); } }
  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); } }
  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
  59. Thanks ! Any question ? We’re hiring deezer.com/jobs speakerdeck.com/xgouchet

  60. A. Addendum Gradle script

  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' }