How do I register my custom Environment Post Processor in Spring Boot 2.0? - spring-boot

I followed the exact steps in this documentation.
I have the following entries in my META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=com.mygroup.myapp.CustomEnvironmentPostProcessor
My post processor:
public class CustomEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered {
..
}
I don't see anything in the logs as if it didn't get registered or not existing.
I unzipped the JAR and I can see META-INF/spring.factories. I can also see BOOT-INF/classes directly from the root.
What am I missing here?

There is no elegant way to solve this. You can make something like this :
#Component
public class CustomEnvironmentPostProcessor implements
EnvironmentPostProcessor, ApplicationListener<ApplicationEvent> {
private static final DeferredLog log = new DeferredLog();
#Override
public void postProcessEnvironment(
ConfigurableEnvironment env, SpringApplication app) {
log.error("This should be printed");
}
#Override
public void onApplicationEvent(ApplicationEvent event) {
log.replayTo(CustomEnvironmentPostProcessor.class);
}
}

define spring.factories file
Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
class name with package

As of Spring Boot 2.4, there are now optional constructor parameters that provide access to deferred logs:
public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final Log log;
public CustomEnvironmentPostProcessor(Log log) {
this.log = log;
}
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
log.info("CustomEnvironmentPostProcessor!!");
}
}
NOTE: The log messages are deferred so they will appear in the log output only after the logging system has been initialized.
See https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/env/EnvironmentPostProcessor.html

Related

spring-statemachine 2.3.1 - How to get a StateMachine from a StateMachineModelFactory?

I'm having the following business case while working with spring-statemachine 2.3.1 in a project:
The state machine is defined with the Papyrus plugin and loaded from an uml file using the UmlStateMachineModelFactory as shown below:
public class MyStateMachineConfig extends StateMachineConfigurerAdapter<String, String>
{
#Override
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception
{
model.withModel().factory(myStateMachineModelFactory());
}
#Bean
public StateMachineModelFactory<String, String> myStateMachineModelFactory()
{
return new UmlStateMachineModelFactory("classpath:my.uml");
}
....
I need to persist the state machine context in a database using JPA. In order to do this, I need to use StateMachinePersister.persist(). This method uses as its first input parameter a StateMachine instance. However, I'm not able to get the StateMachine instance from my StateMachineModelFactory. The class StateMachineFactory has a method named getStateMachine() while the class StateMachineModelFactory doesn't.
I didn't find neither a way to get a StateMachineFactory instance from a StateMachineModelFactory instance. Could anyone please help with some suggestions, ideally examples ? The documentation has different examples of how to do it but none for the case when the state machine is loaded from an UML file.
Kind regards,
Nicolas DUMINIL
Your configuration for StateMachineFactory should look like below
#Configuration
#EnableStateMachineFactory
public static class SsmConfig
extends EnumStateMachineConfigurerAdapter<States, Events> {
#Override
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
states
.withStates()
.initial(States.S1)
.end(States.SF)
.states(EnumSet.allOf(States.class));
}
}
And then you can simply inject StateMachineFactory everywhere and just get state machine.
class SomeService {
#Autowired
private StateMachineFactory<States, Events> factory;
void method() {
StateMachine<States,Events> stateMachine = factory.getStateMachine();
stateMachine.start();
}
}
More information can be found here.
Your configuration, which extends StateMachineConfigurerAdapter is an "adaptation" or modification of the auto-configured Spring state machine.
In order for your configuration to be picked-up by spring you need to annotate it with #Configuration.
You also have to enable the State Machine auto-configuration, which happens with #EnableStateMachie annotation.
#Configuration
#EnableStateMachine
public class MyStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
You can refer to the official documentation for more details.
Once this is active, you can inject the StateMachine as a dependency.

Alfresco Process Services with TaskListener #Autowired issue

