initParams are not working in WebFilter annotation - spring-boot

I created a filter using following annotations in a java web project created by spring-boot:
#Order(2)
#Component
#WebFilter(
filterName = "jwtFitler",
urlPatterns = "/*",
initParams = { #WebInitParam(name = "excludedPaths", value = "login, hello") }
)
However, the initParams are not working, excludedPaths and excludedUrls are always null. Could anyone help tell me why?
public class MyFilter implements Filter {
private String[] excludedUrls;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
String excludedPaths = filterConfig.getInitParameter("excludedPaths");
System.out.println("excludedPaths:" + excludedPaths);
if(!StringUtils.isEmpty(excludedPaths))
excludedUrls = excludedPaths.split(",");
System.out.println("excludedUrls:" + excludedUrls);
}
//......
}

Due to your use of #Component, your Filter is being found as a plain Spring component. As a result, the #WebFilter configuration has no effect.
If you want Spring Boot to scan for Servlet components (#WebFilter, #WebListener and #WebServlet) you need to use #ServletComponentScan. Typically, that annotation is added to your main application class alongside #SpringBootApplication. With that in place, you should remove #Component from your Filter.

Related

Grpc Spring Boot Starter

I am trying to integrate a grpcService to my spring boot application. In this service class i have a jpaRepository which is #Autowired. When i run the server this repository is not injected( is null when i'm trying to use it).
#Service
public class MedicationPlanService extends medicationPlanGrpc.medicationPlanImplBase {
#Autowired
private MedicationPlanRepository medicationPlanRepository;
#Override
public void hello(MedicationPlan.HelloRequest request, StreamObserver<MedicationPlan.HelloResponse> responseObserver) {
List<MedicationPlan> medicationPlans = medicationPlanRepository.findAll();
MedicationPlan.HelloResponse.Builder response = MedicationPlan.HelloResponse.newBuilder();
response.setResponseMessage("hello");
responseObserver.onNext(response.build());
responseObserver.onCompleted();
}
}
#Component
public class GrpcServerStartConfiguration {
#PostConstruct
public void startGrpcServer() throws IOException, InterruptedException {
Server server = ServerBuilder.forPort(9091).addService(new MedicationPlanService()).build();
server.start();
System.out.println("gRPC server started");
server.awaitTermination();
}
}
When i try to use the medicationPlanRepository i realized that it is null.
Thanks in advance :).
Because you're creating MedicationPlanService with "new", it becomes a simple object, not a bean. And things such as DI don't work in this way.
Initialize these 2 classes correctly (via #ComponentScan or #Bean in #Configuration class). Then inject MedicationPlanService into GrpcServerStartConfiguration. The latter you can mark as #Configuration for better understaning btw.

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.

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

Multiple servlet mappings in Spring Boot

Is there any way to set via property 'context-path' many mappings for a same Spring Boot MVC application? My goal is to avoid creating many 'Dispatcherservlet' for the uri mapping.
For example:
servlet.context-path =/, /context1, context2
You can create #Bean annotated method which returns ServletRegistrationBean , and add multiple mappings there. This is more preferable way, as Spring Boot encourage Java configuration rather than config files:
#Bean
public ServletRegistrationBean myServletRegistration()
{
String urlMapping1 = "/mySuperApp/service1/*";
String urlMapping2 = "/mySuperApp/service2/*";
ServletRegistrationBean registration = new ServletRegistrationBean(new MyBeautifulServlet(), urlMapping1, urlMapping2);
//registration.set... other properties may be here
return registration;
}
On application startup you'll be able to see in logs:
INFO | localhost | org.springframework.boot.web.servlet.ServletRegistrationBean | Mapping servlet: 'MyBeautifulServlet' to [/mySuperApp/service1/*, /mySuperApp/service2/*]
You only need a single Dispatcherservlet with a root context path set to what you want (could be / or mySuperApp).
By declaring multiple #RequestMaping, you will be able to serve different URI with the same DispatcherServlet.
Here is an example. Setting the DispatcherServlet to /mySuperApp with #RequestMapping("/service1") and #RequestMapping("/service2") would exposed the following endpoints :
/mySuperApp/service1
/mySuperApp/service2
Having multiple context for a single servlet is not part of the Servlet specification. A single servlet cannot serve from multiple context.
What you can do is map multiple values to your requesting mappings.
#RequestMapping({"/context1/service1}", {"/context2/service1}")
I don't see any other way around it.
You can use 'server.contextPath' property placeholder to set context path for the entire spring boot application. (e.g. server.contextPath=/live/path1)
Also, you can set class level context path that will be applied to all the methods e.g.:
#RestController
#RequestMapping(value = "/testResource", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestResource{
#RequestMapping(method = RequestMethod.POST, value="/test", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TestDto> save(#RequestBody TestDto testDto) {
...
With this structure, you can use /live/path1/testResource/test to execute save method.
None of the answers to this sort of question seem to mention that you'd normally solve this problem by configuring a reverse proxy in front of the application (eg nginx/apache httpd) to rewrite the request.
However if you must do it in the application then this method works (with Spring Boot 2.6.2 at least) : https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot.
It describes creating a filter, putting it early in the filter chain and basically re-writing the URL (like a reverse proxy might) so that requests all go to the same place (ie the actual servlet.context-path).
I've found an alternative to using a filter described in https://www.broadleafcommerce.com/blog/configuring-a-dynamic-context-path-in-spring-boot that requires less code.
This uses RewriteValve (https://tomcat.apache.org/tomcat-9.0-doc/rewrite.html) to rewrite urls outside of the context path e.g. if the real context path is "context1" then it will map /context2/* to /context1/*
#Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final List<String> LEGACY_PATHS = List.of("context2", "context3");
#Override
public void customize(TomcatServletWebServerFactory factory) {
RewriteValve rewrite = new RewriteValve() {
#Override
protected void initInternal() throws LifecycleException {
super.initInternal();
try {
String config = LEGACY_PATHS.stream() //
.map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1", p, factory.getContextPath())) //
.collect(Collectors.joining("\n"));
setConfiguration(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
factory.addEngineValves(rewrite);
}
}
If you need to use HTTP redirects instead then there is a little bit more required (to avoid a NullPointerException in sendRedirect):
#Component
public class LegacyUrlWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
private static final List<String> LEGACY_PATHS = List.of("context2", "context3");
#Override
public void customize(TomcatServletWebServerFactory factory) {
RewriteValve rewrite = new RewriteValve() {
#Override
protected void initInternal() throws LifecycleException {
super.initInternal();
try {
String config = LEGACY_PATHS.stream() //
.map(p -> String.format("RewriteRule ^/%s(/.*)$ %s$1 R=permanent", p, factory.getContextPath())) //
.collect(Collectors.joining("\n"));
setConfiguration(config);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
if (request.getContext() == null) {
String[] s = request.getRequestURI().split("/");
if (s.length > 1 && LEGACY_PATHS.contains(s[1])) {
request.getMappingData().context = new FailedContext();
}
}
super.invoke(request, response);
}
};
factory.addEngineValves(rewrite);
}
}
I use this approach:
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
#Configuration
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
rootContext.setServletContext(servletContext);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/mapping1/*");
dispatcher.addMapping("/mapping2/*");
servletContext.addListener(new ContextLoaderListener(rootContext));
}
}

Why Spring's #Component does not work on a JSP Custom Tag class?

I have a custom tag:
#Component
public class CVTag extends SimpleTagSupport {
#Inject
private JaxbSupport jaxbSupport;
#Override
public void doTag() throws JspException, IOException {
JspWriter writer = getJspContext().getOut();
Groups groups = jaxbSupport.getJaxbGroups();
}
NullPointerException is thrown as jaxbSupport is null.
Is there really a limitation stipulating Custom-Tag cannot be a Spring managed Bean? or I am doing something wrong?
Using Spring 3.2.4.
Thanks.

Resources