Spring AOP - Determine whether method was invoked by #Scheduled - spring

I have a runtime annotation #MyAnnotation, and I would like to write an Aspect that determines whether the test() method below was called by:
Spring's #Scheduled framework
normal method invocation
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Scheduled(cron = "*/1 * * * * *") // scheduled to invoke every second
#MyAnnotation
public void test() {
// business logic
}
}
aspect code (pointcut + advice)
#Around(value="#annotation(myAnnotation)")
public Object featureToggle(ProceedingJoinPoint joinPoint, MyAnnotation myAnnotation) throws Throwable {
Boolean isInvoked = // TODO - is invoked by #Scheduled or not
}

Inspecting stack traces is always ugly, but of course you can do it:
package de.scrum_master.spring.q65397019;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {}
package de.scrum_master.spring.q65397019;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class MyComponent {
#Scheduled(fixedRate = 1000)
// #Async
#MyAnnotation
public void doSomething() {}
}
package de.scrum_master.spring.q65397019;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
#SpringBootApplication
#Configuration
#EnableScheduling
#EnableAsync
public class DemoApplication {
public static void main(String[] args) throws InterruptedException {
try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) throws InterruptedException {
MyComponent myComponent = appContext.getBean(MyComponent.class);
myComponent.doSomething();
Thread.sleep(1000);
myComponent.doSomething();
}
}
package de.scrum_master.spring.q65397019;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;
#Aspect
#Component
public class MyAspect {
#Around("#annotation(myAnnotation)")
public Object advice2(ProceedingJoinPoint joinPoint, MyAnnotation myAnnotation) throws Throwable {
if (
Arrays.stream(new Exception().getStackTrace())
.map(StackTraceElement::toString)
.anyMatch(string -> string.contains("scheduling.support.ScheduledMethodRunnable.run("))
)
System.out.println(joinPoint + " -> scheduled");
else
System.out.println(joinPoint + " -> normal");
return joinPoint.proceed();
}
}
This will print something like:
(...)
2020-12-22 10:00:59.372 INFO 1620 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
execution(void de.scrum_master.spring.q65397019.MyComponent.doSomething()) -> scheduled
2020-12-22 10:00:59.456 INFO 1620 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-12-22 10:00:59.456 INFO 1620 --- [ main] d.s.spring.q65397019.DemoApplication : Started DemoApplication in 6.534 seconds (JVM running for 8.329)
execution(void de.scrum_master.spring.q65397019.MyComponent.doSomething()) -> normal
execution(void de.scrum_master.spring.q65397019.MyComponent.doSomething()) -> scheduled
execution(void de.scrum_master.spring.q65397019.MyComponent.doSomething()) -> normal
2020-12-22 10:01:00.475 INFO 1620 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Shutting down ExecutorService 'taskScheduler'
2020-12-22 10:01:00.477 INFO 1620 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
(...)
On Java 9+ you could use the stack walking API which would be more efficient than creating full stack traces from exception instances or querying them from the currently running thread.
Caveat: If you also annotate your scheduled method with #Async, then this will not work anymore because then the asynchronously running method does not have a stack trace in which you could identify that it was triggered by a ScheduledMethodRunnable or an application class.

