Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

2. Aspects Oriented Programming The general idea

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

.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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

4. Hello AOP Let’s write an aspect

Slide 19

Slide 19 text

Bytecode weaving AspectJ

Slide 20

Slide 20 text

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()); } }

Slide 21

Slide 21 text

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()); } }

Slide 22

Slide 22 text

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()); } }

Slide 23

Slide 23 text

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()); } }

Slide 24

Slide 24 text

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()); } }

Slide 25

Slide 25 text

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()); } }

Slide 26

Slide 26 text

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()); } }

Slide 27

Slide 27 text

5. Concrete examples Beyond the logging “Hello world”

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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(); } } }

Slide 33

Slide 33 text

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(); } } }

Slide 34

Slide 34 text

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(); } } }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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(); } } }

Slide 38

Slide 38 text

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(); } } }

Slide 39

Slide 39 text

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(); } } }

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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); } }

Slide 42

Slide 42 text

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); } }

Slide 43

Slide 43 text

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() { // ... } }

Slide 44

Slide 44 text

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(); } } }

Slide 45

Slide 45 text

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(); } }); } }

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

6. More than just advices

Slide 48

Slide 48 text

@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

Slide 49

Slide 49 text

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"; }

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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()); } }

Slide 52

Slide 52 text

5. General advices no pun intended...

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

“ “It’s a kind of magic” Queen ♬

Slide 56

Slide 56 text

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); } }

Slide 57

Slide 57 text

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); } }

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

A. Addendum Gradle script

Slide 61

Slide 61 text

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