Injecting a service via a InvocationHandler - java

Is there a clean way in Spring (with no XML) to have an interface wired to an invocation handler? Currently I have to do something like this:
#Inject
private ServiceProxyCreator services;
private MyServiceInterface service;
private MyServiceInterface getService() {
if ( service == null )
service = services.createProxy( MyServiceInterface.class );
return service;
}
Where #createProxy is simply an implementation of something like this:
#SuppressWarnings( "unchecked" )
public <T> T createProxy( Class<T> type ) {
JobRpcHandler handler = new JobRpcHandler();
handler.setServiceName( type.getSimpleName() );
return (T) Proxy.newProxyInstance(
type.getClassLoader(), new Class[]{type}, handler );
}
But with all this DI functionality in Spring it seems like I should be able to do this all automatically so that I can simply do the following:
#Inject
private MyService service;
With the injection customized in some way that I don't know to create the Proxy behind the scenes without having to call #createProxy.
Any suggestions on a more elegant approach?

Take a look at FactoryBean. You can write your own this way:
public class ServiceProxyFactoryBean implements FactoryBean<Object>
private Class<T> type;
public DutySetFactoryBean(Class<?> type) {
this.type = type;
}
#Override
public synchronized Object getObject() {
JobRpcHandler handler = new JobRpcHandler();
handler.setServiceName(type.getSimpleName());
return Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler);
}
#Override
public Class<?> getObjectType() {
return type;
}
#Override
public boolean isSingleton() {
return true;
}
}
and use it in your configuration file:
<bean class="package.name.ServiceProxyFactoryBean">
<constructor-arg>
<value type="java.lang.Class">package.name.MyServiceInterface</value>
</constructor-arg>
</bean>
or, using Java configuration, that way:
#Bean
public ServiceProxyFactoryBean myServiceFactoryBean() {
return new ServiceProxyFactoryBean(MyServiceInterface.class);
}
#Bean
public MyServiceInterface myService() {
return (MyServiceInterface)sessionFactoryBean().getObject();
}
If you want automatically create proxies for all the annotated interfaces in a classpath, you can define your own BeanDefinitionRegistryPostProcessor. Here you must scan your classpath with ResourceLoader using the following pattern:
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
Resource[] resources = patternResolver.getResources(
"classpath*:" + packageName.replace('.', '/') + "/**/*.class");
for (Resource resource : resources) {
MetadataReader reader = metadataReaderFactory.getMetadataReader(resource);
if (!reader.getAnnotationMetadata().isAnnotated(
MyProxyAnnotation.class.getName())) {
continue;
}
Class<?> cls = Class.forName(reader.getClassMetadata().getClassName(), true,
resourceLoader.getClassLoader());
String factoryBeanName = createNewName();
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(
ServiceProxyFactoryBean.class);
bdb.addConstructorArgValue(cls);
registry.registerBeanDefinition(factoryBeanName, bdb.getBeanDefinition());
bdb = BeanDefinitionBuilder.genericBeanDefinition(cls);
bdb.setFactoryBean(factoryBeanName, "getBean");
registry.registerBeanDefinition(createNewName(), bdb.getBeanDefinition());
}
Now, for all interfaces, annotated with MyProxyAnnotation, you have a proxy, which you can inject into your beans. For example:
#MyProxyAnnotation
public interface MyServiceInterface {
void foo();
}
And
#Component
public class MyBean {
#Autowired
private MyServiceInterface myService;
}
That's all. No configuration needed.
I am not sure this code works or even compiles. It not the final solution, just a general way you should move toward. So you should research and debug a little.

Related

Adding additional Spring Security method annotations