Maybe you would like to achieve something like that:
#Slf4j
#Component
public class ScheduledTask {
#Scheduled(cron = "0/1 * * * * *")
#ScheduledTaskAnnotation(message = "ScheduledTaskMessage", number = 10)
public void doAction() {
log.debug("Task scheduled");
}
}
#Slf4j
#Aspect
#Component
public class ScheduledTaskAspect {
#Around("execution(public * *(..)) && #annotation(hu.gaszabo.sample.schedule.ScheduledTaskAnnotation)")
public void logScheduledTaskAction(final ProceedingJoinPoint p) {
log.debug("Aspect");
parameters(p).ifPresent(a -> {
log.debug("message: {}", a.message());
log.debug("number: {}", a.number());
});
try {
p.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
}
private Optional<ScheduledTaskAnnotation> parameters(final ProceedingJoinPoint p) {
final Method method = ((MethodSignature) p.getSignature()).getMethod();
return Optional.ofNullable(AnnotationUtils.findAnnotation(method, ScheduledTaskAnnotation.class));
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(value = { ElementType.METHOD })
public #interface ScheduledTaskAnnotation {
String message() default "Message";
long number() default 0L;
}

Related

Open tracer Jaeger Trace not getting reflect for apache camel

I am new to apache camel and open tracing with Jaeger, I trying to get the traces for apache camel in Jaeger UI but its not getting captured through Open Tracing.
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.springframework.stereotype.Component;
#Component
public class RouteBuilderClient {
public void test() throws Exception {
DefaultCamelContext camelContext = new DefaultCamelContext();
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
from("timer:first-timer")
.to("log:first-timer");
}
});
camelContext.start();
camelContext.stop();
}
}
However if if I extend the RouteBuilder class(Below is the sample code) and then I override the configure method then traces are getting generated for Apache camel. Is there any way without extending the routeBuilder I can get the traces?
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
#Component
public class RouteBuilderClient extends RouteBuilder {
#Override
public void configure() throws Exception {
from("timer:first-timer")
.to("log:first-timer");
}
}
My controller class:
import org.apache.camel.opentracing.starter.CamelOpenTracing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#CamelOpenTracing
public class JaegarClientApplication {
public static void main(String[] args) {
SpringApplication.run(JaegarClientApplication.class, args);
}
}
Try adding this in your setup() method, before starting the Camel context:
OpenTracingTracer ottracer = new OpenTracingTracer();
ottracer.init(camelContext);
camelContext.start();
Below is my code that Worked successfully.
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.opentracing.OpenTracingTracer;
public class RouteBuilderClient3 {
public void test() throws Exception {
RouteBuilder builder = new RouteBuilder() {
public void configure() {
from("timer:first-timer3").transform().constant("Rajesh client 3")
.to("log:first-timer3");
}
};
CamelContext myCamelContext = new DefaultCamelContext();
myCamelContext.addRoutes(builder);
OpenTracingTracer ottracer = new OpenTracingTracer();
ottracer.init(myCamelContext);
myCamelContext.start();
// myCamelContext.stop();
}
}

NullPointerException when using customized Autowired

I customized a Annotation #CustomizedAutowired like #Autowired by using BeanPostProcessor (InjectBeanPostProcessor.java), but I got a NullPointerException when AOP is used.
Why it is null when using AOP?
Why DemoController seems to be proxied twice when using AOP?
what should I do, so that #CustomizedAutowired can work just like #Autowired?
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Inherited
public #interface CustomizedAutowired {}
#RestController
#RequestMapping("/hello")
public class DemoController {
#CustomizedAutowired
private InjectBean injectBean;
#GetMapping("/world")
public LocalDateTime hello() {
injectBean.hello(); // injectBean is null
return LocalDateTime.now();
}
}
#Aspect
#Component
public class AopDemo {
#Pointcut("execution(public java.time.LocalDateTime *(..))")
public void pointcut() {}
#AfterReturning(pointcut = "pointcut()")
public void round() {
System.out.println("after returning");
}
}
#Component
public class InjectBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetClass = bean.getClass();
while (targetClass != null) {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(CustomizedAutowired.class)) {
field.setAccessible(true);
try {
field.set(bean, new InjectBean());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
targetClass = targetClass.getSuperclass();
}
return bean;
}
}
#SpringBootApplication
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#CustomizedAutowired
private InjectBean injectBean;
#Override
public void run(String... args) throws Exception {
System.out.println("instance -> " + this);
injectBean.hello(); // works fine here
}
}
Here is the result:
You cannot do field.set(bean, new InjectBean()) on the proxy bean, because it does not inherit any private fields. You need to unwrap the proxy and set the field on the original object.
I am not going to comment on the idea of using all that ugly reflection in order to implement your custom injection idea, just help you make it work. You can use AopTestUtils.getTargetObject(bean) instead of your while-loop in order to get the original object and then easily its class afterwards.
How about this?
package de.scrum_master.spring.q70408968;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.test.util.AopTestUtils;
import java.lang.reflect.Field;
#Component
public class InjectBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// Unwrap bean, in case it is a proxy
Object targetObject = AopTestUtils.getTargetObject(bean);
Class<?> targetClass = targetObject.getClass();
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(CustomizedAutowired.class)) {
field.setAccessible(true);
try {
field.set(targetObject, new InjectBean());
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
}
This is going to work both with and without AOP proxies. Note how I am injecting the field into targetObject, but returning the original bean instance (i.e. the proxy in the AOP case).
Then, get rid of the annotated member in the application class, because the application is not a normal Spring component.
package de.scrum_master.spring.q70408968;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
try (ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args)) {
context.getBean(DemoController.class).hello();
}
}
}
Now the application runs just fine.
o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
d.s.spring.q70408968.DemoApplication : Started DemoApplication in 7.457 seconds (JVM running for 10.318)
Hello from InjectBean
after returning
Or maybe you prefer a Java streams approach, but that is just cosmetics:
package de.scrum_master.spring.q70408968;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.test.util.AopTestUtils;
import java.util.Arrays;
#Component
public class InjectBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// Unwrap bean, in case it is a proxy
Object targetObject = AopTestUtils.getTargetObject(bean);
Arrays.stream(targetObject.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(CustomizedAutowired.class))
.forEach(field -> {
field.setAccessible(true);
try {
field.set(targetObject, new InjectBean());
}
catch (IllegalAccessException e) { e.printStackTrace(); }
});
return bean;
}
}
After searching from google, I found that the reason why NPE happens is that we got the wrong targetObject, BeanPostProcessor.postProcessAfterInitialization(Object bean, String beanName) gives us a proxied bean, and if we use AOP, it will be proxied twice, replace postProcessAfterInitialization with postProcessBeforeInitialization can solve this problem, or another solution that can do the Injection Operation before being proxied by AOP.

