Last active
September 16, 2018 02:43
-
-
Save rupert-ong/04ac79a4f138064f82656cf3fcdab9d4 to your computer and use it in GitHub Desktop.
Java: Instance Creation and Invocation Revisited With Custom Annotation (No Constructor) #java #reflection #runtime #worker #annotation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps.utils; | |
| import com.ps.WorkHandler; | |
| import com.ps.finance.BankAccount; | |
| import com.ps.finance.HighVolumeAccount; | |
| @WorkHandler(useThreadPool = false) | |
| public class AccountWorkerRevisited implements Runnable, TaskWorker { | |
| // Only include BankAccount as HighVolumeAccount inherits from it | |
| private BankAccount ba; | |
| private char txType = 'w'; | |
| public void setTxType(char type) { | |
| this.txType = type; | |
| } | |
| @Override | |
| public void setTarget(Object target) { | |
| if (BankAccount.class.isInstance(target)) | |
| ba = (BankAccount) target; | |
| else | |
| throw new IllegalArgumentException("Not a BankAccount instance"); | |
| } | |
| @Override | |
| public void doWork() { | |
| // HighVolumeBankAccount already implements Runnable | |
| // Could actually use Runnable.class.isInstance(ba) instead | |
| Thread t = new Thread(HighVolumeAccount.class.isInstance(ba) ? (HighVolumeAccount) ba : this); | |
| t.start(); | |
| try { | |
| t.join(); | |
| } catch (InterruptedException e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| @Override | |
| public void run() { | |
| int amt = 50; | |
| if (txType == 'w') | |
| ba.withdraw(amt); | |
| else | |
| ba.deposit(amt); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps.finance; | |
| import com.ps.ProcessedBy; | |
| import com.ps.utils.AccountWorkerRevisited; | |
| @ProcessedBy(AccountWorkerRevisited.class) | |
| public class BankAccount { | |
| private String id; | |
| private int balance; | |
| public BankAccount(String id) { | |
| this.id = id; | |
| } | |
| public BankAccount(String id, int balance) { | |
| this.id = id; | |
| this.balance = balance; | |
| } | |
| public String getId() { | |
| return id; | |
| } | |
| public synchronized int getBalance() { | |
| return balance; | |
| } | |
| public synchronized void deposit (int amount) { | |
| this.balance += amount; | |
| } | |
| public synchronized void withdraw (int amount) { | |
| this.balance -= amount; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps.finance; | |
| public final class HighVolumeAccount extends BankAccount implements Runnable { | |
| public HighVolumeAccount(String id) { super(id); } | |
| public HighVolumeAccount(String id, int balance) { super(id, balance); } | |
| private int[] readDailyDeposits() { | |
| return new int[100]; | |
| } | |
| private int[] readDailyWithdrawals() { | |
| return new int[100]; | |
| } | |
| @Override | |
| public void run() { | |
| for(int depositAmt : readDailyDeposits()) | |
| deposit(depositAmt); | |
| for(int withdrawalAmt : readDailyWithdrawals()) | |
| withdraw(withdrawalAmt); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps; | |
| import com.ps.finance.BankAccount; | |
| import com.ps.utils.TaskWorker; | |
| public class Main { | |
| public static void main(String[] args) { | |
| doWorkOnBankAccount(); | |
| } | |
| private static void doWorkOnBankAccount() { | |
| BankAccount acct = new BankAccount("1234"); | |
| // startWork("com.ps.utils.AccountWorkerRevisited", acct); | |
| startWorkUsingAnnotationMetaData(acct); | |
| System.out.println("New balance is " + acct.getBalance()); | |
| } | |
| private static void startWork(String workerTypeName, Object workerTarget) { | |
| try { | |
| // Only use reflection once now. We no longer use a constructor | |
| // determined by the class parameter we pass into it. | |
| // (workerTarget now determined in implemented setTarget method) | |
| Class<?> workerType = Class.forName(workerTypeName); | |
| // Our AccountWorkerRevisited class implements our TaskWorker class | |
| TaskWorker worker = (TaskWorker) workerType.newInstance(); | |
| worker.setTarget(workerTarget); | |
| // Get annotation class used in worker | |
| WorkHandler wh = workerType.getAnnotation(WorkHandler.class); | |
| if (wh == null) | |
| throw new Exception("No annotation found"); | |
| // Accessing annotation to see if we want to use our app's thread pool service | |
| if (wh.useThreadPool()) { | |
| // Use app's thread pool | |
| /* Example code | |
| pool.submit(new Runnable(){ | |
| worker.doWork(); | |
| }); | |
| */ | |
| } else { | |
| worker.doWork(); | |
| } | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /** | |
| * Notice how we do not take a parameter to determine the Worker class. | |
| * This relationship has been defined in the ProcessedBy annotation in | |
| * the BankAccount class (to AccountWorkerRevisited). | |
| * @param workerTarget Object to be worked on | |
| */ | |
| private static void startWorkUsingAnnotationMetaData(Object workerTarget) { | |
| try { | |
| // Get class of object to be worked on (ie BankAccount) | |
| Class<?> targetType = workerTarget.getClass(); | |
| // Now get Worker class from custom annotation value | |
| ProcessedBy pb = targetType.getAnnotation(ProcessedBy.class); | |
| Class<?> workerType = pb.value(); | |
| TaskWorker worker = (TaskWorker) workerType.newInstance(); | |
| worker.setTarget(workerTarget); | |
| // Get annotation class used in worker | |
| WorkHandler wh = workerType.getAnnotation(WorkHandler.class); | |
| if (wh == null) | |
| throw new Exception("No annotation found"); | |
| // Accessing annotation to see if we want to use our app's thread pool service | |
| if (wh.useThreadPool()) { | |
| // Use app's thread pool | |
| /* Example code | |
| pool.submit(new Runnable(){ | |
| worker.doWork(); | |
| }); | |
| */ | |
| } else { | |
| worker.doWork(); | |
| } | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps; | |
| import java.lang.annotation.ElementType; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.RetentionPolicy; | |
| import java.lang.annotation.Target; | |
| @Target(ElementType.TYPE) | |
| @Retention(RetentionPolicy.RUNTIME) | |
| /** | |
| * Sets up relationship between Account and the Worker class | |
| */ | |
| public @interface ProcessedBy { | |
| Class<?> value(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps.utils; | |
| public interface TaskWorker { | |
| /** | |
| * Implement a setTarget method to set the type of the object | |
| * we wish to work on. This way, we don't use reflection for it. | |
| * Key is to use reflection only when necessary (determining our worker class) | |
| * | |
| * @param target | |
| */ | |
| public void setTarget(Object target); | |
| /** | |
| * Method to run what task(s) need to be completed | |
| */ | |
| public void doWork(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.ps; | |
| import java.lang.annotation.ElementType; | |
| import java.lang.annotation.Retention; | |
| import java.lang.annotation.RetentionPolicy; | |
| import java.lang.annotation.Target; | |
| // Create a custom annotation to denote context and intent | |
| // Must use Retention annotation to specify availability | |
| @Retention(RetentionPolicy.RUNTIME) | |
| // Narrow application of our annotation to element type only | |
| @Target( {ElementType.TYPE} ) | |
| public @interface WorkHandler { | |
| // Fields are defined like methods | |
| boolean useThreadPool() default true; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use custom annotation to express context and intent in our application (metadata). Here, the WorkHandler allows us to specify if our AccountWorker class should use our app's threadpool (hypothetical) or not with the useThreadpool element.
The ProcessedBy annotation sets up a relationship between classes (BankAccount and AccountWorkerRevisited).
Annotations are accessed with reflection.
Note that annotation are defined like interface classes, but with an @ preceding the interface declaration. Elements in the annotation are defined like methods, but set like fields. You must use a retention annotation to specify an annotation's availability (RetentionPolicy.SOURCE, RetentionalPolicy.CLASS (default) or RetentionalPolicy.RUNTIME). Also use the target annotation to limit the application of your custom annotation (type, method, etc.).