2011-01-28 38 views
5

Tôi thực sự cuộn lên tay áo của mình và cố gắng hiểu chú thích Java lần đầu tiên và đã đọc bài viết về Sun, Oracle và Wikipedia về chủ đề này. Họ dễ hiểu về khái niệm, nhưng tôi cảm thấy khó khăn khi đặt tất cả các mảnh ghép lại với nhau.Chú thích Java và apt (nguyên tắc cơ bản)

Ví dụ sau đây có lẽ là kỹ thuật khủng khiếp, nhưng chỉ hài hước với tôi (đó là một ví dụ !).

Hãy nói rằng tôi có lớp sau đây:

 
public Widget 
{ 
    // ... 

    public void foo(int cmd) 
    { 
     switch(cmd) 
     { 
     case 1: 
      function1(); 
      break; 
     case 2: 
      function2(); 
      break; 
     case 3: 
     default: 
      function3(); 
      break; 
     } 
    } 
} 

Bây giờ, ở một nơi khác trong dự án của tôi, tôi có một lớp khác, tàu con thoi, có một phương pháp gọi là blastOff():

 
public class SpaceShuttle 
{ 
    // ... 

    public void blastOff() 
    { 
     // ... 
    } 
} 

Bây giờ, tôi muốn định cấu hình chú thích được gọi là Widgetize để mọi phương thức được chú thích bằng @Widgetize sẽ có Widget :: foo (int) được gọi trước khi có cuộc gọi của riêng chúng.

 
@interface Widgetize 
{ 
    int cmd() default 2; 
} 

Vì vậy, bây giờ hãy xem lại tàu con thoi:

 
public class SpaceShuttle 
{ 
    // ... 

    @Widgetize(3) 
    public void blastOff() 
    { 
     // Since we pass a cmd of "3" to @Widgetize, 
     // Widget::function3() should be invoked, per 
     // Widget::foo()'s definition. 
    } 
} 

Than ôi, câu hỏi của tôi!

  1. Tôi giả định rằng ở đâu đó tôi cần xác định bộ xử lý chú thích; một lớp Java sẽ chỉ định phải làm gì khi các chú thích @Widgetize (int) gặp phải, đúng không? Hay điều này xảy ra trong các tập tin cấu hình XML được đưa vào apt (giống như cách kiến ​​đọc tệp build.xml)?

  2. Chỉnh sửa: Nếu tôi đúng về các bộ xử lý chú thích này ở câu hỏi số 1 ở trên, thì làm cách nào để "lập bản đồ"/"đăng ký"/làm cho các bộ vi xử lý này biết đến apt?

  3. Trong bản dựng, apt thường chạy trước javac, để thay đổi dựa trên chú thích hoặc tạo mã diễn ra trước khi biên dịch? (Đây là câu hỏi kiểu thực hành tốt nhất).

Cảm ơn và tôi xin lỗi vì mẫu mã của tôi, họ hóa ra cồng kềnh hơn nhiều so với tôi dự định họ (!)

+0

Đối với hồ sơ, câu hỏi của bạn là rất rõ ràng. Tôi nghĩ rằng vấn đề bạn sẽ chạy vào một khi bạn đã có apt cấu hình (và lý do tôi đề nghị AOP) là điều này. Nó rất dễ dàng để tạo mã. Tuy nhiên, nó thực sự khó sửa đổi mã. Phần phức tạp ở đây là sửa đổi mã gọi 'spaceShuttle.blastOff()' 'để thực sự gọi đúng phương thức proxy. – Pace

Trả lời

4

này nghe có vẻ giống như AOP (lập trình hướng Aspect) so với các chú thích. Các chủ đề thường bị nhầm lẫn vì AOP sử dụng chú thích để đạt được mục tiêu của nó. Thay vì phát minh lại AOP từ đầu, tôi khuyên bạn nên tìm kiếm và hiện có thư viện AOP như AspectJ.