Spring boot different in hibernate vs eclipselink

I'm trying to change from Hibernate to Eclipselink as Jpa provider for spring boot. However, I'm seeing some difference in behaviour between the two.
Eclipselink Configuration
package com.test.test2;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
public class EclipselinkJpaConfiguration extends JpaBaseConfiguration {
protected EclipselinkJpaConfiguration(DataSource dataSource, JpaProperties
properties, ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
super(dataSource, properties, jtaTransactionManager);
}
#Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter();
}
#Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> map = new HashMap<String, Object>();
map.put(PersistenceUnitProperties.WEAVING, "false");
return map;
}
}
Controller
package com.test.test2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
#RestController
public class PersonController {
#Autowired
PersonService personService;
#GetMapping("/test")
public ResponseEntity<String> test() {
return new ResponseEntity<String>(personService.helloWorld(), HttpStatus.OK);
}
#PostMapping("/createNewPerson")
public ResponseEntity<Person> createNewPerson() {
Person res = personService.createNewPerson();
return new ResponseEntity<Person>(res, HttpStatus.OK);
}
#PostMapping("/updatePerson/{id}")
public ResponseEntity<Person> updatePerson(#PathVariable("id") Long id) {
Person res = personService.updatePerson(id);
personService.transAnnotation();
return new ResponseEntity<Person>(res, HttpStatus.OK);
}
}
Service
package com.test.test2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
#Service
public class PersonService {
#Autowired
PersonRepository personRepository;
public String helloWorld() {
return "Hello World!";
}
public Person createNewPerson () {
Person newPerson = new Person();
newPerson.setFirstName("firstName");
newPerson.setLastName("lastName");
return personRepository.save(newPerson);
}
public Person updatePerson(Long id) {
Optional<Person> personOP = personRepository.findById(id);
if (personOP.isPresent()) {
Person person = personOP.get();
person.setFirstName("Update Name");
}
return null;
}
#Transactional(readOnly = true)
public void transAnnotation () {
}
}
Repository
package com.test.test2;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface PersonRepository extends JpaRepository<Person, Long>{
}
Eclipselink behavior
When calling /updatePerson/{id} api, person.firstName is updated to db without calling personRepoitory.save() if the next method is tagged with #Transactional.
As spring repository itself is transactional, even calling a personRepository.findAll() will commit and firstName is updated to db.
Logs
2021-11-24 12:01:06.264 INFO 2868 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
2021-11-24 12:01:06.269 INFO 2868 --- [ main] com.test.test2.Test2Application : Started Test2Application in 1.966 seconds (JVM running for 2.227)
2021-11-24 12:01:12.689 INFO 2868 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-11-24 12:01:12.689 INFO 2868 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-11-24 12:01:12.689 INFO 2868 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
[EL Fine]: sql: 2021-11-24 12:01:12.741--ServerSession(2122837918)--Connection(555991754)--SELECT ID, first_name, last_name FROM TBL_PERSON WHERE (ID = ?)
bind => [1]
[EL Fine]: sql: 2021-11-24 12:01:12.771--ClientSession(1084235622)--Connection(819605412)--UPDATE TBL_PERSON SET first_name = ? WHERE (ID = ?)
bind => [Update Name, 1]
Whereas for Hibernate, the changes are not commited to db unless a save() is called.
I am aware that a if a function is tagged with #Transactional, we do not need to call save() as it will auto commit the changes. However, currently it is tagged only in the next method called.
Any possible causes for this? Could it be due to incorrect configuration?

