Spring Cloud Function (GCP adapter) throws Hibernate lazy could not initialize proxy - no session - spring-boot

This is a common error in Spring when tries to transform automatically an entity object whit some hibernate proxys but i dont't know how to load Jackson DataType Hibernate5 module in Spring cloud functions gcp adapter.
#SpringBootApplication
#Log4j2
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
log.info("configurando cors");
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
#Bean
public Module datatypeHibernateModule() {
log.info("Cargando modulo hibernate jackson");
return new Hibernate5Module();
}
}
If i use the same code whit normal Spring boot project the module works but in this case i found on the log the adapter don't used Jackson and they implements Gson.
at com.google.gson.Gson.toJson(Gson.java:638)
at com.google.gson.Gson.toJson(Gson.java:618)
at org.springframework.cloud.function.json.GsonMapper.toJson(GsonMapper.java:70)
This is the entire log file
My first workaround is change the Page object for String and use manually jackson mapper.
public class ObtenerEstados implements Function<Void, String> {
#Autowired
private EstadoService estadoService;
#SneakyThrows
#Override
public String apply(Void unused) {
Page<Estado> page = estadoService.buscarTodos(0, 33);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Hibernate5Module());
String objectAsString = objectMapper.writeValueAsString(page);
return objectAsString;
}
}
I created two branches on the Github repository
functions (this branch have the bug)
function-jackson-hibernate5 (this branch have the workaround)
If you haved already installed Docker and Docker Compose you can reproduce the bug easy.
Follow the next steps:
git clone https://github.com/ripper2hl/sepomex.git
cd sepomex
git checkout -b dev origin/functions
docker-compose pull db
docker-compose up -d db
export spring_profiles_active=local
mvn -Pgcp function:run
And execute with a curl or any REST client
curl http://localhost:8080/
I know the alternative for use a DTO object but i prefer not use this option

So whenever Gson is on the classpath it is given priority and of course with Google that is the case. Please set spring.http.converters.preferred-json-mapper=jackson property to force Jackson.

Finally i fixed with this fragment of code
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public JsonMessageConverter jsonMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Hibernate5Module());
JacksonMapper jacksonMapper = new JacksonMapper(objectMapper);
return new JsonMessageConverter(jacksonMapper);
}
}
The documentation explain Gson is the default MessageConverter but it's not be clear how to change(more easy) gson to jackson.
https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_provided_messageconverters

Related

Is there a possibility to group properties using Spring Boot?

Can something like this be accomplished using Spring Boot?
The idea is to group properties and assign the same value to all of them, so instead of all of the properties ending with 'test*' i would like to change just one property 'my.flag'. I know that such functionality works in case of loggers, but can I define my own group?
I am not sure whether your problem has been solved or not, but I want to provide a solution to achieve what you want by using spring.factories and implementing ApplicationListener as following steps.
STEP 1
Create a class MyPropertiesListener which implements ApplicationListener and read the value of my.flag in application.properties first, then set it to all the properties whose key starts with my.flag..
public class MyPropertiesListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
String myFlag = env.getProperty("my.flag");
Properties props = new Properties();
MutablePropertySources propSrcs = env.getPropertySources();
StreamSupport.stream(propSrcs.spliterator(), false)
.filter(ps -> ps instanceof EnumerablePropertySource)
.map(ps -> ((MapPropertySource) ps).getPropertyNames())
.flatMap(Arrays::<String>stream)
.forEach(propName -> {
if (propName.toString().startsWith("my.flag.")) {
props.put(propName, myFlag);
}
});
env.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
}
}
STEP 2
Create a file named spring.factories under src/main/resource/META-INF and configure MyPropertiesListener into it.
org.springframework.context.ApplicationListener=xxx.xxx.xxx.MyPropertiesListener
TEST
The value of my.flag.test3 is false in application.properties originally, but it is going to be overwritten as true while application starts.
#SpringBootApplication
public class Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Value("${my.flag.test3}")
private String test3;
#Override
public void run(String... args) throws Exception {
System.out.println(test3); //true
}
}
See also
Creating a Custom Starter with Spring Boot

Upgrade from Spring Boot 1.3 to Spring Boot 1.4 and Pageable is not working as expected.