Tuy nhiên, để trả lời câu hỏi cụ thể của bạn, có hai cách tiếp cận có thể có để đạt được mục tiêu của bạn.

Runtime Cách tiếp cận

Đây là phương pháp thường được thực hiện bởi các khuôn khổ container (như mùa xuân).Cách nó hoạt động là thay vì tự khởi tạo các lớp của bạn, bạn hỏi một thùng chứa cho một thể hiện của lớp của bạn.

Vùng chứa có logic để kiểm tra lớp cho bất kỳ RuntimeAnnotations nào (như @Widgetize). Sau đó, vùng chứa sẽ tự động tạo proxy của lớp của bạn gọi phương thức Widgetize chính xác trước và sau đó gọi phương thức đích.

Vùng chứa sau đó sẽ trả lại proxy đó cho người yêu cầu ban đầu. Người yêu cầu vẫn sẽ làm điều anh ta có lớp (hoặc giao diện) mà anh ta yêu cầu và hoàn toàn không biết về hành vi proxy được thêm vào bởi container.

Đây cũng là hành vi được AspectJ sử dụng.

Enhancement Cách tiếp cận

Đây là phương pháp chụp bởi AspectJ. Thành thật mà nói, tôi không biết nhiều chi tiết về cách thức hoạt động của nó. Bằng cách nào đó, AspectJ sẽ quét các tệp lớp của bạn (mã byte), tìm ra nơi các chú thích là, và sau đó sửa đổi mã byte để gọi lớp proxy thay vì lớp thực tế.

Lợi ích của phương pháp này là bạn không cần sử dụng vùng chứa. Hạn chế là bạn phải thực hiện bước nâng cao này sau khi biên dịch mã của mình.

+0

Tôi đồng ý, AOP có vẻ như cách tiếp cận tốt hơn (+1) –

+0

& @Sean - trình bao bọc APT như apt-jelly là gì? Chúng dường như làm tất cả những công việc khó khăn mà tôi đang nói đến ở đây cho bạn bằng cách cung cấp một API siêu đơn giản. Suy nghĩ? – Eugie

+0

Hãy xem nhận xét của tôi về câu hỏi ban đầu của bạn. Tạo proxy là phần dễ dàng. Phần khó khăn là sửa đổi mã hiện có để gọi mã được tạo ra chứ không phải mã hiện có. apt-jelly chỉ dành cho thế hệ tạo tác. – Pace

2

Tôi giả định rằng ở đâu đó tôi cần phải xác định bộ xử lý chú thích; một lớp Java sẽ chỉ định những gì cần làm khi chú thích @Widgetize (int) là gặp phải, phải không? Hay điều này xảy ra trong các tệp cấu hình XML được cho ăn vào apt (giống như cách đọc kiến ​​ tệp build.xml)?

Trong Java 1.6, cách tiêu chuẩn để xác định bộ xử lý chú thích thông qua ServiceLoader SPI.

Trong buildscripts, là apt thường chạy trước javac, để chú thích dựa trên thay đổi hoặc mã thế hệ diễn ra trước khi biên dịch? (Đây là câu hỏi loại thực hành tốt nhất ).

APT phải diễn ra trước khi biên dịch, vì nó hoạt động trên tệp nguồn (thực sự trên cây cú pháp).

1

Tôi thường xuyên sử dụng các phương pháp đánh chặn phương pháp với Hibernate. Hibernate yêu cầu một giao dịch được bắt đầu và cam kết quanh mọi truy vấn. Thay vì có nhiều mã trùng lặp, tôi chặn mọi phương thức Hibernate và bắt đầu giao dịch trong trình chặn.

Tôi sử dụng công cụ đánh chặn phương pháp AOP Alliance kết hợp với Google Guice cho việc này. Sử dụng chúng, bạn sử dụng chú thích Widgetise của bạn và sau đó sử dụng Guice để nói nơi bạn thấy chú thích này sử dụng phương pháp đánh chặn phương pháp này. Đoạn mã Guice sau đây thực hiện điều này.

 bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), new TransactionalInterceptor); 

