Thứ Tư, 11 tháng 10, 2017

Java 9: Những thay đổi trong Reflection và Package Access


Java 1.9 đã được ra lò. Nhiều công ty sẽ bắt đầu đánh giá cũng như di chuyển codebase của mình qua nó. Nhưng tôi thì sẽ không làm như vậy,
Code của tôi khá là đơn giản, vốn được viết trên Java 1.8, và được dùng cho HTTP requests. Để giữ cho codebase của mình thật đơn giản, bởi tôi chả muốn thêm bất kì thư viện nào, tôi dùng một HttpURLConnection cho HTTP requests.
Thế nhưng phương pháp trên lại không thành công với Java 1.9.
Khi rà sát lại code của mình, tôi nhận ra vấn đề không phải là ở HttpURLConnection mà là Reflection được dùng để tránh những khó khăn trong HttpUrlConnection.

Khó khăn gì?

Với HttpUrlConnection , tôi không thế lấy được request headers được gởi đi.
Có thể thấy URLConnection class có tất cả các request headers:
Thế nhưng nó lại chỉ giữ cho riêng mình.
May thay, Class có hỗ trợ một cơ chế để return lại những request trên với getRequestProperties.
Nhưng khoang đã, có gì đó không đúng!
Hóa ra, getRequestProperties thật sự không hề muốn return lại cho ta nếu có connected, bởi checkConnected sẽ cho rằng đó là một ngoại lệ.
Được lắm!
Kế hoạch mới: Tôi sẽ debug code và xem thử có dùng được cái gì không! Cũng như là tôi sẽ lấy các request ngày sau khi chúng đã set nhưng trước khi tất cả được connected.
Thế những tôi không thể kiếm ra được điểm đó.
Trước dòng code trên, tôi vẫn chưa được connected nên không có truy cập vào request headers được.
Sau  dòng code  đó, request đã được thực hiện và connected nên tôi cũng không thể làm gì được.
Dù đã thử nhiều cách khác nhau nhưng tôi vẫn bị bế tắc và do đó phải dùng tới Reflection.

Tôi đã thât bại ở đâu?

Vốn dĩ đã biết requests field trong URLConnection có thông tin mà tôi cần, tôi chỉ dơn giản dùng reflection để lấy chúng:
Và thế là cả làng đều vui, cho đến khi Java 1.9 xuất hiện.

Tiến về Java 1.9

Sau khi debug, tôi có đọc qua một blog post từ codefx.org. Nó có khá nhiều thông tin hữu ích:
Hóa ra lỗi mà tôi gặp phải trong Java 1.9 nằm gói gọn như sau:
Khá là lạ bởi quá trình import của sun.net.www.MessageHeader không hề có bất cứ lỗi nào. Mãi cho đến compilation.
Khi tôi xem code trong URLConnection, responses field vẫn còn ở đó và vẫn là typesun.net.www.MessageHeader.
Như vậy package đúng là có tồn tại. Thế nhưng với Java 1.9, proprietary packages đã không còn truy cập được vào nữa.
Dù vậy, phương pháp getRequestProperties vẫn có thể dùng để bypass phần kiểm tra của this.checkConnected();
Tôi tin là việc ngăn chặn không cho truy cập của Java hoàn toàn có lí do chính đáng nhưng trong tình huống của tôi thì việc này lại không cần thiết bởi chỉ có một thread, tôi chỉ muốn request và lấy nội dung trong request headers được gửi đi.
Thế nên tôi dùng Reflection để toggle các giá trị trong connected field để checkConnected() không cho rằng đó là một ngoại lệ:
Và nó hoàn toàn trơn tru không chỉ với Java 1.9, mà còn 1.8 và 1.7

Nhưng Java 1.9 không hề khoái Reflection

Java 1.9 sẽ không ngừng thông báo cho tôi biết rằng việc dùng reflective như trên là phạm pháp.
Tôi đoán là do module system mới trong Java 1.9 và mặc dù code của tôi vẫn chạy được nhưng trong tương lai rất có thể nó sẽ bị fail.

Reflection luôn đi kèm với rủi ro

Reflection luôn xuất hiện rủi ro với những dòng code của bạn bị thay đổi ngoài ý muốn.
Và điều đó cũng dễ hiểu bởi tôi không hề có quyền được phép truy cập vào những khu vực đó.
Thế nhưng, khi testing tôi sẽ cần những tính năng mà lại không được hỗ trợ bởi class.
Nếu như “library” có thể hỗ trợ một cách đàng hoàng cho quá trình testing thì tôi đã không gặp phải những vấn đề như trên.
Lí do tôi học reflection trong Java là bởi vì tôi cần phải test nhiều phần khác nhau với các phương thức có thể nhưng ‘frameworks’ lại cho rằng điều đó là không cần thiết và quyết định chặn chúng lại.
Có thể nói đôi khi các tính năng dù với lí do chính đáng nhưng lại vô hình chung gây cản trở cho testing.

Những lựa chọn khác

Tôi có thể thêm HTTP library để tạo requests và cho phép truy cập vào data thô.
Tôi cũng có thể thêm code-based HTTP Proxy, khiến cho tất cả các requests khi được gửi phải qua proxy, và sử dụng chính proxy đó để truy cập vào headers.
Tuy vậy nó lại đi ngược với mục tiêu của tôi: giữ cho code đơn giản nhất có thể. Thế nên tôi quyết định dùng tới những mánh khóe mà mình có thể nghĩ tới.
Do đó với các code đã được tung ra cho cộng đồng thì tôi đã xóa mọi reflection. Tuy vậy, tôi vẫn giữ nó trong phiên bản riêng của mình bởi tôi vẫn có thể quản lí được JVM và cho code chạy bình thường.
Tôi vẫn sẽ tiếp tục dùng reflection để tránh các khó khăn do libraries và frameworks tạo ra tuy nhiên xin lưu ý là cách thức này sẽ kèm theo những rủi ro tiềm tàng nên bạn sẽ phải cẩn thận.

Không có nhận xét nào:

Đăng nhận xét