I am converting an existing Spring Boot application from 1.3.6 to 1.4.1. I would like to have a default page size for repository and controller responses of 25. I am not getting the expected behavior in either case. For repository methods I am getting a page size of 20. For controllers I am getting 0 for the page size.
I added a new configuration class to define the default page size. I found this code snippet in another article. The debug message does get printed out.
#Configuration
public class RestConfigurationAdapter extends WebMvcConfigurerAdapter {
private static final int DEFAULT_PAGE_SIZE = 25;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
System.out.println("DEBUG: AddArguments----");
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setFallbackPageable(new PageRequest(0, DEFAULT_PAGE_SIZE));
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
}
In a custom controller I would like to have a default pageable populated with a size of 25. However the pageable object is null in this controller. In 1.3.x the pageable object worked as expected.
public class BatchManagerController
{
#Autowired
private BatchRepository batchRepository;
#Autowired
private PagedResourcesAssembler pagedResourcesAssembler;
#Transactional(readOnly = true)
#RequestMapping(value = "/search/managerBatchView", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#PreAuthorize("hasRole(T(com.nextgearcapital.tms.api.util.AuthorityEnum).MANAGER)")
public ResponseEntity<?> getManagerBatchListView(BatchListSearchRequest requestDTO, Pageable pageable, PersistentEntityResourceAssembler myAssembler)
{
System.out.println("DEBUG1:---------- " + pageable);
Page<Batch> batchPage = batchRepository.findBatchesForManager(requestDTO, pageable);
PagedResources<VaultResource> pagedResources = pagedResourcesAssembler.toResource(batchPage, myAssembler);
return new ResponseEntity<>(pagedResources, HttpStatus.OK);
}
}
When calling SDR Repository methods with a pageable parameter, the parameter works correctly, but it has a default page size of 20, rather than 25.
I would appreciate any help and advise in getting the correct configuration for pagination.
You probably have 2 solutions
Register the PageableHandlerMethodArgumentResolver as an #Bean which will disable the auto configuration for Spring Data Web.
Create a BeanPostProcessor to do additional configuration on the existing PageableHandlerMethodArgumentResolver.
Using #Bean
#Configuration
public class RestConfigurationAdapter extends WebMvcConfigurerAdapter {
private static final int DEFAULT_PAGE_SIZE = 25;
#Bean
public PageableHandlerMethodArgumentResolver pageableResolver() {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setFallbackPageable(new PageRequest(0, DEFAULT_PAGE_SIZE));
return resolver;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
System.out.println("DEBUG: AddArguments----");
argumentResolvers.add(pageableResolver());
}
}
Drawback is that it will disable the autoconfiguration for Spring Data Web, so you might miss some things.
Using a BeanPostProcessor.
#Bean
public BeanPostProcessor pageableProcessor() {
private static final int DEFAULT_PAGE_SIZE = 25;
return new BeanPostProcessor() {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof PageableHandlerMethodArgumentResolver) {
((PageableHandlerMethodArgumentResolver) bean).setFallbackPageable(new PageRequest(0, DEFAULT_PAGE_SIZE));
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
}
Drawback is that it is a little more complex as registering your own PageableHandlerMethodArgumentResolver instance as a bean. Advantage however is that you can simply use this to add additional configuration to existing beans and leave the auto configuration in tact.
Starting in spring-data-commons version 2.0, there is are 2 new classes that might make this kind of thing easier:
SortHandlerMethodArgumentResolverCustomizer
PageableHandlerMethodArgumentResolverCustomizer
Unfortunately that's not the version that ships with the current version (1.5.9) of Spring Boot, so replace at your own risk.
#Bean
PageableHandlerMethodArgumentResolverCustomizer pagingCustomizer() {
// p is PageableHandlerMethodArgumentResolver
return p -> p.setMaxPageSize(25);
}
In this case, one would probably call resolveArgument to manipulate it.
That said, I'm not sure spring-data-rest would use that config. There is a HateoasPageableHandlerMethodArgumentResolver which seems more likely that source of what I would think SDR would use. If that's the case, the BeanPostProcessor #M. Deinum suggested is probably your best option.
Spring Data Web Support

What's the best way to add a new property source in Spring?

I'd like to add a new property source that could be used to read property values in an application. I'd like to do this using Spring. I have a piece of code like this in a #Configuration class:
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources sources = new MutablePropertySources();
MyCustomPropertySource propertySource = new MyCustomPropertySource("my custom property source");
sources.addFirst(propertySource);
properties.setPropertySources(sources);
return properties;
}
This seems to work pretty well. However, what it is also doing is overriding other property values (e.g. server.port property in application.properties file used by spring boot) which I don't want to overwrite. So the basic question is what's the best way to add this propertysource but not have it override other properties. Any way to grab the existing propertysources and simply add on to it?
I got this working by adding a custom initiailizer to my spring boot app:
#SpringBootApplication
public class MyApp {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApp.class)
.initializers(new MyContextInitializer()) // <---- here
.run(args);
}
}
Where MyContextInitializer contains: -
public class MyContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
// Create map for properites and add first (important)
Map<String, Object> myProperties = new HashMap<>();
myProperties.put("some-prop", "custom-value");
environment.getPropertySources().addFirst(
new MapPropertySource("my-props", myProperties));
}
}
Note, if your application.yaml contains: -
some-prop: some-value
another-prop: this is ${some-prop} property
Then the initialize method will update the some-prop to custom-value and when the app loads it will have the following values at run-time:
some-prop: custom-value
another-prop: this is custom-value property
Note, if the initialize method did a simple System.setProperty call i.e.
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
System.setProperty("some-prop", "custom-value");
}
... then the another-prop would be equal to this is some-value property which is not what we generally want (and we lose the power of Spring config property resolution).
Try setting IgnoreUnresolvablePlaceholders to TRUE. I had a similar problem which I was able to resolve in this way. In my case, I had another placeholderconfigurer, which was working - but properties in the second one were not being resolved unless I set this property to TRUE.
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
return propertySourcesPlaceholderConfigurer;
}
Yet another possibility (after lots of experimentation, it's what worked for me) would be to declare your PropertySource inside a ApplicationContextInitializer and then inject that one in your SpringBootServletInitializer:
public class MyPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertyInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
MyPropertySource ps = new MyPropertySource();
applicationContext.getEnvironment().getPropertySources().addFirst(ps);
}
}
public class MyInitializer extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return super.configure(builder.initializers(new MyPropertyInitializer()));
}
}
You can perhaps add your propertySource straight into environment once it is initialized.
EDIT: As this is done AFTER the class is processed, you cannot expect the #Value annotations to pick up anything from this particular PropertySource in the same #Configuration class - or any other that is loaded before.
#Configuration
public class YourPropertyConfigClass{
#Value("${fromCustomSource}")
String prop; // failing - property source not yet existing
#Autowired
ConfigurableEnvironment env;
#PostConstruct
public void init() throws Exception {
env.getPropertySources().addFirst(
new MyCustomPropertySource("my custom property source"));
}
}
#Configuration
#DependsOn("YourPropertyConfigClass")
public class PropertyUser {
#Value("${fromCustomSource}")
String prop; // not failing
}
You could move the #PostConstruct to a separate #Configuration class and mark other classes using those properties #DependOn("YourPropertyConfigClass") (this works - but perhaps there is a better way to force configuration order?).
Anyway - it is only worth it, if MyCustomPropertySource cannot be simply added using #PropertySource("file.properties") annotation - which would solve the problem for simple property files.
If you implement PropertySourceFactory as such:
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
public class CustomPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) {
...
}
}
You can use the following property source:
#PropertySource(name="custom-prop-source", value="", factory=CustomPropertySourceFactory.class)
Kind of hack-ish, but it works.

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 can i externalize datasource configuration with spring-boot?

