Comparison of Guice and(move to) Spring - spring

Could someone give me advice, please, how to re-write some method using simple Spring (w/o Boot)?
Here I have some code methods:
1. createInjector
private Injector injector;
someMethod(){
injector = Guice.createInjector(new ExampleClass1(), new ExampleClass2());}
2 setModules(Modules.override
setModules(Modules.override(new ExampleClass3()).with(new ExampleClass4()));
//////////////////////////////////////////////////////////////////
public static void setModules(Module... modules) {
initInjector(modules);
}
private static void initInjector(Module... modules) {
injector = Guice.createInjector(modules);
}
}

Taking the risk that my answer is too general.
Roughly saying you can think Guice modules as equivalent a configuration class with #Configuration annotation, that contains #Bean etc.
The Guice injector can be considered as equivalent to the Spring ApplicationContext.
So for example if we have two configuration files:
#Configuration
public class ConfigA {
#Bean
ExampleClass1 exampleClass1(){
return new ExampleClass1();
}
#Bean
ExampleClass2 exampleClass2(){
return new ExampleClass2();
}
}
#Configuration
public class ConfigB {
#Bean
ExampleClass1 exampleClass1(){
return new ExampleClass1();
}
#Bean
ExampleClass3 exampleClass2(){
return new ExampleClass3();
}
}
And Services ExampleClass4 that you want as alternative of ExampleClass3.
You may use the #Primary annotation
public class ExampleClass4 extends ExampleClass3 {
#Override
public String toString() {
return "ExampleClass4{}";
}
}
#Configuration
public class ConfigC {
#Bean
#Primary
ExampleClass3 exampleClass3(){
return new ExampleClass4();
}
}
So rewriting the app to Spring (core 5.2, not Spring boot) will be:
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ap = initAppContext();
overrideBinding(ap);
System.out.println(ap.getBean(ExampleClass3.class));
//prints ExampleClass4{}
}
private static AnnotationConfigApplicationContext initAppContext() {
AnnotationConfigApplicationContext ap = new AnnotationConfigApplicationContext();
ap.register(ConfigA.class, ConfigB.class);
return ap;
}
private static void overrideBinding(AnnotationConfigApplicationContext ap) {
ap.register(ConfigC.class);
ap.refresh();
}
}
This technic of overriding a binding will work only because ExampleClass3 wasn't defined as primary, if it doesn't that would not work and you need to consider a different approach.
For more information:
https://www.baeldung.com/spring-application-context
https://docs.spring.io/spring-javaconfig/docs/1.0.0.m3/reference/html/modularizing-configurations.html
Override bean definition in java config

Related

Spring boot - How to create spring beans for the objects created in static methods

I am trying to move old project to spring boot project. I created the spring project project and moved the code and now I am trying to springify the project by creating beans for the objects.
#SpringBootApplication
public class TestApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Override
public void run(String... args) throws CmdLineException {
final CommandLineArgs arguments = new CommandLineArgs();
CmdLineParser parser = new CmdLineParser(arguments);
parser.parseArgument(args);
A.generatedData(arguments);
}
}
class A{
public static void generatedData(CommandLineArgs arguments) {
LOG.info("Starting visualization.");
B b = new B(createModel(arguments));
b.build();
}
private static Model createModel(CommandLineArgs arguments) {
...
}
}
class B{
public void build() {
...
}
}
Some objects are created within the static method so I am not sure how to create beans of those objects. Can you please help convert this code in the spring world?
Original answer:
You typically just make a config class containing beans that return the object you want to wire in anywhere:
#Configuration
public class MyConfiguration {
#Bean
public B b() {
return new B();
}
}
And then you can wire them in like this:
#Component
#RequiredArgsConstructor
public class MyRunner implements CommandLineRunner {
private final B b;
#Override
public void run(String... args) throws Exception {
b.build();
}
}
Also, by implementing the CommandLineRunner or ApplicationRunner interfaces on a component, you can leave your application class alone...
These will run after the context is loaded and before your application starts.
You can also create beans with the #Component annotation:
#Component
public class B {
// ...
}
Or even create beans depending on other beans.
Here's a slightly more complex example:
#Configuration
public class DependentBeanConfig {
#Bean
SimpleBean someBean() { // interface
return new SomeBeanImpl(); // implementation
}
#Bean
DependentBean dependentBean(){
return new DependentBeanImpl(simpleBean());
}
}
#Configuration and #Service are just sub-types of the #Component annotation that basically do exactly the same. You can even create static beans inside them...
All of Spring is built upon this concept.
But you do not autowire static members, because that would be rather contradictory. If it belongs to the class it should not be wired in or vice versa. If you want to wire it in, it probably does not belong.
Another problem you might run in to is non-object oriented programming, where you might want to rethink the objects in the code.
Addendum:
To configure the bean create some setters:
public class B {
private YourProperty yourProperty;
//...
public setYourProperty(YourProperty yourProperty) {
this.yourProperty = yourProperty;
}
}
And in the bean creation:
#Configuration
public class MyConfiguration {
#Bean
public B b() {
B b = new B();
b.setYourProperty(yourProperty)
return b;
}
}
You can also wire in an Environment bean. Then you can fetch properties from the environment:
#Configuration
// You don't need this annotation for application.properties in Spring Boot projects
// I added it to easily understand what is going on
#PropertySource("classpath:yourpropertiesfile.properties")
public class MyConfiguration {
private Environment environment;
// ...
#Bean
public B b() {
B b = new B();
b.setYourProperty(environment.getProperty("yourProperty"))
return b;
}
}
You can then override these properties with commandline arguments as explained here.
The commandline / applciation runner I showed earlier can also take the application arguments without overriding environment variables.

