Spring: Is it possible to lazy load all Spring Beans without #Lazy - spring

I am looking to lazy load all spring beans when running integration tests that utilize #ContextConfiguration. Ideally I would be able to apply this lazy loading in one place and have it applied to any beans that are loaded via the #ContextConfiguration annotation.

Yes it is.
You can do this using BeanDefinitionRegistryPostProcessor/BeanFactoryPostProcessor.
#Configuration
static class LazyBeans implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry bdr) throws BeansException {
for (String name : bdr.getBeanDefinitionNames()) {
final BeanDefinition beanDefinition = bdr.getBeanDefinition(name);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
beanDefinition.setLazyInit(true);
}
}
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory clbf) throws BeansException {}
}

Related

How to access properties in BeanFactoryPostProcessor with spring boot 1.5.x

I am trying to get properties from application.yml in BeanFactoryPostProcessor with spring boot 1.5.x:
The application.yml:
prong:
nfcloan:
jackson:
json-sub-types-package:
- com.shuweicloud.starter.acc.dto.request
#ConfigurationProperties(prefix = "prong.nfcloan.jackson")
public class JacksonProperties {
private List<String> jsonSubTypesPackage;
public List<String> getJsonSubTypesPackage() {
return jsonSubTypesPackage;
}
public void setJsonSubTypesPackage(List<String> jsonSubTypesPackage) {
this.jsonSubTypesPackage = jsonSubTypesPackage;
}
}
#Component
public class AccBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
#Autowired
private JacksonProperties jacksonProperties;
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
List<String> packages = jacksonProperties.getJsonSubTypesPackage();
// do something
}
}
The main class:
#SpringBootApplication
#EnableConfigurationProperties({JacksonProperties.class})
public class AccountingApplication {
public static void main(String[] args) {
SpringApplication.run(AccountingApplication.class, args);
}
}
But the packages variable is null. How to solve it?
I found a solution:
#Component
public class AccBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#SuppressWarnings("unchecked")
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
List<String> packages = environment.getProperty("prong.nfcloan.jackson.json-sub-types-package", List.class);
// do something
}
}
Spring boot internally uses Binder APIs to "map" the resolved properties into the #ConfigurationProperties beans.
Indeed, this resolution happens during the spring boot startup process after the BeanFactoryPostProcessors get created.
Now your solution will clearly work, because you kind of "bypass" this resolution.
However if you want to still have the Configuration as an instance of JacksonProperties (might be relevant if you have a lot of properties to resolve, or in general prefer to work more in a more spring-ish manner), you can use this binder API:
// inside the "postProcessBeanFactory" method, using the injected environment
BindResult<ExampleProperties> bindResult = Binder.get(environment)
.bind("prong.nfcloan.jackson", JacksonProperties.class);
JacksonProperties properties = bindResult.get();

#Autowired does not work with #Configurable

I am trying to do an image upload API. I have a ImageUpload task as follows,
#Component
#Configurable(preConstruction = true)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> {
#Autowired
private ImageUploadService imageUploadService;
#Override
public JSONObject call() throws Exception {
....
//Upload image via `imageUploadService`
imageUploadService.getService().path('...').post('...'); // Getting null pointer here for imageUploadService which is a WebTarget
}
}
The ImageUploadService looks like the below,
#Component
public class ImageUploadService {
#Inject
#EndPoint(name="imageservice") //Custom annotation, battle tested and works well for all other services
private WebTarget imageservice;
public WebTarget getService() {
return imageservice;
}
}
Here is the spring boot application class,
#ComponentScan
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.ENABLED)
#EnableAutoConfiguration
public class ImageApplication extends SpringBootServletInitializer {
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new RequestContextListener());
}
public static void main(String[] args) throws IOException {
SpringApplication.run(ImageApplication.class);
}
}
Additional information :
Spring version of dependencies are at 4.2.5.RELEASE
pom.xml has dependencies added for spring-aspects and
spring-instrument
I am getting a NullPointerException in ImageUploadTask. My suspicion is that #Autowired doesn't work as expected.
Why wouldn't work and how do I fix this?
Is it mandatory to use #Autowired only when I use #Conigurable, why not use #Inject? (though I tried it and getting same NPE)
By default the autowiring for the #Configurable is off i.e. Autowire.NO beacuse of which the imageUploadService is null
Thus update the code to explicity enable it either as BY_NAME or BY_TYPE as below.
#Component
#Configurable(preConstruction = true, autowire = Autowire.BY_NAME)
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ImageUploadTask implements Callable<JSONObject> { .... }
Rest of the configuration viz. enabling load time weaving seems fine.
Also regarding #Inject annotation have a look here which pretty much explains the difference (or similarity perhaps)

