I want implement strategy design pattern in spring boot application. I create BeanPostProcessor for construct strategy resolver:
#Component
public class HandlerInAnnotationBeanPostProcessor implements BeanPostProcessor {
private final UnpHandlersResolver unpHandlersResolver;
public HandlerInAnnotationBeanPostProcessor(UnpHandlersResolver unpHandlersResolver) {
this.unpHandlersResolver = unpHandlersResolver;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Annotation[] annotations = bean.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof HandlerIn) {
if (bean.getClass() != UnpHandler.class)
throw new RuntimeException("Not UnpHandler bean annotated by HandlerIn");
SmevMessageType[] type = ((HandlerIn) annotation).type();
for (SmevMessageType smevMessageType : type) {
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
}
}
}
return bean;
}
}
And I create resolver:
#Slf4j
#Component
public class UnpHandlersResolverImpl implements UnpHandlersResolver {
private Map<SmevMessageType, UnpHandler> map = new HashMap<>();
#Override
public void setHandler(SmevMessageType messageType, UnpHandler unpHandler) {
map.put(messageType, unpHandler);
}
#Override
public UnpHandler getUnpHandler(SmevMessageType type) {
UnpHandler sendRequestHandler = map.get(type);
if (sendRequestHandler == null)
throw new IllegalArgumentException("Invalid SendRequestHandler type: " + type);
return sendRequestHandler;
}
}
My BeanPostProcessor scan all beans with annotation HandlerIn and add to resolver's mup. I think it's wrong to do that:
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
But I not understand how can I add find beans to resolver. Before this implementation I faind beans in #Postconstruct method of resolver like:
context.getBeansWithAnnotation(HandlerIn.class);
But in this solution I have context in resolver and I think is bad.
Tell me how to properly implement what I want? In short, I want to have a set of classes that implement different behaviors. And the class that controls them. Give the class a parameter so that he chooses the right strategy and gives it to me. Like this:
Handler handler = handlersResolver.getHandler(messageType);
Result result = handler.somthing(param);
I'm going to try to make a simple example.
Interface Greeting {
void sayHello();
String getSupportedLanguage();
}
Then you have X number of implementations and you can loop through them in your "resolver"'s constructor to build the map. (I've seen this called a Proxy or a Decorator in code though, i.e. GreetingProxy or GreetingDecorator)
#Service
public GreetingResolver {
private Map<String, Greeting> languageToGreetingMap = new HashMap<>();
#Autowired
public GreetingResolver(List<Greeting> greetings) {
for (Greeting greeting : greetings) {
languageToGreetingMap.put(greeting.getSupportedLanguage(), greeting);
}
}
public void sayGreetingForLanguage(String language) {
languageToGreetingMap.get(language).sayHello();
}
}
This is a basic example of how one can do the strategy pattern in Spring. Every interface implementation of "Greeting" only knows about itself and what it can support. We then autowire all implementations in a list and loop through to create the map once and then during runtime only the relevant entry from the map in retrieved and used.
Note: this was typed "free hand" directly in the web page so please forgive any typos in the code.
Related
I am creating a project which will respond to collect multiple bean object, save it to the database and return the status of the transaction. There can be multiple objects that can be sent from the client. For each object, they are having separate database thus separate controller.
So I planned to create a framework that can accept multiple objects from multiple controllers and send only one centralized object. But I am not sure how to use a centralized object as a return type in the controller(currently I returned them as Object). Below is my code:
Controller:
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public Object findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Service:
#Service
#Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {
#Autowired
private PlayerRepository repository;
#Override
public PlayerValue findByNumber(String number) {
Optional<PlayerEntity> numberValue = repository.findByNumber(number);
return numberValue.map(PlayerEntity::toValue).orElse(null);
}
}
In service I returned the PlayerValue object but I want to wrap this object into a centralized bean ResponseValue. I created an aspect for that
#Aspect
#Component
public class Converter {
private static final Logger LOG = LoggerFactory.getLogger(Converter.class);
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerClassMethod() {}
private <T> ResponseValue<T> convert(List<T> results) {
String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();
return new ResponseValue<>(ResponseValueStatus.OK, message, results);
}
#Around("restControllerClassMethod()")
#SuppressWarnings("unchecked")
public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
ResponseValue value;
try {
Object findObject = joinPoint.proceed();
List<Object> objects = toList(findObject);
value = convert(objects);
} catch (NullPointerException e) {
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
//this exception will go in a controller advice and create a response value with this message
} catch (Throwable e) {
LOG.error("Exception occurred while converting the object", e);
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
}
return value;
}
private List<Object> toList(Object findObject) {
List<Object> objects = new ArrayList<>();
if (findObject instanceof List) {
((List) findObject).forEach(item -> objects.add(findObject));
} else {
objects.add(findObject);
}
return objects;
}
}
To sum up, There could be multiple entity similar to PlayerValue. I need a way to return the result in a centralized bean. Above process work, BUT for this I have to give return type as Object in Controller. Does anybody has an idea how can I use return type as List or T in controller. Also I know it can be done by implementing a ValueConverter interface, but this conversion is straightforward. So it would be nice if any other developer don't have to implement the ValueConverter everytime he want to add a different controller.
Also feel free to review the implementation and let me know if anyone has some alternative idea or some comments.
Note: I reduce a lot of code in the question so that it can be easier to understandable without understanding the actual requirement context. Please do let me know if anyone need more info.
After some research I came across to a better design solution for the framework (but of course with flaws) to achieve conversion to a centralized bean for multiple domain objects is to use a marker interface.
Marker interface can provide a centralized type for all the bean. The main rule need to be followed by the client is to implement that marker interface. So the basic solution is
Marker interface:
public interface StateResponseValue<T> {
}
Implement the interface in all the bean.
public class PlayerValue implements StateResponseValue<PlayerValue> {
}
public class ResponseValue<T> implements StateResponseValue<T> {
//fields and their getter and setter
}
Change the return type in service and controller.
public interface StatService<T> {
StateResponseValue<T> findByNumber(String number);
}
Change the return type in controller
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public StateResponseValue<T> findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Note: The main drawback I feel is that whenever we want to access the field client need to explicitly cast the object to ResponseValue which is still pretty ugly.
What if you create an AbstractStatController which is generic ?
Generic interface StatService:
public interface StatService<T> {
T findByNumber(String number);
}
Generic abstract class AbstractStatController:
public abstract class AbstractStatController<T> {
abstract StatService<T> getStatService();
#RequestMapping("/number/{number}")
public T findByNumber(#PathVariable String number) {
return getStatService().findByNumber(number);
}
}
Concrete class PlayerController:
#RestController
#RequestMapping("/stat/player")
public class PlayerController extends AbstractStatController<Player> {
private final PlayerService playerService;
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#Override
StatService<Player> getStatService() {
return playerService;
}
}
I’d like to apply a customizable aspect on two different services (spring bean). My problem is how/where to set/define the pointcut expression. One normally defines the pointcut expression on a ‘dummy method’ or directly on the advice method. However that means the pointcut is static (not customizable).
I’d like to define the pointcut at the bean creation level to be able to create the same kind of advice for different targets. Ideally I’d like to do something like this:
#Aspect
public class ServiceAspect {
private static final Logger LOG = LoggerFactory.getLogger(ServiceAspect.class);
private final String discriminator;
// no advice defined here!!!
public ServiceAspect(String discriminator) { this.discriminator = discriminator; }
public Object around(ProceedingJoinPoint jp) throws Throwable {
LOG.info(discriminator + " called");
return jp.proceed();
}
}
#Configuration
#EnableAspectJAutoProxy
#PropertySource("classpath:application.properties")
public class ServiceConfiguration {
#Bean
public MyService service1() { return new MyServiceImpl(); }
#Bean
#Around("bean(service1)") // define the advice when bean is created
#ConditionalOnProperty("aspect1Enbaled")
public ServiceAspect aspect() {
return new ServiceAspect("Aspect-1");
}
#Bean
public YourService service2() { return new YourServiceImpl(); }
#Bean
#Around("bean(service2)") // define a different advice when bean is created
#ConditionalOnProperty("aspect2Enbaled")
public ServiceAspect aspect() {
return new ServiceAspect("Aspect-2");
}
}
Notice that the #Around annotation is on the definition of the bean. I can thus reuse the aspect for different target. Using the #ConditionalOnProperty, this would enable me to turn on/off individual aspect base on a property.
Can anyone help me with this? I suspect I’ll need to create some kind of factory but can’t seem to see how I can REPLACE an already defined bean (the service bean) with a proxy!
I need to build mappings for classes (literally a Map<Class<?>, String>), which won't vary at runtime, and keeping things decoupled is a priority. Since I'm in a Spring application, I thought I'd use an annotation and ClassPathScanningCandidateComponentProvider more or less like so:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Mapping {
String value();
}
And:
public class MappingLookUp {
private static final Map<Class<?>, String> MAPPING_LOOK_UP;
static {
Map<Class<?>, String> lookUp = new HashMap<>();
ClassPathScanningCandidateComponentProvider scanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
scanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Mapping.class));
for (BeanDefinition beanDefinition : scanningCandidateComponentProvider.findCandidateComponents("blah")) {
Class<?> clazz;
try {
clazz = Class.forName(beanDefinition.getBeanClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Mapping mapping = AnnotationUtils.getAnnotation(clazz, Mapping.class);
if (mapping == null) {
throw new IllegalStateException("This should never be null");
}
lookUp.put(clazz, mapping.value());
}
MAPPING_LOOK_UP = Collections.unmodifiableMap(lookUp);
}
public static String getMapping(Class<?> clazz) {
...
}
}
Although I believe this will work, this feels like:
a lot to put in a static initialization
a hacky use of the scanning component provider, even though it's commonly recommended for this purpose; BeanDefinition makes it sound like it's intended for finding Spring beans rather than general class definitions.
To be clear, the annotated values are data classes -- not Spring-managed beans -- so a BeanPostProcessor pattern doesn't fit, and indeed, that's why it feels awkward to use the scanning component provider that, to me, seems intended for discovery of Spring managed beans.
Is this the proper way to be implementing this pattern? Is it a proper application of the provider? Is there a feasible alternative without pulling in other classpath scanning implementations?
I will suggest this doesn't look like it is done in a very Spring-y way.
If I were to be doing this, I would utilize Spring's BeanPostProcessor or BeanFactoryPostProcessor. Both of these allow for introspection on all Bean's in Spring's BeanFactory, and would allow you to get away from the static-ness of your current setup, as the PostProcessors are just Spring Bean's themselves.
class MappingLookup implements BeanPostProcessor {
private final Map<Class<?>, String> lookup = new HashMap<>();
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// check bean's class for annotation...
// add to lookup map as necessary...
// make sure to return bean (javadoc explains why)
return bean;
}
public String getMapping(Class<?> clazz) {
// ...
}
// omitted other methods...
}
I asked a very similar question recently How to get list of Interfaces from #ComponentScan packages and finally implemented the first of suggested approaches.
You can see the code https://github.com/StanislavLapitsky/SpringSOAProxy see https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java and of course initialization annotation https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/annotation/ProxyableScan.java the key thing is to add #Import({ProxyableScanRegistrar.class})
The key code is
public class ProxyableScanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Get the ProxyableScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}
In Spring 4.2+, we can use #EventListener annotation with a "condition" expression.
In my scenario, I need to match the id of the event object with a regular expression that is configured in a .properties file.
However, it seems impossible to reference any bean's property or method from the condition's regular expression, as the root context seems to be the event object itself.
So far, I have an abstract class, that sets the event id pattern property based on the class name. The goal is to make the implementation of each Event Listener as clean and simple as possible.
#Service
#PropertySource(value = "classpath:subscriberEventMapping.properties")
public abstract class AbstractEventHandler implements IEventHandler {
private String eventIdPattern;
#Autowired
Environment env;
#Autowired(required = true)
public void configureEventIdPattern() {
String simpleClassName = this.getClass().getSimpleName();
String resolvedEventIdPattern = env.getProperty(
simpleClassName.substring(0,1).toLowerCase() +
simpleClassName.substring(1, simpleClassName.length()));
this.eventIdPattern = resolvedEventIdPattern == null ? ".*" : resolvedEventIdPattern;
}
public String getEventIdPattern() {
return eventIdPattern;
}
}
The properties file looks like this:
regExpEventHandler=^(901|909|998|1000)$
dummyEventHandler=^([1-9][0-9]{0,2}|1000)$
And then, I have a sample Event Listener that extends the above Abstract class:
#Service
public class RegExpEventHandler extends AbstractEventHandler {
#Log
private ILog logger;
#Override
#EventListener(condition = "#event.eventid matches #regExpEventHandler.getEventIdPattern()")
public void onEvent(Event event) {
logger.debug("RegExpEventHandler processing : {} with event pattern : {}", event, getEventIdPattern());
}
}
The problem is that the expression
"#event.eventid matches #regExpEventHandler.getEventIdPattern()"
does not work, because the bean "#regExpEventHandler" cannot be found in the context used by the #EventListener.
Is there a way to access methods or properties of an existing Spring Bean here? Any other better approach for this scenario ?
I know I can easily access STATIC constants or methods by using something like:
#event.eventid matches T(my.package.RegExpEventHandler.MY_CONSTANT)
But a String constant (static final) cannot be initialized from a properties file using a #Value expression.
Using NON-FINAL static constants can work, but then EACH Event Listener needs to add boiler-plate to initialize the static constant from a non-static variable using a #Value expression, which we want to avoid.
Thanks a lot in advance !
It works for me - I looked at the EventExpressionEvaluator and saw that it added a bean resolver to the evaluation context...
public EvaluationContext createEvaluationContext(ApplicationEvent event, Class<?> targetClass,
Method method, Object[] args, BeanFactory beanFactory) {
Method targetMethod = getTargetMethod(targetClass, method);
EventExpressionRootObject root = new EventExpressionRootObject(event, args);
MethodBasedEvaluationContext evaluationContext = new MethodBasedEvaluationContext(
root, targetMethod, args, getParameterNameDiscoverer());
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
So I wrote a quick test...
#SpringBootApplication
public class So43225913Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So43225913Application.class, args);
context.publishEvent("foo");
}
#EventListener(condition = "#bar.accept(event)")
public void listen(Object event) {
System.out.println("handler:" + event);
}
#Bean
public Bar bar() {
return new Bar();
}
public static class Bar {
public boolean accept(Object o) {
System.out.println("bar:" + o);
return true;
}
}
}
and it works just fine...
bar:org.springframework.context.PayloadApplicationEvent[...
handler:foo
(This was with 4.3.7; boot 1.5.2).
Based on parameters passed to a method, I need to select from one of many Spring beans that are implementations of the same class, but configured with different parameters.
E.g. if user A invokes the method, I need to call dooFoo() on bean A, but if it's user B then I need to call the very same method, only on bean B.
Is there a 'Springier' way of doing this other than sticking all the beans in a map, and deriving a key from the parameters passed to my method?
We face that issue in our project, and we solve it through a Factory-Like class. The client class -the one that needed the bean at runtime- had an instance of the factory, that was injected through Spring:
#Component
public class ImTheClient{
#Autowired
private ImTheFactory factory;
public void doSomething(
Parameters parameters) throws Exception{
IWantThis theInstance = factory.getInstance(parameters);
}
}
So, the IWantThis instance depends on the runtime value of the parameters parameter. The Factory implementation goes like this:
#Component
public class ImTheFactoryImpl implements
ImTheFactory {
#Autowired
private IWantThisBadly anInstance;
#Autowired
private IAlsoWantThis anotherInstance;
#Override
public IWantThis getInstance(Parameters parameters) {
if (parameters.equals(Parameters.THIS)) {
return anInstance;
}
if (parameters.equals(Parameters.THAT)) {
return anotherInstance;
}
return null;
}
}
So, the factory instance holds reference to both of the posible values of the IWantThis class, being IWantThisBadly and IAlsoWantThis both implementations of IWantThis.
Seems like do you want a ServiceLocator using the application context as registry.
See ServiceLocatorFactoryBean support class for creating ServiceLocators mapping keys to bean names without coupling client code to Spring.
Other option is to use a naming convention or annotation based configuration.
for example, assuming that you annotate Services with #ExampleAnnotation("someId"), you can use something like the following Service Locator to retrieve them.
public class AnnotationServiceLocator implements ServiceLocator {
#Autowired
private ApplicationContext context;
private Map<String, Service> services;
public Service getService(String id) {
checkServices();
return services.get(id);
}
private void checkServices() {
if (services == null) {
services = new HashMap<String, Service>();
Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
for (Object bean : beans.values()) {
ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
services.put(ann.value(), (Service) bean);
}
}
}
}
Sticking them in a map sounds fine. If it's a Spring-managed map (using util:map, or in Java config), that's better than creating it somewhere else, because then Spring owns all the object references and can manage their lifecycle properly.
If the beans (A, B) you are talking about are SessionScope its no problem at all, they will be selected correctly.
public class BusinessLogic {
private BaseClassOfBeanAandB bean;
public void methodCalledByUserAorB() {
bean.doFoo();
}
}