How to override DefaultListableBeanFactory in Spring Boot application?

Are there ways to override properties of DefaultListableBeanFactory in Spring Boot application?
For example, I want to set the DefaultListableBeanFactory.allowBeanDefinitionOverriding property to false.
EDIT
Use case.
I have pretty plain class:
#Data
#AllArgsConstructor
class MyTempComponent {
private String value;
}
Which I want use as #Bean in my application but this bean can be defined several times, for example:
#Configuration
class TestConfiguration1 {
#Bean
MyTempComponent myTempComponent() {
return new MyTempComponent("Value 1");
}
}
#Configuration
class TestConfiguration2 {
#Bean
MyTempComponent myTempComponent() {
return new MyTempComponent("Value 2");
}
}
Also there is a consumer of that bean:
#Component
class TestConfiguration3 {
private MyTempComponent myTempComponent;
#Autowired
public TestConfiguration3(MyTempComponent myTempComponent) {
this.myTempComponent = myTempComponent;
}
#PostConstruct
public void print() {
System.out.println(this.myTempComponent.getValue());
}
}
When an application starts it prints "Value 2" message, i.e. initializes myTempComponent bean from TestConfiguration2.
I want to prohibit creation of that bean (and any other beans) if there are two or more candidates.
As I realized I can reach this goal via setting DefaultListableBeanFactory.allowBeanDefinitionOverriding to false.
But how to set this property in Spring Boot application? Could you provide an example please?
You can set
private static class CustomAppCtxInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
#Override
public void initialize(GenericApplicationContext applicationContext) {
applicationContext
.getDefaultListableBeanFactory()
.setAllowBeanDefinitionOverriding(false);
}
}
and then have
public static void main(String[] args) {
try {
final SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.addInitializers(new CustomAppCtxInitializer());

unable to use #AspectJ with Spring-Apache CXF services

I am new to spring and am working on a rest service written using Spring and Apache CXF with Java Configurations. I have the following rest service.
#Path("/release/")
#Component
#RestService
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ReleaseResource extends AbstractService implements IResource {
#Override
#CustomLogger
#GET
public Response get() {
//Some Logic
return Response.ok("Success!!").build();
}
}
I have created an aspect using #AspectJ for logging. However, the aspect is not working on the services written in CXF. I did a bit of searching in net and found that Spring needs proxy beans for the aspects to work. Then I tried few approaches such as
Making the service class implement an interface
Using CGLIB library and scope proxy mode TARGET_CLASS
Extending a class with method
#Override
public void setMessageContext(MessageContext context) {
this.context = context;
}
But none of them worked.
Any idea if it is possible to run the aspect around the services?
If yes, can someone please tell me how to.
I have read that this can be achieved by bytecode weaving the aspectj manually instead of using spring aspectj autoproxy (not sure how to do it though). Can someone tell me if this is a good option and how to do it?
EDIT:
Sorry for the incomplete info provided. Attaching the other classes
#Aspect
#Configuration
public class LoggerAspect {
#Pointcut(value = "execution(* *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(CustomLogger)")
public Object logAction(ProceedingJoinPoint pjp, CustomLogger customLogger) throws Throwable {
//Log Some Info
return pjp.proceed();
}
}
Web Initializer class:
#Configuration
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(TestConfig.class);
return appContext;
}
}
Config Class:
#Configuration
#ComponentScan(basePackages = "com.my.package")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TestConfig {
private static final String RESOURCES_PACKAGE = "com.my.package";
#ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public JacksonJsonProvider jacksonJsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public LoggerAspect getLoggerAspect() {
return new LoggerAspect();
}
#Bean
IResource getReleaseResource() {
return new ReleaseResource();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(),
JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setProvider(jacksonJsonProvider());
return factory.create();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, TestConfig.RESOURCES_PACKAGE);
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
}
RestServiceBeanScanner class
public final class RestServiceBeanScanner {
private RestServiceBeanScanner() {
}
public static List<Object> scan(ApplicationContext applicationContext, String... basePackages) {
GenericApplicationContext genericAppContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(genericAppContext, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RestService.class));
scanner.scan(basePackages);
genericAppContext.setParent(applicationContext);
genericAppContext.refresh();
List<Object> restResources = new ArrayList<>(
genericAppContext.getBeansWithAnnotation(RestService.class).values());
return restResources;
}
}

Spring Boot Apache Camel Routes testing

I have a Springboot application, where I have some Camel routes configured.
public class CamelConfig {
private static final Logger LOG = LoggerFactory.getLogger(CamelConfig.class);
#Value("${activemq.broker.url:tcp://localhost:61616}")
String brokerUrl;
#Value("${activemq.broker.maxconnections:1}")
int maxConnections;
#Bean
ConnectionFactory jmsConnectionFactory() {
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory(new ActiveMQConnectionFactory(brokerUrl));
pooledConnectionFactory.setMaxConnections(maxConnections);
return pooledConnectionFactory;
}
#Bean
public RoutesBuilder route() {
LOG.info("Initializing camel routes......................");
return new SpringRouteBuilder() {
#Override
public void configure() throws Exception {
from("activemq:testQueue")
.to("bean:queueEventHandler?method=handleQueueEvent");
}
};
}
}
I want to test this route from activemq:testQueue to queueEventHandler::handleQueueEvent.
I tried different things mentioned here http://camel.apache.org/camel-test.html, but doesn't seem to get it working.
I am trying to do something like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {CamelConfig.class, CamelTestContextBootstrapper.class})
public class CamelRouteConfigTest {
#Produce(uri = "activemq:testQueue")
protected ProducerTemplate template;
#Test
public void testSendMatchingMessage() throws Exception {
template.sendBodyAndHeader("testJson", "foo", "bar");
// Verify handleQueueEvent(...) method is called on bean queueEventHandler by mocking
}
But my ProducerTemplate is always null. I tried auto-wiring CamelContext, for which I get an exception saying it cannot resolve camelContext. But that can be resolved by adding SpringCamelContext.class to #SpringBootTest classes. But my ProducerTemplate is still null.
Please suggest. I am using Camel 2.18 and Spring Boot 1.4.
In Camel 2.22.0 and ongoing, which supports Spring Boot 2 you can use the following template to test your routes with Spring Boot 2 support:
#RunWith(CamelSpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
...
})
#EnableAutoConfiguration
#DisableJmx
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class RouteTest {
#TestConfiguration
static class Config {
#Bean
CamelContextConfiguration contextConfiguration() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext camelContext) {
// configure Camel here
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
// Start your manual routes here
}
};
}
#Bean
RouteBuilder routeBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:someEndpoint").to("mock:done");
}
};
}
// further beans ...
}
#Produce(uri = "direct:start")
private ProducerTemplate template;
#EndpointInject(uri = "mock:done")
private MockEndpoint mockDone;
#Test
public void testCamelRoute() throws Exception {
mockDone.expectedMessageCount(1);
Map<String, Object> headers = new HashMap<>();
...
template.sendBodyAndHeaders("test", headers);
mockDone.assertIsSatisfied();
}
}
Spring Boot distinguishes between #Configuration and #TestConfiguration. The primer one will replace any existing configuration, if annotated on a top-level class, while #TestConfiguration will be run in addition to the other configurations.
Further, in larger projects you might run into auto-configuration issues as you can't rely on Spring Boot 2 to configure your custom database pooling or what not correctly or in cases where you have a specific directory structure and the configurations are not located within a direct ancestor directory. In that case it is proabably preferable to omit the #EnableAutoConfiguration annotation. In order to tell Spring to still auto-configure Camel you can simply pass CamelAutoConfiguration.class to the classes mentioned in #SpringBootTest
#SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = {
Route1.class,
Route2.class,
RouteTest.Config.class,
CamelAutoConfiguration.class
}
As no automatic configuration is performed, Spring won't load the test configuration inside your test class nor initialize Camel as well. By adding those configs to the boot classes manually Spring will do it for you.
For one route with MQ and Spring Boot like this:
#Component
public class InboundRoute extends RouteBuilder {
#Override
public void configure() {
JaxbDataFormat personDataFormat = new JaxbDataFormat();
personDataFormat.setContextPath(Person.class.getPackage().getName());
personDataFormat.setPrettyPrint(true);
from("direct:start").id("InboundRoute")
.log("inbound route")
.marshal(personDataFormat)
.to("log:com.company.app?showAll=true&multiline=true")
.convertBodyTo(String.class)
.inOnly("mq:q.empi.deim.in")
.transform(constant("DONE"));
}
}
I use adviceWith in order to replace the endpoint and use only mocks:
#RunWith(CamelSpringBootRunner.class)
#UseAdviceWith
#SpringBootTest(classes = InboundApp.class)
#MockEndpoints("mock:a")
public class InboundRouteCamelTest {
#EndpointInject(uri = "mock:a")
private MockEndpoint mock;
#Produce(uri = "direct:start")
private ProducerTemplate template;
#Autowired
private CamelContext context;
#Test
public void whenInboundRouteIsCalled_thenSuccess() throws Exception {
mock.expectedMinimumMessageCount(1);
RouteDefinition route = context.getRouteDefinition("InboundRoute");
route.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() {
weaveByToUri("mq:q.empi.deim.in").replace().to("mock:a");
}
});
context.start();
String response = (String) template.requestBodyAndHeader("direct:start",
getSampleMessage("/SimplePatient.xml"), Exchange.CONTENT_TYPE, MediaType.APPLICATION_XML);
assertThat(response).isEqualTo("DONE");
mock.assertIsSatisfied();
}
private String getSampleMessage(String filename) throws Exception {
return IOUtils
.toString(this.getClass().getResourceAsStream(filename), StandardCharsets.UTF_8.name());
}
}
I use the following dependencies: Spring Boot 2.1.4-RELEASE and Camel 2.23.2. The complete source code is available on Github.
This is how I did this finally:
#RunWith(SpringRunner.class)
public class CamelRouteConfigTest extends CamelTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(CamelRouteConfigTest.class);
private static BrokerService brokerSvc = new BrokerService();
#Mock
private QueueEventHandler queueEventHandler;
#BeforeClass
// Sets up an embedded broker
public static void setUpBroker() throws Exception {
brokerSvc.setBrokerName("TestBroker");
brokerSvc.addConnector("tcp://localhost:61616");
brokerSvc.setPersistent(false);
brokerSvc.setUseJmx(false);
brokerSvc.start();
}
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new CamelConfig().route();
}
// properties in .yml has to be loaded manually. Not sure of .properties file
#Override
protected Properties useOverridePropertiesWithPropertiesComponent() {
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
PropertySource<?> applicationYamlPropertySource = loader.load(
"properties", new ClassPathResource("application.yml"),null);// null indicated common properties for all profiles.
Map source = ((MapPropertySource) applicationYamlPropertySource).getSource();
Properties properties = new Properties();
properties.putAll(source);
return properties;
} catch (IOException e) {
LOG.error("application.yml file cannot be found.");
}
return null;
}
#Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry jndi = super.createRegistry();
MockitoAnnotations.initMocks(this);
jndi.bind("queueEventHandler", queueEventHandler);
return jndi;
}
#Test
// Sleeping for a few seconds is necessary, because this line template.sendBody runs in a different thread and
// CamelTest takes a few seconds to do the routing.
public void testRoute() throws InterruptedException {
template.sendBody("activemq:productpushevent", "HelloWorld!");
Thread.sleep(2000);
verify(queueEventHandler, times(1)).handleQueueEvent(any());
}
#AfterClass
public static void shutDownBroker() throws Exception {
brokerSvc.stop();
}
}
Did you try using Camel test runner?
#RunWith(CamelSpringJUnit4ClassRunner.class)
If you are using camel-spring-boot dependency, you may know that it uses auto configuration to setup Camel:
CamelAutoConfiguration.java
It means that you may also need to add #EnableAutoConfiguration to your test.

How to register many object as beans in Spring Java Config?

I would like dynamically register multiple objects as Spring beans. Is is possible, without BeanFactoryPostProcessor?
#Configuration public class MyConfig {
#Bean A single() { return new A("X");}
#Bean List<A> many() { return Arrays.asList(new A("Y"), new A("Z"));}
private static class A {
private String name;
public A(String name) { this.name = name;}
#PostConstruct public void print() {
System.err.println(name);
}
}
}
Actual output shows only one bean is working:
X
Expected:
X Y Z
Spring 4.3.2.RELEASE
You should specify your A bean definition kind of prototype with a parameter
#Configuration
public class MyConfig {
#Bean #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
A template(String seed) {
return new A(seed);
}
#Bean
String singleA() {
return "X";
}
#Bean
List<A> many() {
return asList(template("Y"), template("Z"));
}
private static class A {
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
A a = (A) context.getBean("template");
System.out.println(a);
List<A> l = (List<A>) context.getBean("many");
System.out.println(l);
}
}
prototype scope allows Spring to create a new A with every template execution and register an instance into context.
The result of main execution is as you expect
Y
Z
MyConfig$A#15bfd87
[MyConfig$A#543e710e, MyConfig$A#57f23557]
X
What I want is impossible but requested with https://jira.spring.io/browse/SPR-13348
If you think multiple bean registration is OK please upvote

Resources