Skip to content

Instantly share code, notes, and snippets.

@rupert-ong
Last active September 16, 2018 02:43
Show Gist options
  • Save rupert-ong/04ac79a4f138064f82656cf3fcdab9d4 to your computer and use it in GitHub Desktop.
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
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);
}
}
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;
}
}
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);
}
}
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();
}
}
}
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();
}
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();
}
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;
}
@rupert-ong
Copy link
Author

Implements a custom TaskWorker class, which has a setTarget method to determine the class instance and cast it onto the target object (instead of using reflection to determine the target object class and passing that into a constructor). This is a typical example of using Runtime Reflection, using reflection on the taskWorker only, and using regular Java for everything else. Reflection and its invocations are slower than compiled methods.

@rupert-ong
Copy link
Author

rupert-ong commented Sep 16, 2018

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.).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment