据我所知, @Around
springaop上的注解可以处理方法上的任何返回类型;与 void
类型返回null。
以下是记录方法持续时间的简单建议:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Profiling { }
@Aspect
@Component
public class ProfilingAspect {
// ...
@Around("@annotation(profilingAnnotation)")
public Object logDuration(ProceedingJoinPoint joinPoint, Profiling profilingAnnotation) throws Throwable {
long startTime = Instant.now().toEpochMilli();
Object result = null;
try {
result = joinPoint.proceed(); // on void methods, this supposed to return null
} catch (Throwable e) {
logger.error(e.getMessage(), e);
throw e;
} finally {
long endTime = Instant.now().toEpochMilli(); // Below is not ran all together
long duration = endTime - startTime;
logger.info(joinPoint.getSignature().toShortString()+": "+duration+"ms");
}
//return the result to the caller
return result;
}
}
但是,当在这个方法上调用时,它不会返回任何东西,而是跳过之后的所有代码 proceed()
总而言之。甚至 finally
阻止。
这是有问题的代码:
@GetMapping("/members/exportpdf")
@Profiling
public void exportToPDF(@RequestParam(required = false)String role, HttpServletResponse response) throws DocumentException, IOException, ExecutionException, InterruptedException {
CompletableFuture<List<GuildMember>> guildMembers;
if (role==null) {
guildMembers = guildService.findAll(); // Async Method
} else {
guildMembers = guildService.findByType(role); // Async Method
}
response.setContentType("application/pdf");
DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss");
String currentDateTime = dateFormatter.format(new Date());
String headerKey = "Content-Disposition";
String headerValue = "inline; filename=guildmembers_" + currentDateTime + ".pdf";
response.setHeader(headerKey, headerValue);
PDFExporter exporter = new PDFExporter(guildMembers);
exporter.export(response).get(); // exporter.export(..) is an Async method returning CompletableFuture<Void>
}
这怎么可能?我在配置上遗漏了什么吗?还是Spring的虫子?
注意。我使用的是springboot2.4.4和starter依赖项
编辑。 PDFExporter.export()
使用 OutputStream
在 HttpServletResponse
打印 application/pdf
返回给用户 CompletableFuture<Void>
. 至于为什么,该方法与上面的异步函数通信,因此我想保证操作以某种方式完成。
1条答案
按热度按时间ztyzrc3y1#
为了教你什么是mcve以及如何在这里更好地提问,我将向你展示我根据你的代码片段和描述创建的mcve:
我们需要依赖类来编译代码:
package de.scrum_master.spring.q66958382;
import org.springframework.scheduling.annotation.Async;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class PDFExporter {
CompletableFuture<List> guildMembers;
public PDFExporter(CompletableFuture<List> guildMembers) {
this.guildMembers = guildMembers;
}
@Async
public CompletableFuture export(HttpServletResponse response) {
return CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return null;
});
}
}
package de.scrum_master.spring.q66958382;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Service
public class GuildService {
@Async
public CompletableFuture<List> findAll() {
return CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return Arrays.asList(new GuildMember("Jane"), new GuildMember("John"), new GuildMember("Eve"));
});
}
@Async
public CompletableFuture<List> findByType(String role) {
return CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return Collections.singletonList(new GuildMember("Eve"));
});
}
}
package de.scrum_master.spring.q66958382;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Component
public class MyComponent {
@Autowired
private GuildService guildService;
@GetMapping("/members/exportpdf")
@Profiling
public void exportToPDF(@RequestParam(required = false) String role, HttpServletResponse response) throws IOException, ExecutionException, InterruptedException {
CompletableFuture<List> guildMembers = role == null ? guildService.findAll() : guildService.findByType(role);
}
}
package de.scrum_master.spring.q66958382;
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.mock.web.MockHttpServletResponse;
import org.springframework.scheduling.annotation.EnableAsync;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@SpringBootApplication
@Configuration
@EnableAsync
public class DemoApplication {
public static void main(String[] args) throws InterruptedException, IOException, ExecutionException {
try (ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) throws InterruptedException, IOException, ExecutionException {
MyComponent myComponent = appContext.getBean(MyComponent.class);
myComponent.exportToPDF("admin", new MockHttpServletResponse());
}
}
package de.scrum_master.spring.q66958382;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Aspect
@Component
public class ProfilingAspect {
private static final Logger logger = LoggerFactory.getLogger(ProfilingAspect.class);
@Around("@annotation(profilingAnnotation)")
public Object logDuration(ProceedingJoinPoint joinPoint, Profiling profilingAnnotation) throws Throwable {
long startTime = Instant.now().toEpochMilli();
try {
return joinPoint.proceed();
}
catch (Throwable e) {
logger.error(e.getMessage(), e);
throw e;
}
finally {
long duration = Instant.now().toEpochMilli() - startTime;
logger.info(joinPoint.getSignature().toShortString() + ": " + duration + " ms");
}
}
}
. ____ _ __ _ _
/\ / ' __ _ () __ __ _ \ \ \
( ( )__ | '_ | '| | ' / ` | \ \ \
\/ __)| |)| | | | | || (| | ) ) ) )
' || .__|| ||| |_, | / / / /
=========||==============|/=////
:: Spring Boot :: (v2.1.8.RELEASE)
2021-04-06 09:18:10.793 INFO 13616 --- [ main] d.s.spring.q66958382.DemoApplication : Starting DemoApplication on Xander-Ultrabook with PID 13616 (C:\Users\alexa\Documents\java-src\spring-aop-playground\target\classes started by alexa in C:\Users\alexa\Documents\java-src\spring-aop-playground)
(...)
2021-04-06 09:18:14.809 INFO 13616 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-06 09:18:14.812 INFO 13616 --- [ main] d.s.spring.q66958382.DemoApplication : Started DemoApplication in 4.815 seconds (JVM running for 7.782)
(...)
2021-04-06 09:18:15.839 INFO 13616 --- [ main] d.s.spring.q66958382.ProfilingAspect : MyComponent.exportToPDF(..): 1014 ms
(...)