1

我有一个带有几个类、配置类和方面的 Spring Boot 应用程序,如下所示。下面的例子是为了说明我面临的问题。

我有办公室类,它具有打印机列表作为使用外部属性文件配置创建的依赖项。每当调用 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";
    }
} 

我发现 bean 列表没有在 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,因为我无法更改列表生成逻辑(列表生成逻辑很复杂,必须是动态的)。

4

3 回答 3

2

我添加了一个替代答案,因为您似乎热衷于学习如何使用GenericApplicationContext.registerBean(..)Spring 5 中引入的新方法。由于我不是 Spring 用户,我也想了解它的含义并想出了这个解决方案。

同样,我提供了完整的类定义。它们相似,但与我的第一个答案略有不同。具体来说,Printer不再是原型范围@Component,而是 POJO。不过,为了方便起见,我仍然Office是一个单例组件。如果您还需要多个实例,您可以随时根据需要调整代码。

现在重要并解决您的问题是:以编程方式注册 bean 后,您应该从应用程序上下文中获取它们,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
(...)
于 2020-05-10T11:13:46.073 回答
1

如何将包含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 );
    }
}

你也错过了@ComponentinPrinterOffice类。所以总而言之,你的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
}
于 2020-05-07T16:10:01.420 回答
1

这个基于原型作用域 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 实例,但您仍然可以单独配置它们。我不明白为什么在这种情况下你必须手动注册 bean。

控制台日志将是:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: 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'
于 2020-05-10T08:56:50.063 回答