@ Phương thức không mong muốn và phương thức tĩnh


100

Tôi có @Autowireddịch vụ phải được sử dụng từ bên trong một phương thức tĩnh. Tôi biết điều này là sai nhưng tôi không thể thay đổi thiết kế hiện tại vì nó sẽ đòi hỏi rất nhiều công việc, vì vậy tôi cần một số hack đơn giản cho điều đó. Tôi không thể thay đổi randomMethod()thành không tĩnh và tôi cần sử dụng bean tự động này. Bất kỳ manh mối làm thế nào để làm điều đó?

@Service
public class Foo {
    public int doStuff() {
        return 1;
    }
}

public class Boo {
    @Autowired
    Foo foo;

    public static void randomMethod() {
         foo.doStuff();
    }
}

4
Phương thức tĩnh không thể tham chiếu đến trường không tĩnh / trường hợp.
Sotirios Delimanolis,

18
đó là lý do tại sao tôi tạo chuỗi này, có cách nào mà phiên bản Tự động mong muốn có thể được truy cập từ bên trong phương thức tĩnh ...
Taks

Tại sao sử dụng @Autowosystem trong phương thức tĩnh không đúng?
user59290

Câu trả lời:


151

Bạn có thể thực hiện việc này bằng cách làm theo một trong các giải pháp:

Sử dụng hàm tạo @Autowosystem

Cách tiếp cận này sẽ xây dựng bean yêu cầu một số bean làm tham số khởi tạo. Trong mã hàm tạo, bạn đặt trường tĩnh với giá trị nhận được làm tham số để thực thi hàm tạo. Mẫu vật:

@Component
public class Boo {

    private static Foo foo;

    @Autowired
    public Boo(Foo foo) {
        Boo.foo = foo;
    }

    public static void randomMethod() {
         foo.doStuff();
    }
}

Sử dụng @PostConstruct để chuyển giá trị cho trường tĩnh

Ý tưởng ở đây là chuyển một bean cho một trường tĩnh sau khi bean được cấu hình bởi spring.

@Component
public class Boo {

    private static Foo foo;
    @Autowired
    private Foo tFoo;

    @PostConstruct
    public void init() {
        Boo.foo = tFoo;
    }

    public static void randomMethod() {
         foo.doStuff();
    }
}

3
Đây có phải là một giải pháp an toàn?
Taks

2
Tôi đã sử dụng giải pháp đầu tiên và nó hoạt động như một sự quyến rũ, cảm ơn!
victorleduc

1
Giải pháp đầu tiên không hỗ trợ sử dụng @Qualifier. Nó vẫn có vấn đề nếu sử dụng một số Kho lưu trữ.
user1767316

15
Điều gì sẽ đảm bảo rằng hàm tạo được gọi trước khi phương thức tĩnh được truy cập?
David Dombrowsky

2
phương thức init sẽ gây ra lỗi SonarQube vì phương thức không tĩnh sửa đổi trường tĩnh.
jDub9

45

Bạn phải giải quyết vấn đề này thông qua phương pháp tiếp cận trình truy cập ngữ cảnh ứng dụng tĩnh:

@Component
public class StaticContextAccessor {

    private static StaticContextAccessor instance;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void registerInstance() {
        instance = this;
    }

    public static <T> T getBean(Class<T> clazz) {
        return instance.applicationContext.getBean(clazz);
    }

}

Sau đó, bạn có thể truy cập các cá thể bean theo cách tĩnh.

public class Boo {

    public static void randomMethod() {
         StaticContextAccessor.getBean(Foo.class).doStuff();
    }

}

Tôi thực sự thích giải pháp này mặc dù tôi không hoàn toàn hiểu nó .. Tôi chỉ đang đầu óc của tôi khoảng mùa xuân và tôi cần nhanh chóng cấu trúc lại một số đoạn mã .. và đây là vấn đề trộn tĩnh với tự động tải .. giải pháp này an toàn như thế nào?
Taks

2
Sẽ khá an toàn nếu các cuộc gọi tĩnh nằm trong tầm kiểm soát của bạn. Khía cạnh tiêu cực rõ ràng nhất là bạn có thể gọi getBeantrước khi ngữ cảnh được khởi tạo (NPE) hoặc sau khi ngữ cảnh có đậu của nó bị phá hủy. Cách tiếp cận này có lợi ích của nó là truy cập ngữ cảnh tĩnh "xấu xí" được bao bọc trong một phương thức / lớp.
Pavel Horal

1
Điều này đã cứu mạng tôi. Nó rất hữu ích so với cách tiếp cận khác.
Phoenix

6