Using test application.properties file with CamelSpringTestSupport in Spring Boot

Prerequisites
Apache Tomcat 7
Spring 4.1.5.RELEASE
Spring Boot 1.2.2.RELEASE
Apache Camel 2.15.1
Problem
I am Using Spring Boot with a configuration class which is also used by EndpointSetup.
#SpringBootApplication
#Import({MyConfiguration.class, EndpointSetup.class})
public class MyFatJarRouter extends FatJarRouter { ... }
#Configuration
#ConfigurationProperties(prefix = "camel.route", ignoreUnknownFields = false)
public class MyConfiguration {
private List<String> brokerUrl = new ArrayList<>();
public List<String> getBrokerUrl() {return brokerUrl;}
public void setBrokerUrl(List<String> brokerUrl) {this.brokerUrl = brokerUrl;}
}
In production properties will be read from conf/application.properties by default.
I want to test my routes via CamelSpringTestSupport
So I have tried following:
I have placed a application.properties under test/resources/config/application.properties (--> in classpath of test)
then wrote following:
public class MyJmsTest extends CamelSpringTestSupport {
#Override
protected AbstractApplicationContext createApplicationContext() {
return new AnnotationConfigApplicationContext(MyFatJarRouter.class);
}
#Test
public void myTest() throws Exception {
...
}
}
In the example above the configuration is not read from the application.properties placed in test folder.
How can I read a test specific config file in my CamelSpringTestSupport Unit-Test?
I may be little late in answering, but there is a better way than hacking endpoints. The following solution uses toD introduced in Camel 2.16. I wrote a custom component "github" (there's an official one as well), and the following is how I test it. Note that I'm not using a single Camel proprietary annotation. To inject properties, I can either use the properties attribute in #SpringBootTest, or any of the other standard techniques available in Spring Boot.
Note that I'm using $simple{...} to avoid clash with Spring property resolution.
<rant>
And yes, Camel documentation sucks! They write it like release notes, with a section dedicated to each release, and don't seem to update the doc to keep up with the latest versions (the following technique is not documented). Imagine going to a restaurant and asking for the special, only to be told by the server about the special for the day before, and the week before, and so on. How about versioning the doc instead?
</rant>
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
public class GitHubRouteTest {
#Autowired
private CamelContext camelContext;
#Autowired
private ProducerTemplate template;
#Autowired
private GitHubClient gitHubClient;
#Test
public void testGitHubClientInvoked() throws InterruptedException {
template.sendBodyAndHeader("direct:start", "whatever",
"endpoint", "commits/test/test?username=test&password=test");
verify(gitHubClient).getCommitsForARepo(eq("test"), eq("master"), eq("test"), eq(20));
}
#SpringBootApplication
public static class TestApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(TestApplication.class)
.web(false)
.run(args);
}
#Bean
public RouteBuilder testRoute() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.toD("github:$simple{in.header.endpoint}");
}
};
}
#Bean
public GitHubClient mockGitHubClient() {
GitHubClient mock = Mockito.mock(GitHubClient.class);
return mock;
}
}
}
I solved it by using standard spring unit-tests like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#ActiveProfiles("test") // Load applicaton-test.properties in test/resources/config/application-test.properties
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) // cleanup spring context because jms broker does not exit properly
public class MyJmsTest {
private static final String MOCK_MY_ENDPOINT = "mock:myEndpoint";
#Autowired
CamelContext context;
#Autowired
ApplicationContext applicationContext;
#Autowired
ProducerTemplate producerTemplate;
#Before
public void configureMocks() throws Exception {
context.getRouteDefinition("MyRoute")
.adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveByToString(".*myEndPointId.*")
.replace()
.to(MOCK_MY_ENDPOINT);
}
});
final MockEndpoint endpoint = context.getEndpoint(MOCK_MY_ENDPOINT, MockEndpoint.class);
endpoint.whenAnyExchangeReceived(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
InputStream inStream = getClass().getClassLoader().getResourceAsStream("xml/my.xml");
String in = context.getTypeConverter().convertTo(String.class, inStream);
exchange.getIn().setBody(in);
}
});
}
#Test
public void synchronousCallBasic_1() throws Exception {
final MyConfiguration MyConfiguration = applicationContext.getBean(MyConfiguration.class);
final String myMessageBody =
context.getTypeConverter().convertTo(String.class, getClass().getClassLoader()
.getResourceAsStream("xml/0010_example.xml"));
final Object myResult = producerTemplate.requestBody(MyConfiguration.getActiveMqSynchronousEndpointUri(), myMessageBody);
assertThat(myResult, notNullValue());
assertThat((String)myResult, is("<example>1</example>"));
}
}
I solved this issue, with a lot of annotation which I found here, and now the test properties are correctly injected:
#RunWith(CamelSpringBootRunner.class)
#SpringBootTest
#ActiveProfiles("test")
#EnableAutoConfiguration
#ComponentScan
#ContextConfiguration()
public class MessageDeliveryTest{
}
Also, the test properties file needs to be named application-{env}.properties, where "env" is the profile used here. For eg. for test the properties file should be application-test.properties

