I want to use Guava's Service Manager to manage services in my Dropwizard application.
ServiceManagerProvider provides the service manager:
#Singleton
public class ServiceManagerProvider implements Provider<ServiceManager> {
#Inject
Set<Service> services;
public static Multibinder<Service> serviceRegistry(Binder binder) {
return Multibinder.newSetBinder(binder, Service.class);
}
#Override
public ServiceManager get() {
return new ServiceManager(services);
}
}
ManagedGuavaServices is a Managed object that interacts with the Service Manager to start/stop services:
public class ManagedGuavaServices implements Managed {
#Inject
private ServiceManager _serviceManager;
#Inject
public ManagedGuavaServices(ServiceManager serviceManager) {
_serviceManager = serviceManager;
}
#Override
public void start() throws Exception {
_serviceManager.startAsync();
}
#Override
public void stop() throws Exception {
_serviceManager.stopAsync();
}
}
MyModule is the module where the Guice bindings are specified:
public class MyModule extends DropwizardAwareModule<MyConfig> {
...
#Override
protected void configure() {
bind(ServiceManager.class).toProvider(ServiceManagerProvider.class);
bind(Managed.class).to(ManagedGuavaServices.class).in(Singleton.class);
}
}
And MyApplication is the Dropwizard application that depends on MyModule:
public class MyApplication extends Application<MyConfig> {
#Inject
private Managed managedServices;
...
#Override
public void initialize(Bootstrap<MyConfig> bootstrap) {
bootstrap.addBundle(GuiceBundle.builder()
.printDiagnosticInfo()
.printGuiceBindings()
.enableAutoConfig(getClass().getPackage().getName())
.modules(
new MyModule()
)
.build());
}
...
#Override
public void run(MyConfig config, Environment environment) {
environment.lifecycle().manage(managedServices);
}
}
It seems like everything has been wired together, but when I run the application, I get the error message:
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:221)
at io.dropwizard.lifecycle.setup.LifecycleEnvironment.manage(LifecycleEnvironment.java:45)
at com.example.MyApplication.run(MyApplication.java:188)
at com.example.MyApplication.run(MyApplication.java:60)
at io.dropwizard.cli.EnvironmentCommand.run(EnvironmentCommand.java:44)
at io.dropwizard.cli.ConfiguredCommand.run(ConfiguredCommand.java:87)
at io.dropwizard.cli.Cli.run(Cli.java:78)
at io.dropwizard.Application.run(Application.java:94)
at com.example.MyApplication.main(MyApplication.java:70)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.webobjects._bootstrap.WOBootstrap.main(WOBootstrap.java:118)
which essentially means that the injected managedServices in MZPaymentApplication is null when the application is run.
What is wrong here? Is it the same problem as in this SO question? If so, where should the #PostConstruct be?
The problem is in managedServices being field in Application and not injectable, since app object is not configured by Guice (usually in Dropwizard it is created via new keyword).
So you should store your GuiceBundle instance in initialize method as field and then access any Guice binding in run method with guiceBundle.getInjector().getInstance(), e.g.:
public class MyApplication extends Application<MyConfig> {
private GuiceBundle<MyConfig> guiceBundle;
public static void main(String[] args) throws Exception {
new MyApplication().run(args);
}
#Override
public void initialize(Bootstrap<MyConfig> bootstrap) {
guiceBundle = GuiceBundle.builder()
.printDiagnosticInfo()
.printGuiceBindings()
.enableAutoConfig(getClass().getPackage().getName())
.modules(
new MyModule()
)
.build();
bootstrap.addBundle(guiceBundle);
}
#Override
public void run(MyConfig config, Environment environment) throws Exception {
environment.lifecycle().manage(guiceBundle.getInjector().getInstance(Managed.class));
}
}
Related
I am creating one application in which I am using java 17,Spring Boot, karaf for osgi and hazelcast jet as doing some join for data coming from different sources(modules) but as using Hazelcast Jet module cannot identified by the ServiceReference they are the getting registered in different references, So how register them in one context.
My Common module interface which used in different modules (packaging as jar in all modules):
public interface DataService {
String name();
public BatchStage<Object> getData(Pipeline pipeline, Map<String, Object> source);
}
First Module here we implement the above interface:
public class JDBCDataSource implements DataService, Serializable {
public BatchStage<Object> getData(Pipeline pipeline, Map<String, Object> source) {
//Hazelcast Jdbc implementation
}
}
And Activator for First module:
public class Activator implements BundleActivator {
public static BundleContext bundleContext;
ConfigurableApplicationContext appContext;
#Override
public void start(BundleContext context) throws Exception {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
appContext = SpringApplication.run(Activator.class);
bundleContext = context;
registerServices();
}
#Override
public void stop(BundleContext context) throws Exception {
bundleContext = null;
SpringApplication.exit(appContext, () -> 0);
}
public static BundleContext getBundleContext() {
return bundleContext;
}
private void registerServices() {
DataService service = new JDBCDataSource();
bundleContext.registerService(DataService.class.getName(), service, new HashMap<String, Object>());
System.out.println("Service registered: " + service.name());
}
public static void main(String[] args) {
SpringApplication.run(Activator.class, args);
}
}
And one rest controller in First Module via which we fetch all services
#RestController
#RequestMapping("/data")
#Component
public class RController {
public Map<String, Object> fetchRecordsPipline() {
BundleContext bundleContext = Activator.getBundleContext();
Collection<ServiceReference<DataService>> references = bundleContext
.getServiceReferences(DataService.class, null);
for (ServiceReference<DataService> reference : references) {
DataService calcService = bundleContext.getService(reference);
System.out.println(calcService.name());
}
}
}
Same way second Module here we implement the above interface:
public class JsonDataSource implements DataService, Serializable {
public BatchStage<Object> getData(Pipeline pipeline, Map<String, Object> source) {
//Hazelcast json implementation
}
}
And Activator for Second module:
public class Activator implements BundleActivator {
public static BundleContext bundleContext;
ConfigurableApplicationContext appContext;
#Override
public void start(BundleContext context) throws Exception {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
appContext = SpringApplication.run(Activator.class);
bundleContext = context;
registerServices();
}
#Override
public void stop(BundleContext context) throws Exception {
bundleContext = null;
SpringApplication.exit(appContext, () -> 0);
}
public static BundleContext getBundleContext() {
return bundleContext;
}
private void registerServices() {
DataService service = new JsonDataSource();
bundleContext.registerService(DataService.class.getName(), service, new HashMap<String, Object>());
System.out.println("Service registered: " + service.name());
}
public static void main(String[] args) {
SpringApplication.run(Activator.class, args);
}
}
Hazelcast jet Instance in generated in First Module.
Now we run it in karaf both the modules are getting registered and when we call rest controller request it is only fetching First module from the Collection<ServiceReference> "JDBC Service" only not "Json Service" but both the service are registered properly in karaf, as per my understanding as I am using Hazelcast Jet it causing them to get registered in different context so now how to registered them both in same context(BundleContex/Spring Context) so they can communicate and I can join both sources data?
Here I have 3 Interfaces: InterfaceA and InterfaceB and SharedInterface
public interface InterfaceA {
/**
* print some message
*/
void printMsg();
}
public interface InterfaceB {
/**
* print some message
*/
void printMsg();
}
public interface SharedInterface {
/**
* print some message
*/
void printSharedMsg();
}
and there are 3 implementations of these interfaces:
public class ImplementA1 implements InterfaceA, SharedInterface {
#Override
public void printMsg() {
System.out.println("this is message of interfaceA1");
}
#Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementA1");
}
}
public class ImplementA2 implements InterfaceA, SharedInterface {
#Override
public void printMsg() {
System.out.println("this is message of interfaceA2");
}
#Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementA2");
}
}
public class ImplementB implements InterfaceB, SharedInterface {
#Override
public void printMsg() {
System.out.println("this is message of interfaceB");
}
#Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementB");
}
}
ImplementA1 and ImplementA2 are the same type of operation, ImplementB is another type of operation. So I decided to develop 2 config class to register ImplementA1,ImplementA2 and ImplementB which is showing below.
#Configuration
public class InterfaceAConfig {
#Bean
public InterfaceA registerInterfaceA1(){
return new ImplementA1();
}
#Bean
public InterfaceA registerInterfaceA2(){
return new ImplementA2();
}
}
#Configuration
public class InterfaceBConfig {
#Bean
public InterfaceB registerInterfaceB(){
return new ImplementB();
}
}
Now I want to let all beans which implement SharedInterface print their message in a component. And it works well,here is the code:
#Component
#AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//print shared message after IOC container refreshed
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingContextGetBean();
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
But I found another way of inject beans to component, using
#Autowired
List<TargetType> myListName
So I decided to change my SharedInterfaceComponent to this for test, and it worked:
#Component
#AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//todo why do spring failed due to this autowire?
#Autowired
private List<InterfaceA> autowiredList;
//print shared message after IOC container refreshed
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingAutowiredGerBean();
//usingContextGetBean();
}
private void usingAutowiredGerBean() {
for (InterfaceA interfaceA : autowiredList) {
if (SharedInterface.class.isAssignableFrom(interfaceA.getClass())){
((SharedInterface) interfaceA).printSharedMsg();
}
}
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
But when I tried to use SharedInterface instead of InerfaceA to get beans from IOC , it goes wrong. The code is showing below:
#Component
#AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//todo why do spring failed due to this autowire?
#Autowired
private List<SharedInterface> autowiredList;
//print shared message after IOC container refreshed
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingAutowiredGerBean();
//usingContextGetBean();
}
private void usingAutowiredGerBean() {
for (SharedInterface sharedInterface : autowiredList) {
if (SharedInterface.class.isAssignableFrom(sharedInterface.getClass())){
((SharedInterface) sharedInterface).printSharedMsg();
}
}
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
In this Demo the application will fail and showing
***************************
APPLICATION FAILED TO START
***************************
Description:
Field autowiredList in com.wwstation.test.config.SharedInterfaceComponent required a bean of type 'java.util.List' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'java.util.List' in your configuration.
But in my other projects, the same situation will not lead to a crush, I can get SharedInterface by using #Autowired but there I can only get beans implement InterfaceA or InterfaceB but never all of them. I thought, the not crushing case may be caused by some of my dependencies in other projects.
Can anyone help me about how to get all the SharedInterface more graceful? Thanks alot!
The problem is your configuration.
#Bean
public InterfaceA registerInterfaceA1(){
return new ImplementA1();
}
The problem with this is that Spring will use the return type of the method to see if it fullfils injection points (in this case your list). As InterfaceA isn't a SharedInterface eventually it will fail as there are no beans that implement the SharedInterface according to your configuration!.
What you should do with your own beans is to be as specific as possible in the return type. So instead of InterfaceA make it return the actual class ImplementA1 and ImplementA2. That way Spring, at configuration time, can determine that those implement SharedInterface and use those to fill the list.
#Bean
public ImplementA1 registerInterfaceA1(){
return new ImplementA1();
}
Let's say I have a custom ConstraintValidator:
public class FooValidator implements ConstraintValidator<ValidFoo, String> {
#Override
public void initialize(final ValidFoo foo) {
// No-op
}
#Override
public boolean isValid(final String foo, final ConstraintValidatorContext context) {
}
}
I'd like to be able to initialize this class by passing some configuration from the ServiceConfiguration in Dropwizard run or initialize.
Is this possible?
First, it's worth noting that the upcoming Dropwizard 2.0.0 release has built in support for this
For now, the process is a bit involved. You basically want to re-bootstrap the Hibernate validation but with a custom constraint validator factory that would support injection.
It's gonna involve about 4 custom classes, so bear with me. Here goes:
First, we start by registering a custom feature to wrap this functionality, into our Application class:
public void run(MainConfiguration config, Environment environment) throws Exception {
// ...
environment.jersey().register(InjectingValidationFeature.class);
}
Now we define the feature: InjectingValidationFeature - it basically registers our custom implementations within the service container:
public class InjectingValidationFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(ValidatorFactory.class).to(Validator.class).in(Singleton.class);
bind(InjectingConfiguredValidator.class).to(ConfiguredValidator.class).in(Singleton.class);
bind(InjectingConstraintValidatorFactory.class).to(ConstraintValidatorFactory.class).in(Singleton.class);
}
});
return true;
}
}
Now we define those classes that we are registering above. Let's start with the core piece, the InjectingConstraintValidatorFactory which is what Hibernate Validator will actually use to create the constraint validators. Note that because we are registering them in the container, we can actually start injecting stuff already, here is our custom ConstraintValidatorFactory making use of the service locator to make dependency injection possible:
public class InjectingConstraintValidatorFactory implements ConstraintValidatorFactory {
private final ServiceLocator serviceLocator;
#Inject
public InjectingConstraintValidatorFactory(ServiceLocator serviceLocator) {
this.serviceLocator = serviceLocator;
}
#Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
return this.serviceLocator.createAndInitialize(key);
}
#Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
this.serviceLocator.preDestroy(instance);
}
}
Now our factory for the central javax.validation.Validator interface:
public class ValidatorFactory implements Factory<Validator> {
private final ConstraintValidatorFactory constraintValidatorFactory;
#Inject
public ValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) {
this.constraintValidatorFactory = constraintValidatorFactory;
}
#Override
public Validator provide() {
return Validation.byDefaultProvider().configure().constraintValidatorFactory(
this.constraintValidatorFactory).buildValidatorFactory()
.getValidator();
}
#Override
public void dispose(Validator instance) {
// Nothing
}
}
And finally, our InjectingConfiguredValidator, notice how it's just using DropwizardConfiguredValidator but with an #Inject which would allow us to receive the validator from our ValidatorFactory above:
public class InjectingConfiguredValidator extends DropwizardConfiguredValidator {
#Inject
public InjectingConfiguredValidator(Validator validator) {
super(validator);
}
}
That's it. With the above, we managed to both register an injection-aware Validator with Jersey and also into our service container so you can also #Inject Validator anywhere and use it however you like.
I am trying to inject a object provided by HK2 Factory service in a Jersey Test Class but getting unsatisfied dependencies exception.
I have a factory service as below
public class TestFactory implements Factory<TestObject>{
private final CloseableService closeService;
#Inject
public TestFactory(CloseableService closeService) {
this.closeService = closeService;
}
#Override
public TestObject provide() {
TestObject casualObject = new TestObject();
this.closeService.add(() -> dispose(casualObject));
return casualObject;
}
#Override
public void dispose(TestObject instance) {
instance.destroy();
}
}
And a Jersey Test class
public class SampleTestCass extends JerseyTestNg.ContainerPerClassTest
{
//#Inject
private TestObject myTestObject;
private ServiceLocator locator;
#Override
protected Application configure()
{
ResourceConfig resConfig = new ResourceConfig(MyApi.class);
resConfig.register(getBinder());
locator = setupHK2(getBinder());
return resConfig;
}
// setup local hk2
public setupHK2(AbstractBinder binder)
{
ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create("test-locator");
DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
DynamicConfiguration dc = dcs.createDynamicConfiguration();
locator.inject(binder);
binder.bind(dc);
dc.commit();
return locator;
}
// get the binder
public AbstractBinder getBinder()
{
return new AbstractBinder() {
#Override
protected void configure() {
bindFactory(TestFactory.class, Singleton.class).to(TestObject.class).in(PerLookup.class);
}
}
}
#BeforeClass
public void beforeClass()
{
myTestObject = locator.getService(TestObject.class);
// use myTestObject
}
#AfterClass
public void afterClass()
{
if (locator != null) {
locator.shutdown();
}
}
#Test()
public void someTest()
{
// some test code...
}
}
And getting below exceptions
A MultiException has 3 exceptions. They are:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=CloseableService,parent=TestFactory,qualifiers={},position=0,optional=false,self=false,unqualified=null,2053349061)
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.test.factories.TestFactory errors were found
java.lang.IllegalStateException: Unable to perform operation: resolve on com.test.factories.TestFactory
CloseableService is a service available within a Jersey application. The ServiceLocator you created is not tied to the Jersey application. It is just a standalone locator. So trying to register the TestFactory with this locator will cause it to fail, as there is no CloseableService. The one that you registered with the ResourceConfig will work just fine.
Not sure what exactly you're trying to do, but if you want access to the service inside the test, one thing you can do is just bind the service as an instance, something like
class MyTest {
private Service service;
#Override
public ResourceConfig configure() {
service = new Service();
return new ResourceConfig()
.register(new AbstractBinder() {
#Override
public void configure() {
bind(service).to(Service.class);
}
})
}
}
I am trying to inject a vertx instance
public ServiceBinder(Vertx vertx) {
this.vertx = vertx;
}
I am binding like this
#Override
protected void configure() {
bind(Vertx.class).toInstance(this.vertx);
}
And I am invoking injection like this
public class BaseVerticle extends AbstractVerticle{
#Override
public void start(Future<Void> startFuture) {
Guice.createInjector(new ServiceBinder(vertx)).injectMembers(this);
}
}
Now I try to inject this in another class
public class DelegateFactory {
#Inject
private Vertx vertx;
}
However here the value of vertx is null. Do I need inject DelegateFactory too?
I tried annotating DelegateFactory with #Singleton, but it did not help
Make sure that :
Your ServiceBinder class extends com.google.inject.AbstractModule
Your DelegateFactory is binded in your ServiceBinder or another guice's AbstractModule subclass like that :
bind(DelegateFactory.class).in(Singleton.class)
or
bind(DelegateFactory.class).toInstance(...)
P.S : it's better to be fail-fast in your ServiceBinder constructor :
import static java.util.Objects.requireNonNull;
public ServiceBinder(Vertx vertx) {
this.vertx = requireNonNull(vertx, "vertx must not be null");
}