Những gì bạn có thể làm là @Autowiredmột phương thức setter và đặt nó một trường tĩnh mới.

public class Boo {
    @Autowired
    Foo foo;

    static Foo staticFoo;   

    @Autowired
    public void setStaticFoo(Foo foo) {
        Boo.staticFoo = foo;
    }

    public static void randomMethod() {
         staticFoo.doStuff();
    }
}

Khi bean được xử lý, Spring sẽ đưa một Foocá thể triển khai vào trường cá thể foo. Sau đó, nó cũng sẽ đưa Foocá thể tương tự vào setStaticFoo()danh sách đối số, sẽ được sử dụng để đặt trường tĩnh.

Đây là một cách giải quyết tồi tệ và sẽ không thành công nếu bạn cố gắng sử dụng randomMethod()trước khi Spring xử lý một phiên bản của Boo.


sẽ sử dụng @PostConstruct trợ giúp?
Taks

@Taks Chắc chắn, điều đó cũng hoạt động. Trên setStaticFoo()có nghĩa là, nếu không có sự Footham số.
Sotirios Delimanolis

câu hỏi là nó sẽ làm cho nó an toàn hơn .. :) Tôi nghĩ rằng mùa xuân sẽ xử lý tất cả mọi thứ trước khi cho phép chúng tôi thực hiện bất kỳ phương pháp ..
TAKS

1
@Taks Cách bạn đã hiển thị nó không hoạt động (trừ khi bạn đang hiển thị mã giả). Bất kỳ manh mối làm thế nào để làm điều đó? Nhiều câu trả lời bạn nhận được là cách giải quyết nhưng tất cả đều có chung một vấn đề là bạn không thể sử dụng trường tĩnh cho đến khi Spring xử lý lớp của bạn (thực sự xử lý một trường hợp có tác dụng phụ). Theo nghĩa đó, nó không an toàn.
Sotirios Delimanolis,

3

Nó tệ nhưng bạn có thể lấy bean bằng cách sử dụng ApplicationContextAwaregiao diện. Cái gì đó như :

public class Boo implements ApplicationContextAware {

    private static ApplicationContext appContext;

    @Autowired
    Foo foo;

    public static void randomMethod() {
         Foo fooInstance = appContext.getBean(Foo.class);
         fooInstance.doStuff();
    }

    @Override
    public void setApplicationContext(ApplicationContext appContext) {
        Boo.appContext = appContext;
    }
}

0

Điều này được xây dựng dựa trên câu trả lời của @ Pavel , để giải quyết khả năng ngữ cảnh Spring không được khởi tạo khi truy cập từ phương thức getBean tĩnh:

@Component
public class Spring {
  private static final Logger LOG = LoggerFactory.getLogger (Spring.class);

  private static Spring spring;

  @Autowired
  private ApplicationContext context;

  @PostConstruct
  public void registerInstance () {
    spring = this;
  }

  private Spring (ApplicationContext context) {
    this.context = context;
  }

  private static synchronized void initContext () {
    if (spring == null) {
      LOG.info ("Initializing Spring Context...");
      ApplicationContext context = new AnnotationConfigApplicationContext (io.zeniq.spring.BaseConfig.class);
      spring = new Spring (context);
    }
  }

  public static <T> T getBean(String name, Class<T> className) throws BeansException {
    initContext();
    return spring.context.getBean(name, className);
  }

  public static <T> T getBean(Class<T> className) throws BeansException {
    initContext();
    return spring.context.getBean(className);
  }

  public static AutowireCapableBeanFactory getBeanFactory() throws IllegalStateException {
    initContext();
    return spring.context.getAutowireCapableBeanFactory ();
  }
}

Phần quan trọng ở đây là initContextphương pháp. Nó đảm bảo rằng ngữ cảnh sẽ luôn được khởi tạo. Tuy nhiên, hãy lưu ý rằng đó initContextsẽ là một điểm gây tranh cãi trong mã của bạn vì nó được đồng bộ hóa. Nếu ứng dụng của bạn được song song hóa nhiều (ví dụ: phụ trợ của một trang web có lưu lượng truy cập cao), thì đây có thể không phải là giải pháp tốt cho bạn.


-2

Sử dụng AppContext. Đảm bảo rằng bạn tạo một bean trong tệp ngữ cảnh của mình.

private final static Foo foo = AppContext.getApplicationContext().getBean(Foo.class);

public static void randomMethod() {
     foo.doStuff();
}

Cái này là cái gì?? Sự khác biệt giữa
@Autowosystem

Thông thường khi bạn không thể biến lớp thành một mùa xuân thông thường @Component, điều này xảy ra rất nhiều với mã kế thừa.
carpinchosaurio
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.