-
-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Inject #590
base: master
Are you sure you want to change the base?
Conversation
Now everything (almost) is configurable via DI container and the things remain perfectly the same until now if no DI container is detected. Only one new tiny dependency is added ( From the beginning, we wanted this feature to be as unobtrusive as possible, with as less as possible changes. I think that we can do the code more compact but this is another story. For example for each inject aware field we have a declaration and a lazy initialization getter (and in some cases a setter): @Inject
private Optional<ErrorHandler> errorHandler = Optional.empty();
public ErrorHandler getErrorHandler() {
if (!errorHandler.isPresent()) {
errorHandler = Optional.of(new DefaultErrorHandler(this));
}
return errorHandler.get();
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = Optional.of(errorHandler);
} I prefer something more verbose/light as: @Inject
private Optional<ErrorHandler> errorHandler;
public ErrorHandler getErrorHandler() {
return OptionalUtils.setOnNull(Supplier<ErrorHandler>).get();
} Also the proposed implementation with lazy initialization getters come with some improvements from performance point of view because the objects are initialized only on request. That is all. I will add in my next two comments how I tested with Spring and Guice. If this PR will be accepted, after merge I will update |
For Spring Test, I modified pippo-demo-spring public class SpringApplication3 extends ControllerApplication {
@Inject
private List<? extends Controller> controllers;
@Override
protected void onInit() {
// add routes for static content
addPublicResourceRoute();
addWebjarsResourceRoute();
addControllers(controllers.toArray(new Controller[0]));
}
} @Configuration
@ComponentScan
public class SpringConfiguration3 extends SpringConfiguration {
@Bean
public ContactService contactService() {
return new InMemoryContactService();
}
@Bean
public TemplateEngine templateEngine() {
return new SimpleTemplateEngine();
}
@Bean
public Router router() {
return new CustomRouter();
}
@Bean
public WebServer webServer() {
return new TjwsServer();
}
@Bean
public PippoSettings pippoSettings() {
return new PippoSettings();
}
@Bean
public Application application() {
return new SpringApplication3();
}
@Bean
public Pippo pippo() {
return new Pippo(application()).setServer(webServer());
}
} public class SpringDemo3 {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration3.class);
Pippo pippo = context.getBean(Pippo.class);
pippo.start();
}
} @Path
@Component
public class ContactsController extends Controller {
@Inject
private ContactService contactService;
@Inject
private TemplateEngine templateEngine;
@GET
public void sayHello() {
StringWriter writer = new StringWriter();
Map<String, Object> model = new HashMap<>();
model.put("name", "Decebal");
templateEngine.renderString("Hello ${name}", model, writer);
getResponse().send(writer.toString());
}
} |
For Guice Test, I modified pippo-demo-guice public class GuiceApplication3 extends ControllerApplication {
@Inject
private List<? extends Controller> controllers;
@Override
protected void onInit() {
// add routes for static content
addPublicResourceRoute();
addWebjarsResourceRoute();
addControllers(controllers.toArray(new Controller[0]));
}
} public class GuiceModule3 extends AbstractModule {
@Override
protected void configure() {
bind(ContactService.class).to(InMemoryContactService.class).asEagerSingleton();
bind(Application.class).to(GuiceApplication3.class).asEagerSingleton();
bind(Router.class).to(CustomRouter.class).in(Scopes.SINGLETON);
bind(TemplateEngine.class).to(SimpleTemplateEngine.class).asEagerSingleton();
bind(WebServer.class).to(TjwsServer.class).in(Scopes.SINGLETON);
bind(Pippo.class);
bindOptionalApplication();
bindOptionalControllerApplication();
}
@Singleton
@Provides
@Inject
public List<? extends Controller> controllers(ContactsController contacts) {
return Arrays.asList(contacts);
}
private void bindOptionalApplication() {
OptionalBinder.newOptionalBinder(binder(), ContentTypeEngines.class);
OptionalBinder.newOptionalBinder(binder(), ErrorHandler.class);
OptionalBinder.newOptionalBinder(binder(), HttpCacheToolkit.class);
OptionalBinder.newOptionalBinder(binder(), Languages.class);
OptionalBinder.newOptionalBinder(binder(), Messages.class);
OptionalBinder.newOptionalBinder(binder(), MimeTypes.class);
OptionalBinder.newOptionalBinder(binder(), Router.class);
OptionalBinder.newOptionalBinder(binder(), WebSocketRouter.class);
OptionalBinder.newOptionalBinder(binder(), RequestResponseFactory.class);
OptionalBinder.newOptionalBinder(binder(), RoutePreDispatchListenerList.class);
OptionalBinder.newOptionalBinder(binder(), RoutePostDispatchListenerList.class);
OptionalBinder.newOptionalBinder(binder(), TemplateEngine.class);
OptionalBinder.newOptionalBinder(binder(), new TypeLiteral<RouteHandler<?>>(){});
OptionalBinder.newOptionalBinder(binder(), new TypeLiteral<List<Initializer>>(){});
}
private void bindOptionalControllerApplication() {
OptionalBinder.newOptionalBinder(binder(), ControllerFactory.class);
OptionalBinder.newOptionalBinder(binder(), ControllerInitializationListenerList.class);
OptionalBinder.newOptionalBinder(binder(), ControllerInstantiationListenerList.class);
OptionalBinder.newOptionalBinder(binder(), ControllerInvokeListenerList.class);
OptionalBinder.newOptionalBinder(binder(), new TypeLiteral<List<MethodParameterExtractor>>(){});
}
} public class GuiceDemo3 {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new GuiceModule3());
Pippo pippo = injector.getInstance(Pippo.class);
pippo.start();
}
} @Path
public class ContactsController extends Controller {
@Inject
private ContactService contactService;
@Inject
private TemplateEngine templateEngine;
@GET
public void sayHello() {
StringWriter writer = new StringWriter();
Map<String, Object> model = new HashMap<>();
model.put("name", "Decebal");
templateEngine.renderString("Hello ${name}", model, writer);
getResponse().send(writer.toString());
}
} I don't like the complexity of |
Sorry I didn't understand why What if I have dozens of controllers? |
@decebals , will you still make changes or can I test this version in my application? |
I think that you can test it. The code is perfect functional from what I see until now. Maybe little adjustments in time. |
Your question is good. In my last (relative big) project I use Pippo with Spring. In Spring this part with gathering all controllers and inject them in application is easy and automatically. All you have to do is to add |
In the end I think that I solved the problem in an elegant way using Guice Multibinding and Reflections. public class GuiceModule3 extends AbstractModule {
@Override
protected void configure() {
bind(ContactService.class).to(InMemoryContactService.class).asEagerSingleton();
bind(Application.class).to(GuiceApplication3.class).asEagerSingleton();
bind(Router.class).to(CustomRouter.class).in(Scopes.SINGLETON);
bind(TemplateEngine.class).to(SimpleTemplateEngine.class).asEagerSingleton();
bind(WebServer.class).to(TjwsServer.class).in(Scopes.SINGLETON);
bind(Pippo.class);
bindControllers();
bindOptionalApplication();
bindOptionalControllerApplication();
}
private void bindControllers() {
// retrieve controller classes
Reflections reflections = new Reflections(getClass().getPackage().getName());
Set<Class<? extends Controller>> controllers = reflections.getSubTypesOf(Controller.class);
// bind found controllers
Multibinder<Controller> multibinder = Multibinder.newSetBinder(binder(), Controller.class);
controllers.forEach(controller -> multibinder.addBinding().to(controller));
}
} public class GuiceApplication3 extends ControllerApplication {
@Inject
private Set<Controller> controllers;
@Override
protected void onInit() {
// add routes for static content
addPublicResourceRoute();
addWebjarsResourceRoute();
addControllers(controllers.toArray(new Controller[0]));
}
} I tested with multiple controllers and the result is good. |
I already use the Reflections lib and it is very good! |
My boot is heavily modified, so for now I won't be able to test it thoroughly. But I have good news: with this version my application continues to work normally. |
I'm trying to adapt my application to this model... @decebals , I use the The
|
For me, Guice's dependency injection just only worked like this: @Inject
private Set<Controller> controllers; obs: |
Yes, it's |
In #590 (comment), it's |
I inject As I mentioned in #565, the pippo - spring integration is good enough for me and without this PR. This PR is useful when you want to fine tuning the pippo stack from DI (Spring, Guice), entirely. |
I obtained relative ( The code in this case looks like: //@Singleton
public class AvajeApplication extends ControllerApplication {
private List<Controller> controllers;
// @Inject
public AvajeApplication(List<Controller> controllers) {
this.controllers = controllers;
}
@Override
protected void onInit() {
// add routes for static content
addPublicResourceRoute();
addWebjarsResourceRoute();
addControllers(controllers.toArray(new Controller[0]));
}
} @Factory
public class AvajeConfiguration {
@Bean
public ContactService contactService() {
return new InMemoryContactService();
}
@Bean
public PippoSettings pippoSettings() {
return new PippoSettings();
}
// @Bean
// public List<Controller> controllers(ContactsController contactsController) {
// System.out.println("AvajeConfiguration.controllers");
// return Collections.singletonList(contactsController);
// }
@Bean
public Application application(ContactsController contactsController, TestController testController) {
return new AvajeApplication(Arrays.asList(contactsController, testController));
}
// @Bean
// public Pippo pippo(Application application, WebServer webServer) {
// return new Pippo(application).setServer(webServer);
// }
} public class AvajeDemo {
public static void main(String[] args) {
BeanScope beanScope = BeanScope.newBuilder().build();
Pippo pippo = beanScope.get(Pippo.class);
pippo.start();
}
} @Path
@Singleton
public class ContactsController extends Controller {
@Inject
ContactService contactService;
@Inject
TemplateEngine templateEngine;
@GET
public void index() {
getResponse().bind("contacts", contactService.getContacts());
getResponse().render("contacts");
}
} |
Hi! I understand that it is possible to inject To explain it better, something like this: Injector injector = Guice.createInjector(
new PippoGuiceModule(),
new AppJpaPersistModule("persistenceUnitName", pippoSettings), // <<< need PippoSettings instance here
new AppGuiceModule()
);
GuiceInjector.set(injector);
Pippo pippo = injector.getInstance(Pippo.class);
pippo.start(); ps: I'm looking for a way to work around this problem. |
We might have abstract controllers, so maybe it's better to avoid bind errors (at least in Guice): Reflections reflections = new Reflections(getClass().getPackage().getName(), new SubTypesScanner());
Set<Class<? extends Controller>> controllers = reflections.getSubTypesOf(Controller.class)
.stream()
.filter(clazz -> clazz.isAnnotationPresent(ro.pippo.controller.Path.class))
.collect(Collectors.toSet()) Or some other logic that checks if it's a concrete class. |
I don't visualize your implementation. How |
What about https://stackoverflow.com/questions/39734343/injecting-a-dependency-into-guice-module? |
Oh, sorry 😅, I forgot to mention it's a class of mine. It's just a wrapper for Guice's It goes something like this: public class AppJpaPersistModule implements Module {
private final PippoSettings settings;
public JPAGuiceModule(PippoSettings settings) {
this.settings = settings;
}
@Override
public void configure(Binder binder) {
JpaPersistModule jpaModule = new JpaPersistModule(Constantes.PU_NAME);
jpaModule.properties( ... ); // TODO: get properties from PippoSettings and add here
binder.install(jpaModule);
}
} ps: But I think I'll change the strategy so I don't need |
@decebals , I use Freemarker and it's not working. The problem is that the To make it work I did:
I configure the Guice module like this: @Override
protected void configure() {
bind(Application.class).to(PippoApplication.class).asEagerSingleton();
bind(TemplateEngine.class).to(FreemarkerTemplateEngine.class).asEagerSingleton();
// ...
} It would be nice to be able to leave the annotation just on the |
@mhagnumdw |
# Conflicts: # pippo-controller-parent/pippo-controller/src/main/java/ro/pippo/controller/ControllerApplication.java
@decebals , please update from master. |
# Conflicts: # pippo-controller-parent/pippo-controller/src/main/java/ro/pippo/controller/ControllerApplication.java
Done. |
@decebals , please update from master. |
Done |
Currently I register a content type like this: What do you think we also use dependency injection to register In Guice we can use If you agree, could you implement it? So I would validate doing the tests in my application. |
Sure, I will do it. Now I am in a mini vacation with family. |
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
@mhagnumdw I don't know if you abandoned the ship but I will write here some of my conclusions :). import javax.annotation.Nullable;
import javax.inject.Inject;
class MyClass {
@Inject @Nullable
private Greeting greeting;
} Both Spring and Guice know how to deal with this combination of annotations. import javax.annotation.Nullable;
import javax.inject.Inject;
class MyClass {
private Greeting greeting;
@Inject
public void setGreeting(@Nullable Greeting greeting) {
this.greeting = greeting;
}
} What I don't like is that in all situations (our initial solution based on And this problem is because import org.springframework.beans.factory.annotation.Autowired;
import com.google.inject.Inject;
class MyClass {
@Autowired(required = false)
@Inject(optional = true)
private Greeting greeting;
} but in this case we must add Everything started from the idea to have something/everything configurable via most popular java IoC (Spring and Guice), but without forcing the pippo developer to use IoC (or a specific IoC). |
Hi @decebals !! I paused this activity for timing reasons. I still think it's a worthwhile activity. But unfortunately, given its magnitude, I won't have time to see it carefully. My application that uses Pippo is only receiving fixes and they are sporadic. |
#554