I am using Alfresco Process Services and have created a created a spring boot project for custom logic like TaskListeners and Delegations. I am creating the jar file from this maven project and copying it into webapps/activiti-app/WEB-INF/lib folder.
I have a simple TaskListener as below which is getting called on Task start. But the #Autowired variables are always null.
package com.activiti.extension.bean;
#Component("myTaskListener")
public class MyTaskListener implements TaskListener {
#Autowired
UserService userService;
#Override
public void notify(DelegateTask task) {
logger.info("userService: " +userService); // Always prints null
}
Finally I was able to make it work. I was putting the task listener in the class field of the Task properties with full package name. Now I am putting Delegate expression like ${myTaskListener} and it worked...
Thank you all for your time and help
This is because your your MyTaskListener is annotated as #Component or at least being ignored by spring during init. for auto-wiring capabilities spring requires this annotation (or similar to this) under the provided #ComponentScan packages to consider the class as a bean otherwise it will take as a normal java class and hence the #autowired is of no use in your case.
This below code is worked for me
#Component
public class MyTaskListener implements TaskListener {
public static UserService getUserServiceObject() {
return SpringApplicationContextHolder.getApplicationContext().getBean(UserService.class);
}
#Override
public void notify(DelegateTask delegateTask) {
//UserService Object, It is not null now
getUserServiceObject();
}
}
#Component
public class SpringApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
There is also one more way to get to your custom service "UserService" using Alfresco Spring Application context.
First access ServiceRegistry (registry used for accessing Alfresco Services but also any other custom service):
ServiceRegistry serviceRegistry = (ServiceRegistry) Context.getProcessEngineConfiguration().getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
Then get custom service UserService:
QName qname = QName.createQName("UserService");
UserService userService = (UserService) serviceRegistry.getService(qname);

How to get a #Configuration object in ServletContextListener?

I am having here a ApplicationConfiguration class:
#Configuration
#PropertySource("classpath:application.properties")
public class ApplicationConfiguration implements DatabaseConfiguration {
#Value("${jdbc.driver}")
private String jdbcDriver;
// ..
#Override
public String getJdbcDriver() {
return this.jdbcDriver;
}
// ..
}
which provides certain kind of information about my application e.g. which JDBC driver I want to use. However, I don't know how I can use it as I am bootstrapping my server. How can I get hands on the initialized ApplicationConfiguration object and what is the best place to do this with Spring.
My current ansatz is using a ServletContextListener but at this point I don't know how to access the configuration object either:
public class BootstrappingServerConfig implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent event) {
// ..
// How can I load my application configuration at this point
// or is there a better place to do that actually?
ApplicationConfiguration applicationConfiguration;
try {
SqlDatabaseBootstrapper.executeMigration(dataSource, applicationConfiguration);
} catch(Exception e) {
throw e;
}
}
// ..
}
What can I do here? Is it a good idea to use a #Configuration object like that in the ServletContextListener or would I do this kind of initialization somewhere else? If it's okay what I plan to do .. how can I get the configuration object?

AOP using Spring Boot