KafkaBindingRebalanceListener Bean not autowired by KafkaMessageChannelBinder Bean

Documentation is pretty straight forward which suggests exposing a Bean of type KafkaBindingRebalanceListener and onPartitiosnAssigned method would be called internally. I'm trying to do the same and somehow while spring framework creates its KafkaMessageChannelBinder Bean the ObjectProvider.getIfUnique() always return null as it not able to find the required bean. It seems when application starts SpringFramework strats creating its Beans first and isnt able to find the Rebalance Listener Bean as it is not yet created. Following are the three code snippets from project. Please help if im missing anything to instruct application to create Beans in application package first before going to Spring Framework.
RebalanceListener
package io.spring.dataflow.sample.seekoffset.config;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.common.TopicPartition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.binder.kafka.KafkaBindingRebalanceListener;
import org.springframework.stereotype.Component;
import java.util.Collection;
#Component
public class KafkaRebalanceListener implements KafkaBindingRebalanceListener {
Logger logger = LoggerFactory.getLogger(SeekOffsetConfig.class);
#Override
public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer, Collection<TopicPartition> partitions, boolean initial) {
logger.debug("onPartitionsAssigned");
}
}
ConfigClass
package io.spring.dataflow.sample.seekoffset.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
#EnableBinding(Sink.class)
public class SeekOffsetConfig {
Logger logger = LoggerFactory.getLogger(SeekOffsetConfig.class);
#StreamListener(Sink.INPUT)
public void receiveMessage(Message<String> message) {
logger.debug("receiveMessage()");
}
}
ApplicationClass
package io.spring.dataflow.sample.seekoffset;
import io.spring.dataflow.sample.seekoffset.config.KafkaRebalanceListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
public class SeekOffsetApplication {
Logger logger = LoggerFactory.getLogger(SeekOffsetApplication.class);
public static void main(String[] args) {
SpringApplication.run(SeekOffsetApplication.class, args);
}
}
What version are you using? This works fine for me with Boot 2.3.2 and Hoxton.SR6:
#SpringBootApplication
#EnableBinding(Sink.class)
public class So63157778Application {
public static void main(String[] args) {
SpringApplication.run(So63157778Application.class, args);
}
#StreamListener(Sink.INPUT)
public void listen(String in) {
System.out.println(in);
}
#Bean
KafkaBindingRebalanceListener rebal() {
return new KafkaBindingRebalanceListener() {
#Override
public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer,
Collection<TopicPartition> partitions, boolean initial) {
System.out.println(bindingName + " assignments: " + partitions + ", initial call :" + initial);
}
};
}
}
input assignments: [input-0], initial call :true
This works for me too:
#SpringBootApplication
#EnableBinding(Sink.class)
public class So63157778Application {
public static void main(String[] args) {
SpringApplication.run(So63157778Application.class, args);
}
#StreamListener(Sink.INPUT)
public void listen(String in) {
System.out.println(in);
}
}
#Component
class Foo implements KafkaBindingRebalanceListener {
#Override
public void onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer,
Collection<TopicPartition> partitions, boolean initial) {
System.out.println(bindingName + " assignments: " + partitions + ", initial call :" + initial);
}
}

Spring Boot JettyServerCustomizer | HTTPS port not opened | A ServletContext is required to configure default servlet handling