I'm writing a library that uses Spring Security and method security to check whether a user is licensed to perform a certain operation. This is in addition to the usual role-based security, and this is causing a problem.
The annotations look like they do in this test class:
#RestController
class TestController {
#RolesAllowed("ROLE_USER")
#Licensed("a")
public ResponseEntity<String> a() {
return ResponseEntity.ok("a");
}
#RolesAllowed("ROLE_USER")
#Licensed("b")
public ResponseEntity<String> b() {
return ResponseEntity.ok("b");
}
#RolesAllowed("ROLE_USER")
#Licensed("c")
public ResponseEntity<String> c() {
return ResponseEntity.ok("c");
}
}
Having the annotations processed seems simple enough, because you add a customMethodSecurityDataSource:
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
#Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new LicensedAnnotationSecurityMetadataSource();
}
// more configurations
}
But the problem is in Spring's implementation:
#Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
Collection<ConfigAttribute> attributes = null;
for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null && !attributes.isEmpty()) {
break;
}
}
// Put it in the cache.
if (attributes == null || attributes.isEmpty()) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return NULL_CONFIG_ATTRIBUTE;
}
this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
My custom metadata source is processed first, and as soon as it finds an annotation that it recognises, it stops processing. Specifically, in this if-block:
if (attributes != null && !attributes.isEmpty()) {
break;
}
The result is that my LicenceDecisionVoter votes to abstain; after all, there could be other annotation processors that check roles. And because there are no more attributes to vote upon, only ACCESS_ABSTAIN is returned, and as per Spring's default and recommended configuration, access is denied. The roles are never checked.
Do I have an alternative, other than to implement scanning for Spring's own annotation processors, like the #Secured and JSR-250 annotations?
Or was the mistake to use Spring Security in the first place for this specific purpose?
As promised, the solution. It was more work than I imagined, and the code may have issues because it is partly copied from Spring, and some of that code looks dodgy (or at least, IntelliJ thinks it does).
The key is to remove the GlobalMethodSecurityConfiguration. Leave that to the application itself. The (auto) configuration class looks like the following:
#EnableConfigurationProperties(LicenceProperties.class)
#Configuration
#Import(LicensedMetadataSourceAdvisorRegistrar.class)
public class LicenceAutoConfiguration {
#Bean public <T extends Licence> LicenceChecker<T> licenceChecker(
#Lazy #Autowired final LicenceProperties properties,
#Lazy #Autowired final LicenceFactory<T> factory
) throws InvalidSignatureException, LicenceExpiredException, WrappedApiException,
IOException, ParseException, InvalidKeySpecException {
final LicenceLoader loader = new LicenceLoader(factory.getPublicKey());
final T licence = loader.load(properties.getLicenceFile(), factory.getType());
return factory.getChecker(licence);
}
#Bean MethodSecurityInterceptor licenceSecurityInterceptor(
final LicensedMetadataSource metadataSource,
final LicenceChecker<?> licenceChecker
) {
final MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
interceptor.setAccessDecisionManager(decisionManager(licenceChecker));
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean LicenceAccessDecisionManager decisionManager(#Autowired final LicenceChecker<?> licenceChecker) {
return new LicenceAccessDecisionManager(licenceChecker);
}
#Bean LicensedMetadataSource licensedMetadataSource() {
return new LicensedMetadataSource();
}
}
The registrar:
public class LicensedMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
final BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(LicensedMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgReference("licensedMetadataSource");
registry.registerBeanDefinition("licensedMetadataSourceAdvisor", advisor.getBeanDefinition());
}
}
And finally, the advisor:
public class LicensedMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final LicenceMetadataSourcePointcut pointcut = new LicenceMetadataSourcePointcut();
private transient LicensedMetadataSource attributeSource;
private transient BeanFactory beanFactory;
private transient MethodInterceptor interceptor;
private transient volatile Object adviceMonitor = new Object();
public LicensedMetadataSourceAdvisor(final LicensedMetadataSource attributeSource) {
this.attributeSource = attributeSource;
}
#Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override public Pointcut getPointcut() {
return pointcut;
}
#Override public Advice getAdvice() {
synchronized (this.adviceMonitor) {
if (this.interceptor == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
this.interceptor = this.beanFactory.getBean("licenceSecurityInterceptor", MethodInterceptor.class);
}
return this.interceptor;
}
}
class LicenceMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
#Override public boolean matches(final Method method, final Class<?> targetClass) {
final LicensedMetadataSource source = LicensedMetadataSourceAdvisor.this.attributeSource;
final Collection<ConfigAttribute> attributes = source.getAttributes(method, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
}
The latter two classes are copied and modified from Spring. The advisor was copied from MethodSecurityMetadataSourceAdvisor, and that's a class that somebody at Spring might have a look at, because of the transient volatile synchronisation object (which I copied, because I can't yet establish if it should be final instead), and because it has a private method that is never used.