I am using this Spring AOP code in my Spring Boot starter project in STS. After debugging this for some time I don't see any problem with the AspectJ syntax. The Maven dependencies are generated by STS for a AOP starter project. Is there a glaring omission in this code like an annotation ? The other problem could be with the AOP starter project or with the way I try to test the code in a #PostConstruct method.
I installed AJDT but it appears STS should show AspectJ markers in the IDE on its own. Right ? I don't see the markers. What other AspectJ debugging options are included in STS ? -Xlint is what I used in Eclipse/AJDT.
StateHandler.java
public class StateHandler<EVENTTYPE extends EventType> {
private State<EVENTTYPE> state;
private Event<EVENTTYPE> event;
public StateHandler(State<EVENTTYPE> state, Event<EVENTTYPE> event) {
this.state = state;
this.event = event;
}
public void handle( Event<EVENTTYPE> event ){
state = state.handle( event );
}
public State<EVENTTYPE> getState() {
return state;
}
}
DeviceLogger .java
#Aspect
#Component
public class DeviceLogger {
private static Logger logger = Logger.getLogger("Device");
#Around("execution(* com.devicemachine.StateHandler.*(..))")
public void log() {
logger.info( "Logger" );
}
}
LoggerApplication.java
#SpringBootApplication
public class LoggerApplication {
private static Logger logger = Logger.getLogger("Device");
public static void main(String[] args) {
SpringApplication.run(LoggerApplication.class, args);
}
#PostConstruct
public void log(){
DeviceState s = DeviceState.BLOCKED;
StateHandler<DeviceEvent> sh = new StateHandler<DeviceEvent>( s,
Event.block(DeviceEvent.BLOCKED, "AuditMessage") );
sh.handle(Event.block(DeviceEvent.UNBLOCKED, "AuditMessage"));
}
}
There are 3 obvious things wrong and 1 not so obvious wrong.
Your aspect is wrong and breaks proper method execution. When using an around aspect you must always return Object and use a ProceedingJoinPoint and call proceed() on that.
You are creating new instances of classes yourself, Spring, by default, uses proxy based AOP and will only proxy beans it knows.
In a #PostConstruct method it might be that proxies aren't created yet and that nothing is being intercepted
You need to use class based proxies for that to be enabled add spring.aop.proxy-target-class=true to your application.properties. By default JDK Dynamic Proxies are used which are interface based.
Fix Aspect
Your current aspect doesn't use a ProceedingJoinPoint and as such never does the actual method call. Next to that if you now would have a method that returns a value it would all of a sudden return null. As you aren't calling proceed on the ProceedingJoinPoint.
#Around("execution(* com.devicemachine.StateHandler.*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
logger.info( "Logger" );
return pjp.proceed();
}
Create a bean to fix proxying and #PostConstruct
#SpringBootApplication
public class LoggerApplication {
private static Logger logger = Logger.getLogger("Device");
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(LoggerApplication.class, args);
StateHandler<DeviceEvent> sh = context.getBean(StateHandler<DeviceEvent>.class);
sh.handle(Event.block(DeviceEvent.UNBLOCKED, "AuditMessage"));
}
#Bean
public StateHandler<DeviceEvent> auditMessageStateHandler() {
return new StateHandler<DeviceEvent>(DeviceState.BLOCKED, Event.block(DeviceEvent.BLOCKED, "AuditMessage") );
}
}
Add property to enable class proxies
In your application.properties in src\main\resources add the following property with a value of true
spring.aop.proxy-target-class=true

How to inject java.nio.file.Path dependency using #ConfigurationProperties

I'm using Spring Boot and have the following Component class:
#Component
#ConfigurationProperties(prefix="file")
public class FileManager {
private Path localDirectory;
public void setLocalDirectory(File localDirectory) {
this.localDirectory = localDirectory.toPath();
}
...
}
And the following yaml properties file:
file:
localDirectory: /var/data/test
I would like to remove the reference of java.io.File (of setLocalDirectory) by replacing with java.nio.file.Path. However, I receive a binding error when I do this. Is there way to bind the property to a Path (e.g. by using annotations)?
To add to jst's answer, the Spring Boot annotation #ConfigurationPropertiesBinding can be used for Spring Boot to recognize the converter for property binding, as mentioned in the documentation under Properties Conversion:
#Component
#ConfigurationPropertiesBinding
public class StringToPathConverter implements Converter<String, Path> {
#Override
public Path convert(String pathAsString) {
return Paths.get(pathAsString);
}
}
I don't know if there is a way with annotations, but you could add a Converter to your app. Marking it as a #Component with #ComponentScan enabled works, but you may have to play around with getting it properly registered with the ConversionService otherwise.
#Component
public class PathConverter implements Converter<String,Path>{
#Override
public Path convert(String path) {
return Paths.get(path);
}
When Spring sees you want a Path but it has a String (from your application.properties), it will lookup in its registry and find it knows how to do it.
I took up james idea and defined the converter within the spring boot configuration:
#SpringBootConfiguration
public class Configuration {
public class PathConverter implements Converter<String, Path> {
#Override
public Path convert(String path) {
return Paths.get(path);
}
}
#Bean
#ConfigurationPropertiesBinding
public PathConverter getStringToPathConverter() {
return new PathConverter();
}
}

Resources