Lately I've found myself using the same technique more than once in order to create some easily extensible mechanism, using: Spring, Annotations and simple predefined interface
I think the best way to picture the usage would be an example:
Let's say you need some kind of email editor in your application which will allow your user to use predefined set of keywords that will be evaluated during the email sending.
For example:
If the user wrote following text in email body : Hi today is ${system.Date} and I'm ...
Then the recipient will get following content : Hi today is 2/7/2014 and I'm ...
In case your task is a bit more complicated than that and you need more comprehensive set of keywords, that will be extended during the time or even you would like to allow 3rd party developers to extend it, following technique might be handy.
I'll skip the part when you find ${prefix.keyword} pattern in the code and start from the point you have extracted the keyword already.
Now let's have a look on how KeywordExecutor implemented, for sake of our example let's assume we have 2 keywords to implement: system.Date and system.Time
You can see that execute method can be as complex as we need and the rest of the code won't be affected.
The last thing that we need is our KeywordExecutorsHelper implementation:
I think the best way to picture the usage would be an example:
Let's say you need some kind of email editor in your application which will allow your user to use predefined set of keywords that will be evaluated during the email sending.
For example:
If the user wrote following text in email body : Hi today is ${system.Date} and I'm ...
Then the recipient will get following content : Hi today is 2/7/2014 and I'm ...
In case your task is a bit more complicated than that and you need more comprehensive set of keywords, that will be extended during the time or even you would like to allow 3rd party developers to extend it, following technique might be handy.
I'll skip the part when you find ${prefix.keyword} pattern in the code and start from the point you have extracted the keyword already.
//.......
for(/*... do the iteration*/){
// get your keyword from text
String keywordValue = getKeywordValue(foundKeyword);
// replace your ${foundKeyword} with keywordValue
}
//......
// call the Helper class to get the correct executor
private String getKeywordValue(String key) {
KeywordExecutor keywordExecutor = KeywordExecutorsHelper.getKeywordExecutor(key);
if(keywordExecutor != null){
return keywordExecutor.execute(context);
}
// if we couldn't find suitable executor just return the keyword as is
return key;
}
Now let's have a look on how KeywordExecutor implemented, for sake of our example let's assume we have 2 keywords to implement: system.Date and system.Time
// the interface
public interface KeywordExecutor {
// you can pass any parameters if you need them for your business case
String execute();
}
// annotation that will hold the required keyword
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Keyword {
String value();
}
@Component // Spring Component annotation
@Keyword("system.Time") // here goes our annotation with the the keyword
public class TimeKeyword implements KeywordExecutor {
// the implementation itself
@Override
public String execute() {
Date now = new Date();
// using Spring SimpleDateFormat here
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
return format.format(now);
}
}
// same as above with slight format change
@Component
@Keyword("system.Date")
public class DateKeyword implements KeywordExecutor {
@Override
public String execute() {
Date now = new Date();
SimpleDateFormat format = new SimpleDateFormat("dd-MMM-yyyy");
return format.format(now);
}
}
You can see that execute method can be as complex as we need and the rest of the code won't be affected.
The last thing that we need is our KeywordExecutorsHelper implementation:
// in this sample I just reading all the classes every time getKeywordExecutor method called, you can optimize this by
// saving the Map a static variable and lazy initializing it during the first getKeywordExecutor call
public class KeywordExecutorsHelper {
public static KeywordExecutor getKeywordExecutor(String keyword) {
// getting all the beans annotated with Keyword annotation
Map keywordExecutors = SpringUtils.getBeanWithAnnotation(Keyword.class);
// the bean name used as a key in retrieved Map
for (String beanName : keywordExecutors.keySet()) {
// since all the beans implementing the same interface, I can safely cast each bean to KeywordExecutor
KeywordExecutor keywordExecutor = (KeywordExecutor) keywordExecutors.get(beanName);
// getting the specific annotation instance from current bean using simple java reflection
Keyword keywordAnnotation = keywordExecutor.getClass().getAnnotation(Keyword.class);
// reading the value attribute and comparing with received keyword
if (keyword.equalsIgnoreCase(keywordAnnotation.value())) {
return keywordExecutor;
}
}
// no implementation found for requested keyword
return null;
}
}
So, lets summarize what have we done and why you might need it:- What
- Defined an interface that will help us to encapsulate bean implementation (execute in the case above)
- Define an annotation that will help us to find all the annotated classes using Spring and will be used as a placeholder for required attributes (Keyword in the case described above)
- Define a Helper class that will retrieve all the possible implementations from Spring context and will return a specific one by demand
- Finally use the Helper class to get the specific implementation
- Why
- In case you need an extensible set of commands that can be summoned using keyword only this implementation can be very handy
- In case you want to outsource the implementation and will receive some kind of 3rd jar, you'll have to define only the interface and the annotation
Hope it was helpful.
No comments:
Post a Comment