Access spring component from servlet

I have a controller (for example. MyManager) where I invoke method (for example myMethod() ) of component class (bean), for example MyComponent. I have I servlet where I want to invoke myMethod() . In servlet I have annotated MyManager by #Autowired annotation , despite this I got NullPointerException. I saw this kind of topic but it is not useful for me. For imagination I write little code :
public class myClass extends HttpServlet {
#Autowired
private MyComponent component;
public void init(ServletConfig config) throws ServletException{
super.init(config);
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
}
protected void doGet(HttpServletRequest req,HttpServletResponse res) throws ... {
List<MyObject> objects =component.myMethod(); // Here is the problem, component is null
}
}
}
I make Spring configuration file "context.xml" and I got bean (component) object, but now I have problem with injected EntityManager in bean object. Now it is null , can anyone help me to solve this problem ? Also update init() method.
public void init(ServletConfig config) throws ServletException{
ApplicationContext con = new ClassPathXmlApplicationContext("context.xml");
component = (MyComponent) con.getBean("myBean");
}
You cannot autowire dependencies like that because Servlets are not Spring Beans. You need to do something like the following:
#Override
public void init() throws ServletException {
super.init();
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
component= applicationContext.getBean(MyComponent.class);
}
Also drop the #Autowired annotation from component

How can I get the bean definitions without actually creating the beans?

For static analysis of my Spring config, I need just the bean definitions - actually creating the beans would cause problems because some need a (properly initialized) database.
Is there a way to prevent the AnnotationConfigApplicationContext to create any beans? Instead, it should just load and analyze the config and stop.
you could implement the BeanDefinitionRegistryPostProcessor interface. in the postProcessBeanDefinitionRegistry method you have access to BeanDefinition's
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// ...
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.getBeanDefinition("myBean");
}
}

Resources