Transient fields in Spring session-scoped controller - spring

I have a Spring controller bean set to a session scope, it looks somewhat like this:
#Controller
#Scope(WebApplicationContext.SCOPE_SESSION)
#RequestMapping("/test")
public class SessionTestController implements Serializable {
private static final long serialVersionUID = -7735095657091576437L;
private transient Log log;
#PostConstruct
public void initialise() {
log = LogFactory.getLog(getClass());
}
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public String doGet() throws InterruptedException {
log.warn("This line will fail after deserialisation..."); // Causes NPE
}
}
It seems Spring does not call #PostConstruct after de-serialisation, this causes my "log" field to become null and throw a NullPointerException in my doGet() method.
How do you usually deal with non-serialisable fields like the logger in session-scoped beans? Do I need to implement some session aware interface to make this work?

I think a typical way is to use readObject() to initialize transient fields, no matter whether it's a Spring bean or not:
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
initializeTransientFields();
}

Related

Is it possible to read property file values inside #Repository?

Is it possible to read property file values inside #Repository.
Any help is appreciated.
As M.Deinum mentioned in the comment section, #Repository is just a variation of #Component. You can read your property file by injecting Environment or you can just use #Value.
If you use a class repository, you can use the #Value annotation on any non-final field to have it populated automatically.
On the other hand, if you are using an interface repository, you cannot read a property directly, as you have nowhere to inject your components/values.
Anyway, I found a sort of work-around which can be used to achieve a similar result.
You start by creating a class that implements ApplicationContextProvider:
#Component
public class StaticPropertiesProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
#Override
public void setApplicationContext(#Nonnull final ApplicationContext applicationContext) throws BeansException {
StaticPropertiesProvider.applicationContext = applicationContext;
}
public static <T> T getProperty(final String property, final Class<T> clazz) {
return applicationContext.getEnvironment().getProperty(property, clazz);
}
}
When your application starts, the static applicationContext field will be populated with your Application Context, so that you can use it afterwards from the static getProperties method.
You can then call it from a default method inside your repository, e.g.
public interface MyRepository extends MongoRepository<MyDocument, String> {
#Query(value = "{'myProperty': ?0}")
List<MyDocument> findByMyProperty(int myProperty);
default List<MyDocument> findByMyProperty() {
final var myPropertyDefault = StaticPropertiesProvider.getProperty("my.property", Integer.class);
return this.findByMyProperty(myProperty);
}
}
Clearly there should be a property name my.property with an int value inside application.properties.
(you can do the same even with JPA repositories)

Spring Data Rest: #Autowire in Custom JsonDeserializer

I am trying to autowire a component into a custom JsonDeserializer but cannot get it right even with the following suggestions I found:
Autowiring in JsonDeserializer: SpringBeanAutowiringSupport vs HandlerInstantiator
Right way to write JSON deserializer in Spring or extend it
How to customise the Jackson JSON mapper implicitly used by Spring Boot?
Spring Boot Autowiring of JsonDeserializer in Integration test
My final goal is to accept URLs to resources in different microservices and store only the ID of the resource locally. But I don't want to just extract the ID from the URL but also verify that the rest of the URL is correct.
I have tried many things and lost track a bit of what I tried but I believe I tried everything mentioned in the links above. I created tons of beans for SpringHandlerInstantiator, Jackson2ObjectMapperBuilder, MappingJackson2HttpMessageConverter, RestTemplate and others and also tried with setting the SpringHandlerInstantiator in RepositoryRestConfigurer#configureJacksonObjectMapper.
I am using Spring Boot 2.1.6.RELEASE which makes me think something might have changed since some of the linked threads are quite old.
Here's my last attempt:
#Configuration
public class JacksonConfig {
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
}
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private Validator validator;
#Autowired
private HandlerInstantiator handlerInstantiator;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.setHandlerInstantiator(handlerInstantiator);
}
}
#Component
public class RestResourceURLSerializer extends JsonDeserializer<Long> {
#Autowired
private MyConfig config;
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ServiceConfig serviceConfig = config.getServices().get("identity");
URI serviceUri = serviceConfig.getExternalUrl();
String servicePath = serviceUri.getPath();
URL givenUrl = p.readValueAs(URL.class);
String givenPath = givenUrl.getPath();
if (servicePath.equals(givenPath)) {
return Long.parseLong(givenPath.substring(givenPath.lastIndexOf('/') + 1));
}
return null;
}
}
I keep getting a NullPointerException POSTing something to the API endpoint that is deserialized with the JsonDeserializer above.
I was able to solve a similar problem by marking my deserializer constructor accept a parameter (and therefore removing the empty constructor) and marking constructor as #Autowired.
public class MyDeserializer extends JsonDeserializer<MyEntity> {
private final MyBean bean;
// no default constructor
#Autowired
public MyDeserializer(MyBean bean){
this.bean = bean
}
...
}
#JsonDeserialize(using = MyDeserializer.class)
public class MyEntity{...}
My entity is marked with annotation #JsonDeserialize so I don't have to explicitly register it with ObjectMapper.

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);

