around-spring目标方法aop跳过代码执行

hvvq6cgz  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(378)

据我所知, @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() 使用 OutputStreamHttpServletResponse 打印 application/pdf 返回给用户 CompletableFuture<Void> . 至于为什么,该方法与上面的异步函数通信,因此我想保证操作以某种方式完成。

ztyzrc3y

ztyzrc3y1#

为了教你什么是mcve以及如何在这里更好地提问,我将向你展示我根据你的代码片段和描述创建的mcve:
我们需要依赖类来编译代码:

package de.scrum_master.spring.q66958382;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Profiling {}
package de.scrum_master.spring.q66958382;

public class GuildMember {
  private String name;

  public GuildMember(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "GuildMember{" +
      "name='" + name + '\'' +
      '}';
  }
}
``` `PDFExporter` 实用程序:
也许你用的是 `PDFExporter` 但这只是猜测。在任何情况下,它看起来都不是spring组件,因为稍后您将调用构造函数,而不是从应用程序上下文获取bean示例。所以我在这里把它建模为一个简单的pojo。

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

服务 `@Async` 方法:
接下来,我对你的公会服务做了一个有根据的猜测:

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

分析方面要针对的组件:
这是您的示例组件,只是在 `guildMembers` 初始化。但它根本不会改变功能:

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

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

}
}

驱动程序应用程序+ `@EnableAsync` 配置:

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
(...)

因此,如果它在您自己的应用程序中不起作用,则可能与您描述它的方式不同,或者您误解了日志。

相关问题