Spring AOP pointcut не запускается при вызове метода объекта, если он доступен внутри списка
У меня есть приложение для весенней загрузки с парой классов, классом конфигурации и аспектом, как показано ниже. Приведенный ниже пример иллюстрирует проблему, с которой я столкнулся.
У меня есть офисный класс, в котором есть список принтеров в качестве зависимости, созданной с использованием конфигурации файла внешних свойств. Я хотел бы выполнять аспект всякий раз, когда вызывается метод Printer.getFilename. Это не запускает аспект, если у меня есть список принтеров, но он работает, когда у меня есть один объект принтера без списка.
package com.example
public class Office {
private final List<Printer> printersList;
public Office(Printer printersList){
this.printersList = printersList;
}
public void printFiles(){
for(Printer printer: printersList)
printer.getFileName();
}
}
package com.example
public class Printer {
private deviceId;
public String getFileName(){
return "fileName";
}
}
@Configuration
public class ApplicationConfiguration{
@Bean
public Office office(){
List<Printer> printerList = new ArrayList<>();
// Adding to the list based on printer id based on some external property file configuration
printerList.add(new Printer());
printerList.add(new Printer());
return new Office(printerList);
}
}
@Aspect
@Component
public class PrinterFileNameAspect {
@Pointcut("execution(* com.example.Printer.getFileName())")
private void getFileNameJp() {}
@Around("getFileNameJp()")
public String returnFileName(ProceedingJoinPoint pjp) {
return "Modified File Name";
}
}
Я обнаружил, что список beans не зарегистрирован в контейнере Spring. Поэтому я изменил класс конфигурации, чтобы зарегистрировать bean-компонент
@Configuration
public class ApplicationConfiguration{
@Autowired
private GenericWebApplicationContext context;
@Bean
public Office office(){
List<Printer> printerList = new ArrayList<>();
// Adding to the list based on printer id
Printer colorPrinter = new Printer();
context.registerBean("colorPrinter", Printer.class, () -> colorPrinter);
printerList.add(colorPrinter);
Printer specialPrinter = new Printer();
context.registerBean("specialPrinter", Printer.class, () -> specialPrinter);
printerList.add(specialPrinter);
return new Office(printerList);
}
}
Вышеуказанные изменения конфигурации не помогают. Я думаю, что упускаю кое-что в основах Spring Aop. Я хочу реализовать spring aop со списком принтеров, так как я не могу изменить логику генерации списка (логика генерации списка сложна и должна быть динамической).
3 ответа
Я добавляю альтернативный ответ, потому что вы, кажется, хотите узнать, как использовать новый метод GenericApplicationContext.registerBean(..)
введен в Spring 5. Поскольку я не являюсь пользователем Spring, я также хотел узнать, что это такое, и придумал это решение.
Опять же, я даю полные определения классов. Они похожи, но немного отличаются от моего первого ответа. В частности,Printer
больше не является прототипом @Component
но POJO. Я все еще ушелOffice
чтобы быть одноэлементным компонентом для удобства. Если вам также нужно несколько экземпляров, вы всегда можете настроить код в соответствии со своими потребностями.
Вот что важно и решает вашу проблему: после программной регистрации beans вы должны получить их из контекста приложения через getBean()
а не просто добавлять созданные вручную экземпляры POJO в список принтеров. Только если вы получаете bean-компоненты из контекста приложения, Spring позаботится также о создании прокси AOP там, где это необходимо.
package de.scrum_master.spring.q61661740;
public class Printer {
private String deviceId;
public Printer(String deviceId) {
this.deviceId = deviceId;
}
public String getFileName() {
return deviceId + ".pdf";
}
}
package de.scrum_master.spring.q61661740;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class Office {
private final List<Printer> printersList = new ArrayList<>();
public void addPrinter(Printer printer) {
printersList.add(printer);
}
public void printFiles() {
for (Printer printer : printersList)
System.out.println(printer.getFileName());
}
}
package de.scrum_master.spring.q61661740;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PrinterFileNameAspect {
// Package name is optional if aspect is in same name as Printer
@Pointcut("execution(* de.scrum_master.spring.q61661740.Printer.getFileName())")
private void getFileNameJp() {}
@Around("getFileNameJp()")
public String returnFileName(ProceedingJoinPoint pjp) throws Throwable {
return "modified_" + pjp.proceed();
}
}
package de.scrum_master.spring.q61661740;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.util.stream.Stream;
@SpringBootApplication
@Configuration
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext(Application.class)) {
// If you want to get rid of the `@SpringBootApplication` annotation, add this:
// appContext.scan(Application.class.getPackage().getName());
Office office = appContext.getBean(Office.class);
Stream
.of("colorPrinter", "specialPrinter")
.forEach(deviceID -> {
appContext.registerBean(deviceID, Printer.class, () -> new Printer(deviceID));
office.addPrinter(appContext.getBean(deviceID, Printer.class));
});
office.printFiles();
}
}
}
Журнал консоли выглядит так (сокращенно):
(...)
18:20:54.169 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'office'
18:20:54.177 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'colorPrinter'
18:20:54.178 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'specialPrinter'
colorPrinter.pdf
specialPrinter.pdf
(...)
Как насчет этого простого решения, основанного на прототипе bean-компонентов с ограниченной областью видимости?
package de.scrum_master.spring.q61661740;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class Printer {
public String getFileName() {
return "fileName";
}
public void configureIndividually(String whatever) {
System.out.println("printer being configured individually: " + whatever);
}
}
package de.scrum_master.spring.q61661740;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class Office {
private final List<Printer> printersList = new ArrayList<>();
public void addPrinter(Printer printer) {
printersList.add(printer);
}
public void printFiles() {
for (Printer printer : printersList)
System.out.println(printer.getFileName());
}
}
package de.scrum_master.spring.q61661740;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PrinterFileNameAspect {
// Package name is optional if aspect is in same name as Printer
@Pointcut("execution(* de.scrum_master.spring.q61661740.Printer.getFileName())")
private void getFileNameJp() {}
@Around("getFileNameJp()")
public String returnFileName(ProceedingJoinPoint pjp) {
return "modified file name";
}
}
package de.scrum_master.spring.q61661740;
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.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Application {
public static void main(String[] args) {
try (ConfigurableApplicationContext appContext = SpringApplication.run(Application.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) {
Printer colorPrinter = appContext.getBean(Printer.class);
colorPrinter.configureIndividually("my color config");
Printer specialPrinter = appContext.getBean(Printer.class);
specialPrinter.configureIndividually("my special config");
Office office = appContext.getBean(Office.class);
office.addPrinter(colorPrinter);
office.addPrinter(specialPrinter);
office.printFiles();
}
}
Теперь вы можете позволить контейнеру позаботиться о создании экземпляров bean-компонентов, но вы все равно можете настроить их индивидуально. Я не понимаю, почему в этом случае вам нужно вручную регистрировать beans.
Журнал консоли будет:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.8.RELEASE)
2020-05-10 15:52:24.817 INFO 10404 --- [ main] d.s.spring.q61661740.Application : Starting Application on Xander-Ultrabook with PID 10404 (C:\Users\alexa\Documents\java-src\spring-aop-playground\target\classes started by alexa in C:\Users\alexa\Documents\java-src\spring-aop-playground)
2020-05-10 15:52:24.821 INFO 10404 --- [ main] d.s.spring.q61661740.Application : No active profile set, falling back to default profiles: default
2020-05-10 15:52:25.895 INFO 10404 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-05-10 15:52:25.918 INFO 10404 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 17ms. Found 0 repository interfaces.
2020-05-10 15:52:26.454 INFO 10404 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$377fd151] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-05-10 15:52:27.148 INFO 10404 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-05-10 15:52:27.189 INFO 10404 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-05-10 15:52:27.190 INFO 10404 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.24]
2020-05-10 15:52:27.375 INFO 10404 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-05-10 15:52:27.376 INFO 10404 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2486 ms
2020-05-10 15:52:27.681 INFO 10404 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-05-10 15:52:28.005 INFO 10404 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-10 15:52:28.007 INFO 10404 --- [ main] d.s.spring.q61661740.Application : Started Application in 3.735 seconds (JVM running for 5.395)
printer being configured individually: my color config
printer being configured individually: my special config
modified file name
modified file name
2020-05-10 15:52:28.135 INFO 10404 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
Как насчет добавления пакета, содержащего Printer
класс к классу приложения SpringBoot и объявить их в @SpringBootApplication
@Aspect
@Component
public class PrinterAspect
{
@Around( "getFileNameJp()" )
private String returnFileName( ProceedingJoinPoint joinPoint ) throws Throwable
{
return "Modified File Name"; // This will always return this name
}
}
И наконец, включите AspectJAutoproxy
для приложения
@SpringBootApplication(scanBasePackages = "com.example.*" )
@EnableAspectJAutoProxy( proxyTargetClass = true )
public class Application
{
public static void main( String[] args )
{
SpringApplication.run( Application.class, args );
}
}
Вы также пропустили @Component
в Printer
а также Office
классы. Итак, в заключение, вашOffice
класс должен выглядеть,
@Component
public class Office
{
private final List<Printer> printer;
public Office( List<Printer> printer )
{
this.printer = printer;
}
public void printFiles()
{
// my code logic ...
// Demo logic
for( Printer printer1 : printer )
{
System.out.println( printer1.getFileName() );
}
// my code logic ...
}
}
И класс принтера должен выглядеть так
@Component
public class Printer
{
private int deviceId;
private String fileName;
public String getFileName()
{
// my code logic here ...
fileName = String.valueOf( System.nanoTime() ); // demo logic
return fileName;
}
}
И использование должно выглядеть так:
@Autowired
private Office office;
@GetMapping( "/demo" )
public List<String> demo()
{
office.printFiles();
return fileNames(); // To be implemented
}