spring inject logback TurboFilter

I use spring to inject DemoService has always been null, there is no problem with the filter inject of servlet, in the class of extends TurboFilter, how can I get the DemoService object?
https://stackoverflow.com/questions/30662641/inject-spring-bean-into-custom-logback-filter
I have tried the answer to this connection and did not solve the problem of inject.
public class ErrorLogTurboFilter extends TurboFilter {
#Autowired
private DemoService demoService;
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
// todo
return FilterReply.NEUTRAL;
}
}
Problem: Logback starts up before the Spring context. Therefore you need to lazy initialize the Filter with the to be injected bean. Apart from that the Filter will not be called as a Spring bean, but as a Turbofilter, that does not know any injections and so on.
What you could try is define that Filter as a Spring bean in your context, that contains the DemoService. Inject the bean via a Setter for the service, but declare the field static, so you are able to access it from the logging context.
Now during the execution you need to check if the static field is already initialized, if so you can use it without a problem.
You are not trying the answer you are quoting, because your extended filter "ErrorLogTurboFilter" does not have a "#Named("errorLogTurboFilter")" which is the standard annotation to make your filter a spring bean.
see : What is javax.inject.Named annotation supposed to be used for?
#markusw According to your prompt, this is my solution,and thank you.
#Configuration
public class WebConfig {
#Bean
public DemoService demoService() {
return new DemoService();
}
}
public class ErrorLogTurboFilter extends TurboFilter {
private ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
private DemoService demoService = ctx.getBean(DemoService.class);
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
// todo
return FilterReply.NEUTRAL;
}
}

Spring Aspectj instantiation models in webapp

I've a Spring web application and I'm trying to add some aspect to my service objects. The goal is to maintain aspect's state only through a single request scope, and to obtain a reference to aspect instance so I can manage state. I've tried 3 different versions of code, making requests via controller:
Same as related code (see below). State is retained through multiple invocations, but the #Autowired TestAspect aspect instance is not the same use by AOP framework.
Adding factory-method="aspectOf" to testAspect bean declaration in beans-context.xml, state is retained as previous case, and the #Autowired TestAspect aspect instance is the same used by AOP framework. Although it works, I want aspect's scope to be a single request, while in this case I have an application-scoped singleton.
Substituting #Aspect with #Aspect("perthis(participateAroundPointcut())") I get the following exception: Caused by: java.lang.IllegalArgumentException: Bean with name 'testAspect' is a singleton, but aspect instantiation model is not singleton, both with factory-method="aspectOf" or without.
How can I get #Autowired TestAspect aspect reference to be the same aspect instance used by AOP framework? Is factory-method="aspectOf" the only one way?
And how can i have a request-scoped aspect instead of a singleton? Why am I getting the exception?
Here is my code.
Service:
#Service
public class TestService {
#Autowired
private TestAspect aspect;
private static final Logger logger = LoggerFactory.getLogger(TestService.class);
public void method(){
logger.debug("Executing method");
}
public void service(){
aspect.initialize();
method();
}
}
Aspect: (without ("perthis(participateAroundPointcut())"))
#Aspect
public class TestAspect {
private static final Logger logger = LoggerFactory.getLogger(ParticipatoryAspect.class);
private boolean initialized=false;
#Pointcut("execution(* org.mose.emergencyalert.TestService.method(..))")
public void participateAroundPointcut(){}
#Around("participateAroundPointcut()")
public void participateAround(ProceedingJoinPoint joinPoint) throws Throwable{
logger.debug("Pre-execution; Initialized: "+initialized);
joinPoint.proceed();
logger.debug("Post-execution");
}
public void initialize(){
this.initialized=true;
logger.debug("Initialized: "+initialized);
}
}
beans-context.xml (without factory-method="aspectOf"):
<aop:aspectj-autoproxy />
<bean id="testAspect" class="org.mose.emergencyalert.aop.aspects.TestAspect"/>

Resources