I'm currently trying to move an existing spring-application to spring-boot and therefore recreate things that worked without boot.
I want to configure some properties (like spring.datasource.*) from an external source. specificly a folder with several properties files.
I set up a configuration class that creates propertyPlaceholder configurers like this:
#Configuration
public class PropertySourceConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer defaultsPlaceHolderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/*-defaults.properties"));
propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertyConfigurer;
}
#Bean
public static PropertySourcesPlaceholderConfigurer externalPlaceHolderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyConfigurer.setLocations(new
PathMatchingResourcePatternResolver().getResources("file:/my-config-path/*.properties"));
propertyConfigurer.setOrder(1);
propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertyConfigurer;
}
This seems to work for most things (like amqp or my own config properties) but when i try to use spring-data-jpa they are ignored. basicly setting spring.datasource.url (and other things used for auto-config) in those files has no effect.
looking through the logs of the PropertySourcesPropertyResolver i figured out that these configurer fall under the localProperties group which is not used when looking for spring.datasource.*.
is there a way to fix this or a better way to add external properties files to my context?
I know i could set spring.config.location to do something similar but i can not pass command-line properties to my application and need to do this config from within my application. afaik this is not possible with this property.
EDIT: setting spring.config.location:
Attempt 1:
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CampaignServiceStarter.class);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.setInitParameter("spring.config.location", "file:/my-config-path/*.properties");
}
}
Attempt 2:
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CampaignServiceStarter.class).properties("spring.config.location=file:/my-config-path/*.properties");
}
}
in both cases the external properties were not picked up at all (even in places where it worked before, like the amqp config)
How to use external configuration is explained in this section of the Spring Boot Reference Guide.
The spring.config.location is a path to the directory which contains your application.properties file. It takes a comma separated list of values so you could specify multiple paths. It doesn't take the wildcard. It is a path so not an expression to match multiple property files. If you want to change the default application then use the spring.config.name to make it something else.
The defaults of Spring Boot are opinionated as the rest of Spring Boot (with the default configuration etc.).
If you want to do more extensive (pre) configuration you should use an ApplicationContextInitializer to manually add the PropertySources to the Environment. This is mentioned here in the Spring Boot Reference Guide.
An example of how the initializer might look.
public class ConfigurationInitializer implements ApplicationContextInitializer {
private static final String DEFAULT_PROPS = "classpath*:/*-defaults.properties";
private static final String EXTERNAL_PROPS = "file:/my-config-path/*.properties";
public void initialize(ConfigurableApplicationContext applicationContext) {
final Resource[] defaultConfigs = applicationContext.getResources(DEFAULT_PROPS);
final Resource[] externalConfigs = applicationContext.getResources(EXTERNAL_PROPS);
final ConfigurableEnvironment env = applicationContext.getEnvironment();
final MutablePropertySources mps = env.getPropertySources();
for (Resource r : externalConfigs) {
mps.addLast(new ResourcePropertySource(r.getFilename(), r);
}
for (Resource r : defaultConfigs) {
mps.addLast(new ResourcePropertySource(r.getFilename(), r);
}
}
}
Then when building your application object add it as follows.
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CampaignServiceStarter.class)
.initializers(new ConfigurationInitializer());
}
}
Now the configs should be added to the list of property sources.

Resources