I am trying to use Spring Boot with embedded. I want the embedded Jetty to open a HTTPS port at 443.
After referring the answer posted here, I came up with this configuration:-
import java.io.FileNotFoundException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ssl.SslSocketConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.util.ResourceUtils;
import com.samsoft.expunto.service.UserService;
/**
* #author Kumar Sambhav Jain
*
*/
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/", "/resources/**")
.permitAll().anyRequest().authenticated().and().formLogin()
.loginPage("/").defaultSuccessUrl("/home", false).and()
.requiresChannel().anyRequest().requiresSecure().and().logout()
.invalidateHttpSession(true).logoutUrl("/logout")
.logoutSuccessUrl("/").and().userDetailsService(userService);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
}
#Bean
public JettyServerCustomizer jettyCutomizer() {
return new JettyServerCustomizer() {
#Override
public void customize(Server server) {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePassword("jetty6");
try {
sslContextFactory.setKeyStorePath(ResourceUtils.getFile(
"classpath:jetty-ssl.keystore").getAbsolutePath());
} catch (FileNotFoundException ex) {
throw new IllegalStateException("Could not load keystore",
ex);
}
SslSocketConnector sslConnector = new SslSocketConnector(
sslContextFactory);
sslConnector.setPort(443);
sslConnector.setMaxIdleTime(60000);
server.addConnector(sslConnector);
}
};
}
}
Trying to run the application using spring-boot:run, I can see in logs that port 80 is opened but no HTTPS port:-
2014-06-10 23:41:56.932 INFO 196 --- [lication.main()] /
: Initializing Spring FrameworkServlet 'dispatcherServlet' 2014-06-10
23:41:56.932 INFO 196 --- [lication.main()]
o.s.web.servlet.DispatcherServlet : FrameworkServlet
'dispatcherServlet': initialization started 2014-06-10 23:41:56.960
INFO 196 --- [lication.main()] o.s.web.servlet.DispatcherServlet
: FrameworkServlet 'dispatcherServlet': initialization completed in 26
ms 2014-06-10 23:41:57.037 INFO 196 --- [lication.main()]
o.e.jetty.server.AbstractConnector : Started
SelectChannelConnector#0.0.0.0:80 2014-06-10 23:41:57.043 INFO 196
--- [lication.main()] .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port: 80 2014-06-10 23:41:57.045 INFO 196 ---
[lication.main()] c.s.expunto.web.config.Application : Started
Application in 7.628 seconds (JVM running for 16.509)
UPDATE
Using this configuration:-
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
implements EmbeddedServletContainerCustomizer {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/", "/resources/**")
.permitAll().anyRequest().authenticated().and().formLogin()
.loginPage("/").defaultSuccessUrl("/home", false).and()
.requiresChannel().anyRequest().requiresSecure().and().logout()
.invalidateHttpSession(true).logoutUrl("/logout")
.logoutSuccessUrl("/").and().userDetailsService(userService);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
}
public JettyServerCustomizer jettyServerCustomizer() {
return new JettyServerCustomizer() {
#Override
public void customize(Server server) {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePassword("jetty6");
try {
sslContextFactory.setKeyStorePath(ResourceUtils.getFile(
"classpath:jetty-ssl.keystore").getAbsolutePath());
} catch (FileNotFoundException ex) {
throw new IllegalStateException("Could not load keystore",
ex);
}
SslSocketConnector sslConnector = new SslSocketConnector(
sslContextFactory);
sslConnector.setPort(443);
sslConnector.setMaxIdleTime(60000);
server.addConnector(sslConnector);
}
};
}
public void customizeJetty(
JettyEmbeddedServletContainerFactory containerFactory) {
containerFactory.addServerCustomizers(jettyServerCustomizer());
}
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof JettyEmbeddedServletContainerFactory) {
customizeJetty((JettyEmbeddedServletContainerFactory) container);
}
container.setContextPath("");
}
}
I get this error:-
: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.<init>(DefaultServletHandlerConfigurer.java:54)
at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping(WebMvcConfigurationSupport.java:346)
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$d7014349.CGLIB$defaultServletHandlerMapping$24(<generated>)
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$d7014349$$FastClassBySpringCGLIB$$ec8be680.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$d7014349.defaultServletHandlerMapping(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
As per M. Deinum advice, moving the customizers to class annotated with #EnableAutoConfiguration did the trick.
This is what worked for me:-
#Configuration
#EnableAutoConfiguration
public class Application implements EmbeddedServletContainerCustomizer {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
#Bean
public JettyServerCustomizer jettyServerCustomizer() {
return new JettyServerCustomizer() {
#Override
public void customize(Server server) {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePassword("jetty6");
try {
sslContextFactory.setKeyStorePath(ResourceUtils.getFile(
"classpath:jetty-ssl.keystore").getAbsolutePath());
} catch (FileNotFoundException ex) {
throw new IllegalStateException("Could not load keystore",
ex);
}
SslSocketConnector sslConnector = new SslSocketConnector(
sslContextFactory);
sslConnector.setPort(443);
sslConnector.setMaxIdleTime(60000);
server.addConnector(sslConnector);
}
};
}
public void customizeJetty(
JettyEmbeddedServletContainerFactory containerFactory) {
containerFactory.addServerCustomizers(jettyServerCustomizer());
}
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof JettyEmbeddedServletContainerFactory) {
customizeJetty((JettyEmbeddedServletContainerFactory) container);
}
}
}

Resources