Creating a pointcut for all the classes in package?

I have to create a configurable pointcut. Can anyone help for achieving the dynamic pointcut.
DynamicPointcut.class
public class DynamicPointcut extends DynamicMethodMatcherPointcut {
#Value("${custom.logging.basepackage}")
String basePackage;
#Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
#Override
public boolean matches(Class<?> clazz) {
List<Class<?>> classList = ClassFinder.find(basePackage);
return classList.stream().anyMatch(x -> x.equals(clazz));
}
};
}
#Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
if(args.length>0){
return true;
}
return false;
}
}
ConfigurableAdvisorConfig.class
#Configuration
public class ConfigurableAdvisorConfig {
#Autowired
private ProxyFactoryBean proxyFactoryBean;
#Autowired
DefaultPointcutAdvisor defaultPointcutAdvisor;
DynamicPointcut pointcut = new DynamicPointcut();
NonProductionLoggingAspect advice = new NonProductionLoggingAspect();
String[] advisor;
List<Advisor> advisorList = new ArrayList<Advisor>();
#Bean
public String[] defaultPointcutAdvisor(){
defaultPointcutAdvisor.setAdvice(new NonProductionLoggingAspect());
defaultPointcutAdvisor.setPointcut(new DynamicPointcut());
advisor = new String[]{"defaultPointcutAdvisor"};
return advisor;
}
#Bean
public ProxyFactoryBean proxyFactoryBean(){
proxyFactoryBean.setInterceptorNames(advisor);
return proxyFactoryBean;
}
}
You can wire up your aspect using the static aspectOf factory method (you can't see that method, it is added by the aspectj compiler)
<bean class="com.YourAspect" factory-method="aspectOf">
<property name="basePackage"
value="${custom.logging.basepackage}" />
Reference:
autowiring in aspect
Use AspectJ support in Spring for your scenario.
#Aspect
#Component
public class DaoAspect{
#Pointcut("within(com.xyz..dao.*)")
public void allDao(){};
#Before("allDao")
public void runAdvise(){
//some code
}
}
Define DaoAspect
Enable aspectJ support in spring by using #EnableAspectJAutoProxy
Ensure your aspect gets registered as bean via component scanning
There you go, this way you can advise all the classes in a
particular package

Spring REST validation on custom annotation

I'm trying to add some extra validation logic on my REST beans using annotations. This is just an example, but the point is that the annotation is to be used on multiple REST resource objects / DTO's.
I was hoping for a solution like this:
public class Entity {
#NotNull // JSR-303
private String name;
#Phone // Custom phonenumber that has to exist in a database
private String phoneNumber;
}
#Component
public class PhoneNumberValidator implements Validator { // Spring Validator
#Autowired
private PhoneRepository repository;
public boolean supports(Class<?> clazz) {
return true;
}
public void validate(Object target, Errors errors) {
Phone annotation = // find fields with annotations by iterating over target.getClass().getFields().getAnnotation
Object fieldValue = // how do i do this? I can easily get the annotation, but now I wish to do a call to repository checking if the field value exists.
}
}
Did you try JSR 303 bean validator implementations like hibernate validator
e.g. is available here http://www.codejava.net/frameworks/spring/spring-mvc-form-validation-example-with-bean-validation-api
Maven Module A:
public interface RestValidator<A extends Annotation, T> extends ConstraintValidator<A, T>
public interface PhoneValidator extends RestValidator<PhoneNumber, String>
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = PhoneValidator.class) // This usually doesnt work since its a interface
public #interface PhoneNumber {
// JSR-303 required fields (payload, message, group)
}
public class Person {
#PhoneNumber
private String phoneNumber;
}
Maven Module B:
#Bean
LocalValidatorFactoryBean configurationPropertiesValidator(ApplicationContext context, AutowireCapableBeanFactory factory) {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setConstraintValidatorFactory(factory(context, factory));
return factoryBean;
}
private ConstraintValidatorFactory factory(final ApplicationContext context, final AutowireCapableBeanFactory factory) {
return new ConstraintValidatorFactory() {
#Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
if (RestValidator.class.isAssignableFrom(key)) {
return context.getBean(key);
} else {
return factory.createBean(key);
}
}
#Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
if (!(instance instanceof RestValidator<?, ?>)) {
factory.destroyBean(instance);
}
}
};
}
#Bean
WebMvcConfigurerAdapter webMvcConfigurerAdapter(final LocalValidatorFactoryBean validatorFactoryBean) {
return new WebMvcConfigurerAdapter() { // Adds the validator to MVC
#Override
public Validator getValidator() {
return validatorFactoryBean;
}
};
}
Then I have a #Component implementation of PhoneValidator that has a Scope = Prototype.
I hate this solution, and I think Spring SHOULD look up on Interface implementations by default, but I'm sure some people that are a lot smarter than me made the decision not to.

Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.
#Service
public class MyService {
public void test(){...}
}
For example for windows's platform I need MyServiceWin extending MyService, for linux platform I need MyServiceLnx extending MyService.
For now I know only one horrible solution:
#Service
public class MyService {
private MyService impl;
#PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
Please consider that I'm using annotation only and not XML config.
1. Implement a custom Condition
public class LinuxCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Same for Windows.
2. Use #Conditional in your Configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
#Bean
#Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
3. Use #Autowired as usual
#Service
public class SomeOtherServiceUsingMyService {
#Autowired
private MyService impl;
// ...
}
Let's create beautiful config.
Imagine that we have Animal interface and we have Dog and Cat implementation. We want to write write:
#Autowired
Animal animal;
but which implementation should we return?
So what is solution? There are many ways to solve problem. I will write how to use #Qualifier and Custom Conditions together.
So First off all let's create our custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public #interface AnimalType {
String value() default "";
}
and config:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AnimalFactoryConfig {
#Bean(name = "AnimalBean")
#AnimalType("Dog")
#Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
#Bean(name = "AnimalBean")
#AnimalType("Cat")
#Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
Note our bean name is AnimalBean. why do we need this bean? because when we inject Animal interface we will write just #Qualifier("AnimalBean")
Also we crated custom annotation to pass the value to our custom Condition.
Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)
public class AnimalCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
and finally injection:
#Qualifier("AnimalBean")
#Autowired
Animal animal;
You can move the bean injection into the configuration, as:
#Configuration
public class AppConfig {
#Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternatively, you may use profiles windows and linux, then annotate your service implementations with the #Profile annotation, like #Profile("linux") or #Profile("windows"), and provide one of this profiles for your application.
Autowire all your implementations into a factory with #Qualifier annotations, then return the service class you need from the factory.
public class MyService {
private void doStuff();
}
My Windows Service:
#Service("myWindowsService")
public class MyWindowsService implements MyService {
#Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
My Mac Service:
#Service("myMacService")
public class MyMacService implements MyService {
#Override
private void doStuff() {
//Mac specific stuff happens here
}
}
My factory:
#Component
public class MyFactory {
#Autowired
#Qualifier("myWindowsService")
private MyService windowsService;
#Autowired
#Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
Then your factory can tap into the Application context and pull instances into it's own map. When you add a new service class, just add another entry to the enum, and that's all you have to do.
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
Now you can just pass the class type you want into the factory, and it will provide you back the instance you need. Very helpful especially if you want to the make the services generic.
Simply make the #Service annotated classes conditional:
That's all. No need for other explicit #Bean methods.
public enum Implementation {
FOO, BAR
}
#Configuration
public class FooCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.FOO == implementation;
}
}
#Configuration
public class BarCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.BAR == implementation;
}
}
Here happens the magic.
The condition is right where it belongs: At the implementating classes.
#Conditional(FooCondition.class)
#Service
class MyServiceFooImpl implements MyService {
// ...
}
#Conditional(BarCondition.class)
#Service
class MyServiceBarImpl implements MyService {
// ...
}
You can then use Dependency Injection as usual, e.g. via Lombok's #RequiredArgsConstructor or #Autowired.
#Service
#RequiredArgsConstructor
public class MyApp {
private final MyService myService;
// ...
}
Put this in your application.yml:
implementation: FOO
👍 Only the implementations annotated with the FooCondition will be instantiated. No phantom instantiations. 👍
Just adding my 2 cents to this question. Note that one doesn't have to implement so many java classes as the other answers are showing. One can simply use the #ConditionalOnProperty. Example:
#Service
#ConditionalOnProperty(
value="property.my.service",
havingValue = "foo",
matchIfMissing = true)
class MyServiceFooImpl implements MyService {
// ...
}
#ConditionalOnProperty(
value="property.my.service",
havingValue = "bar")
class MyServiceBarImpl implements MyService {
// ...
}
Put this in your application.yml:
property.my.service: foo
MyService.java:
public interface MyService {
String message();
}
MyServiceConfig.java:
#Configuration
public class MyServiceConfig {
#Value("${service-type}")
MyServiceTypes myServiceType;
#Bean
public MyService getMyService() {
if (myServiceType == MyServiceTypes.One) {
return new MyServiceImp1();
} else {
return new MyServiceImp2();
}
}
}
application.properties:
service-type=one
MyServiceTypes.java
public enum MyServiceTypes {
One,
Two
}
Use in any Bean/Component/Service/etc. like:
#Autowired
MyService myService;
...
String message = myService.message()