Bộ chặn đánh bắt phương pháp, sau đó bạn có thể gọi foo và sau đó yêu cầu trình chặn đánh chặn phương pháp gốc.Ví dụ: (dưới dạng đơn giản hóa):

public class Interceptor implements MethodInterceptor { 

    //PUT ANY DEPENDENCIES HERE IN A CONSTRUCTOR 

    public Object invoke(MethodInvocation invocation) throws Throwable { 

      //DO FOO HERE 

      result = invocation.proceed(); 
     return result; 
    } 

} 

Điều này có thể khiến tôi hơi bối rối nhưng phải mất một thời gian để làm quen với tôi.

0

Dường như cơ sở của sự nhầm lẫn của bạn là một niềm tin không chính xác rằng Chú thích là thứ gì đó không chỉ là Siêu dữ liệu. Hãy xem trang này page từ hướng dẫn ngôn ngữ JSE 1.5. Dưới đây là một đoạn có liên quan:

Chú thích không trực tiếp ảnh hưởng ngữ nghĩa chương trình, nhưng họ làm ảnh hưởng đến cách chương trình được xử lý bằng công cụ và các thư viện, có thể lần lượt ảnh hưởng đến ngữ nghĩa của các chương trình đang chạy . Bạn có thể đọc chú thích từ tệp nguồn, tệp lớp học hoặc phản ánh tại thời gian chạy.

Trong mã của bạn

@Widgetize(3) 
    public void blastOff()

không gây Widget::function3() để thực hiện (asside: trong java chúng tôi tham khảo một thành viên như Widget.function3()) Chú thích là chỉ tương đương với một bình luận máy có thể đọc (tức metadata).

+0

DwB - cảm ơn cho đầu vào. Tôi hiểu rằng chú thích không phải thay đổi trực tiếp ngữ nghĩa, sự nhầm lẫn của tôi phụ thuộc vào những điều sau: bạn muốn có một phương thức cụ thể (Widget.foo()) để được gọi bất cứ khi nào bạn gặp một phương thức được chú thích bằng @Widgetize. Rõ ràng bạn cần một số cách để ràng buộc hai cấu trúc này lại với nhau (chú thích và phương thức "trigger"/interceptor của nó). Tôi giả định điều này được thực hiện trong một cái gì đó gọi là một bộ xử lý chú thích, có? – Eugie

+0

@Pam Thành thật mà nói, tôi sử dụng chú thích, nhưng tôi không viết chúng. Câu trả lời cho "bạn muốn một phương thức cụ thể ... được gọi bất cứ khi nào ... @Widgetize" nằm trong không gian Aspect (AOP). Chắc chắn, bạn sẽ đánh dấu phương thức bằng một chú thích, nhưng thực tế "gọi blah khi blah" sẽ là kết quả của các công cụ AOP (xem câu trả lời của Pace). APT là tốt đẹp cho việc tạo ra các tập tin dựa trên chú thích, nhưng nó không phải là cho công cụ AOP (tôi tin). – DwB

0

Chú thích có thể được xử lý cả lúc biên dịch và thời gian chạy. Để xử lý chúng trong thời gian chạy yêu cầu sử dụng API phản chiếu, sẽ có tác động hiệu suất nếu nó không được thực hiện một cách thông minh.

Cũng có thể xử lý chú thích ở thời gian biên dịch. Mục tiêu là tạo một lớp mà sau đó bạn có thể sử dụng mã của mình. Có một bộ xử lý chú thích giao diện cụ thể phải thỏa mãn, và chúng phải được khai báo để được thực hiện.

Hãy xem bài viết sau đây cho một ví dụ đó là cấu trúc đơn giản để sử dụng trường hợp của bạn:

Annotation processing 101

Các vấn đề liên quan