The decorator pattern and #Inject

When using Spring's based XML configuration, it's easy to decorate multiple implementations of the same interface and specify the order. For instance, a logging service wraps a transactional service which wraps the actual service.
How can I achieve the same using the javax.inject annotations?
You can use #Named together with #Inject to specify which bean to inject.
A simple example with an injected service:
public class ServiceTest {
#Inject
#Named("transactionDecorator")
private Service service;
}
And the corresponding transaction decorator class:
#org.springframework.stereotype.Service("transactionDecorator")
public class ServiceDecoratorTransactionSupport extends ServiceDecorator {
#Inject
#Named("serviceBean")
public ServiceDecoratorTransactionSupport(Service service) {
super(service);
}
}
This exposes your configuration into your code, so I would recommend doing the decorating logic in a #Configuration class and annotate for example the logging service with #Primary. With this approach your test class can look something like this:
public class ServiceTest {
#Inject
private Service service;
And the configuration class:
#Configuration
public class DecoratorConfig {
#Bean
#Primary
public ServiceDecorator serviceDecoratorSecurity() {
return new ServiceDecoratorSecuritySupport(
serviceDecoratorTransactionSupport());
}
#Bean
public ServiceDecorator serviceDecoratorTransactionSupport() {
return new ServiceDecoratorTransactionSupport(serviceBean());
}
#Bean
public Service serviceBean() {
return new ServiceImpl(serviceRepositoryEverythingOkayStub());
}
#Bean
public ServiceRepository serviceRepositoryEverythingOkayStub() {
return new ServiceRepositoryEverythingOkStub();
}
}
My second example doesn't expose any details about which implementation that will be returned, but it depends on several Spring specific classes.
You can also combine the two solutions. For example use Spring's #Primary annotation on a decorator and let Spring inject this decorator into the instance of the given type.
#Service
#Primary
public class ServiceDecoratorSecuritySupport extends ServiceDecorator {
}
This is the sort of thing you typically use AOP for, rather than writing and wrapping implementations manually (not that you can't do that).
For AOP with Guice, you'd want to create a transactional MethodInterceptor and a logging MethodInterceptor, then use bindInterceptor(Matcher, Matcher, MethodInterceptor) to set which types and methods should be intercepted. The first Matcher matches types to intercept, the second matches methods to intercept. Either can be Matchers.any(), match a specific annotation on a type or method (#Transactional, say) or whatever you want. Matching methods are then intercepted and handled automatically. Decorator pattern with a lot less boilerplate, basically.
To do it manually, one way would be:
class ServiceModule extends PrivateModule {
#Override protected void configure() {
bind(Service.class).annotatedWith(Real.class).to(RealService.class);
}
#Provides #Exposed
protected Service provideService(#Real Service service) {
return new LoggingService(new TransactionalService(service));
}
}
#Target(PARAMETER)
#Retention(RUNTIME)
#BindingAnnotation
public #interface Decorate {
Class<?> value();
}
/* see com.google.inject.name.NamedImpl for rest of
the methods DecorateImpl must implement */
public class DecorateImpl implements Decorate, Serializable {
private final Class<?> value;
private DecorateImpl(Class<?> val) {
value = val;
}
public static Decorate get(Class<?> clazz) {
return new DecorateImpl(clazz);
}
public Class<?> value() {
return value;
}
...
...
}
Here is how to use it:
public interface ApService {
String foo(String s);
}
public class ApImpl implements ApService {
private final String name;
#Inject
public ApImpl(#Named("ApImpl.name") String name) {
this.name = name;
}
#Override
public String foo(String s) {
return name + ":" + s;
}
}
First decorator:
public class ApDecorator implements ApService {
private final ApService dcrtd;
private final String name;
#Inject
public ApDecorator(#Decorate(ApDecorator.class) ApService dcrtd,
#Named("ApDecorator.name") String name) {
this.dcrtd = dcrtd;
this.name = name;
}
public String foo(String s) {
return name + ":" + s + ":"+dcrtd.foo(s);
}
}
Second decorator:
public class D2 implements ApService {
private final ApService dcrt;
#Inject
public D2(#Decorate(D2.class) ApService dcrt) {
this.dcrt = dcrt;
}
#Override
public String foo(String s) {
return "D2:" + s + ":" + dcrt.foo(s);
}
}
public class DecoratingTest {
#Test
public void test_decorating_provider() throws Exception {
Injector inj = Guice.createInjector(new DecoratingModule());
ApService mi = inj.getInstance(ApService.class);
assertTrue(mi.foo("z").matches("D2:z:D:z:I:z"));
}
}
The Module:
class DecoratingModule extends AbstractModule {
#Override
protected void configure() {
bindConstant().annotatedWith(Names.named("ApImpl.name")).to("I");
bindConstant().annotatedWith(Names.named("ApDecorator.name")).to("D");
bind(ApService.class).
annotatedWith(DecorateImpl.get(ApDecorator.class)).
to(AnImpl.class);
bind(ApService.class).
annotatedWith(DecorateImpl.get(D2.class)).
to(ApDecorator.class);
bind(ApService.class).to(D2.class);
}
}
If bindings configuration looks ugly, you can create Builder/DSL that looks nice.
The drawback is that (comparing with manual chain building) you can not chain the same module twice (i.e. D2->D2->D1->Impl) and the boilerplate in the constructor params.

Categories

Resources