Writing high-quality code: 151 suggestions for improving Java programs (Chapter 1: generics and reflection ___ suggestion 7th ~ 109), java151
Recommendation 106: Dynamic proxy can make proxy mode more flexible
The reflection framework of Java provides a Dynamic Proxy mechanism that allows you to generate a Proxy for the target class at runtime to avoid repeated development. We know that a static Proxy implements the logic of a topic role (Subject) through the Proxy and Real Subject, the proxy role only delegates the execution logic to a specific role. A simple static proxy is as follows:
Interface Subject {// define a method public void request ();} // a specific topic role class RealSubject implements Subject {// implementation method @ Override public void request () {// implement specific business logic} class Proxy implements Subject {// implement the class private Subject subject = null to Proxy; // default Proxy public Proxy () {subject = new RealSubject ();} // pass the Proxy public Proxy (Subject _ subject) {subject = _ subject;} @ Override public void request () through the constructor () {before (); subject. request (); after ();} // pre-processing private void after () {// doSomething} // post-processing private void before () {// doSomething }}
This is a simple static proxy. Java also provides java. lang. reflect. Proxy for implementing dynamic Proxy: As long as an abstract topic role and a specific topic role are provided, the logic can be dynamically implemented. The instance code is as follows:
Interface Subject {// define a method public void request ();} // a specific topic role class RealSubject implements Subject {// implementation method @ Override public void request () {// implement specific business logic} class SubjectHandler implements InvocationHandler {// The proxy object private Subject subject; public SubjectHandler (Subject _ subject) {subject = _ subject ;} @ Override public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {// preprocessing System. out. println ("preprocessing... "); // directly call the proxy method Object obj = method. invoke (subject, args); // post-processing System. out. println ("post-processing... "); return obj ;}}
Note that there is no proxy topic role here. Instead, SubjectHandler is used as the main logic delegate processing. The invoke method is required by the InvocationHandler definition of the interface, and it completes the call of the real method.
Let's explain in detail the InvocationHandler interface. Dynamic proxy is based on all the methods generated by the interface to be proxy, that is to say, given one or more interfaces, the dynamic proxy will declare that "I have implemented all the methods under this interface". Let's look at how dynamic proxy can implement the methods in this interface? By default, the return values of all methods are empty. Yes, although the proxy has implemented it, it does not have any logical meaning. What should I do? It is easy to do. It is implemented through the implementation class of the InvocationHandler interface. All methods are processed by the Handler, that is, the InvocationHandler takes over the actual processing tasks for all the proxies.
Let's take a look at the dynamic proxy scenario. The Code is as follows:
Public static void main (String [] args) {// specific topic role, that is, Subject subject = new RealSubject (); // Handler InvocationHandler handler = new SubjectHandler (subject); // The current loader ClassLoader cl = subject. getClass (). getClassLoader (); // dynamic proxy Subject Proxy = (Subject) proxy. newProxyInstance (cl, subject. getClass (). getInterfaces (), handler); // execute the proxy method of the specific topic role. request ();}
In this case, you do not need to explicitly create a proxy class to implement the proxy function. For example, you can perform permission judgment before the role to be played by the proxy, or perform data verification after execution.
Dynamic proxy can easily implement common proxy classes. You only need to read persistent data in the InvocationHandler's invoke method, and can also achieve the dynamic cut-in effect, which is also AOP (Aspect Oriented Programming) concept.
Recommendation 107: Use reflection to increase the universality of decorative patterns
Decorator Pattern is defined as "dynamically adding some additional responsibilities to an object. As for the added functions, the decoration mode is more flexible than the subclass generation. However, the dynamic proxy using Java can also achieve the decorative mode, and its flexibility and adaptability will be stronger.
Take the cartoon Tom and Jerry as an example to see how to pack Jerry to make it more powerful. First, we define Jerry's class: Rat class. The Code is as follows:
interface Animal{ public void doStuff();}class Rat implements Animal{ @Override public void doStuff() { System.out.println("Jerry will play with Tom ......"); } }
Next, we will add some capabilities to Jerry, such as flight and ground drilling capabilities. Of course, inheritance is also easy to implement, but here we only temporarily add these capabilities to the Rat class, the decoration mode is more in line with the scenario here. First, define the decoration class. The Code is as follows:
// Define a certain capability interface Feature {// The loading Feature public void load ();} // The Flying capability class FlyFeature implements Feature {@ Override public void load () {System. out. println ("add a pair of wings... ") ;}}// class DigFeature implements Feature {@ Override public void load () {System. out. println ("increases the drilling capability... ");}}
Two capabilities are defined here: one is flight, and the other is drilling ground. If we assign these two attributes to Jerry, we need a packaging category, the Code is as follows:
Class DecorateAnimal implements Animal {// The encapsulated Animal private animal Animal; // which package private Class is used <? Extends Feature> clz; public DecorateAnimal (Animal _ animal, Class <? Extends Feature> _ clz) {animal = _ animal; clz = _ clz;} @ Override public void doStuff () {InvocationHandler handler = new InvocationHandler () {// specific packaging behavior @ Override public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {Object obj = null; if (Modifier. isPublic (method. getModifiers () {obj = method. invoke (clz. newInstance (), args);} animal. doStuff (); return obj ;}}; // ClassLoader cl = getClass (). getClassLoader (); // dynamic proxy, and handler decides how to wrap Feature Proxy = (Feature) proxy. newProxyInstance (cl, clz. getInterfaces (), handler); proxy. load ();}}
Pay attention to the doStuff method. A decoration type must be a child type of the abstract build (Component). It must implement the doStuff method. The doStuff method here is delegated to the dynamic proxy for execution, in addition, the Controller Handler of the dynamic proxy sets the conditions for determining the decoration method and behavior (that is, the if judgment statement in the InvocationHandler Anonymous class in the Code). Of course, you can also read the persistent data to make judgments. This makes it more flexible.
After the abstract construction is complete, the decoration class is complete, and the decoration class is complete, we can compile the client to call the function. The Code is as follows:
Public static void main (String [] args) {// defines Jerry, the mouse Animal jerry = new Rat (); // increases the flight capability for Jerry jerry = new DecorateAnimal (jerry, flyFeature. class); // jerry increases the mining capability. jerry = new DecorateAnimal (jerry, DigFeature. class); // Jerry started to tease jerry. doStuff ();}
This type of code is only a common decoration mode. You only need to define the decoration class and decoration class. The decoration behavior is implemented by the dynamic proxy, which completely decouples the decoration class and the decoration class, provides system scalability.
Recommendation 108: reflection makes the template method more powerful
The Template Method Pattern Defines the algorithm skeleton in an operation and delays some steps to the subclass, so that the subclass does not change the structure of an algorithm to redefine certain steps of the algorithm. Simply put, the parent class defines the abstract template as the skeleton, including the basic method (implemented by the subclass and called in the template method) and the template method (implementing the scheduling of basic methods and completing the fixed logic), it uses a simple inheritance and overwriting mechanism. Let me look at a basic example.
We often develop some testing or demo programs. We expect the system to automatically initialize at startup to facilitate testing or explanation. The general practice is to write an SQL file and import it automatically before the system starts, however, this is not only troublesome but prone to errors, so we wrote a framework for automatic data initialization: Initialize data when the system (or container) is automatically started. But the problem is that we do not know the content to be initialized for each application. We can only write the content by the implementer, so we must reserve an interface for the author. Now we have to consider using the template method mode, the Code is as follows:
1 public abstract class AbsPopulator {2 // template method 3 public final void dataInitialing () throws Exception {4 // call the basic method 5 doInit (); 6} 7 8 // Basic Method 9 protected abstract void doInit (); 10}
An abstract template class AbsPopulator is defined here. It is responsible for data initialization, but the specific data to be initialized is determined by the doInit method. This is an abstract method and must be implemented by sub-classes, let's take a look at the loading of User table data:
Public class UserPopulator extends AbsPopulator {@ Override protected void doInit () {// initialize the user table, such as creating and loading data }}
The system searches for all AbsPopulator implementation classes at startup, and then dataInitialing initializes the data. Then you may think about how to instruct the AbsPopulator class in containers? If Spring is used as the project of the Ioc container, the @ PostConstruct annotation is directly added to the dataInitialing method. After the Spring container is started, the dataInitialing method is automatically run. Let's take a look at spring-related knowledge. I will not go into details here.
The problem is: initializing a User table requires a lot of operations, such as creating a table first, filtering data, inserting data, and finally verifying, if you put all these into a doInit method, it will be very huge (even if multiple methods are extracted to assume different responsibilities, the code is still poorly readable), what should we do? Or does doInit have no meaning or significance. Can it be an elegant and beautiful name?
The answer is that we can use the reflection enhancement template method mode to enable the template method to call basic methods with fixed rules. Code is the best communication language. Let's look at how to transform the AbsPopulator class. The Code is as follows:
Public abstract class AbsPopulator {// template Method public final void dataInitialing () throws Exception {// obtain all public methods Method [] methods = getClass (). getMethods (); for (Method m: methods) {// determine whether it is the data initialization Method if (isInitDataMethod (m) {m. invoke (this) ;}}// determines whether it is a data initialization Method. The basic Method validator is private boolean isInitDataMethod (Method m) {return m. getName (). startsWith ("init") // start init & Modifier. isPublic (m. getModifie Rs () // public method & m. getReturnType (). equals (Void. TYPE) // the return value is void &&! M. isVarArgs () // The output parameter is null &&! Modifier. isAbstract (m. getModifiers (); // cannot be an abstract method }}
In the general template method mode, abstract templates (here is the AbsPopulator class) need to define a series of basic methods, generally at the protected access level, and are abstract methods, this indicates that the subclass must implement these basic methods, which is both a constraint and a burden for the subclass. However, after reflection is used, you do not need to define any abstract methods. You only need to define a basic method validator (isInitDataMethod In the example) to load the basic methods that comply with the rules. The role of the authenticator here is to identify the basic methods in the subclass method. The template method (dataInitaling In the example) requires the basic method authenticator to return results and execute corresponding methods through reflection.
At this point, if you need to perform a lot of initialization work, the implementation of subclass is very simple, the Code is as follows:
Public class UserPopulator extends AbsPopulator {public void initUser () {/* initialize the user table, such as creating and loading data */} public void initPassword () {/* initialization password */} public void initJobs () {/* initialization task */}}
Methods In the UserPopulator class are called by the template method as long as they meet the basic method authentication conditions, and the data volume of the method is no longer restricted by the parent class, this function allows you to flexibly define basic methods and batch calls of parent classes, and reduces the amount of code in child classes.
If you are familiar with JUnit, we will see that the implementation here is very similar to JUnit. JUnit4 requires that the method name to be tested must start with test and have no return value or parameter, in addition, it is a public modifier, and its implementation principle is very similar. If you are interested, please refer to the source code of Junit.
Suggestion 109: do not pay too much attention to Reflection Efficiency
Reflection Efficiency is a common problem. experienced developers often use this sentence to intimidate new users: reflection efficiency is very low, so they should not use it unless they have. In fact, the first half of this sentence is correct, and the last half is wrong.
Reflection efficiency is much lower than normal code execution, but it is a very effective runtime tool class. As long as the code structure is clear and readable, it should be developed first, it is not too late to modify the performance when performance testing proves that there is a problem here (in general, reflection is not the ultimate killer of performance, however, chaotic code structures and poor readability may cause potential performance risks ). Let's look at this example to get the generic type of the generic class at runtime. The Code is as follows:
Class Utils {// obtain the actual generic Type public static of a generic Class <T> getGenricClassType (class clz) {type Type = clz. getGenericSuperclass (); if (type instanceof ParameterizedType) {ParameterizedType pt = (ParameterizedType) type; Type [] types = pt. getActualTypeArguments (); if (types. length> 0 & types [0] instanceof Class) {// if multiple generic parameters exist, return (Class <T>) based on the Location index) types [0] ;}}return (Class <T>) Object. class ;}}
As we have mentioned earlier, Java generics only exist in the compiler. Why can this tool class obtain runtime generics? This is because the tool only supports inherited generic classes. If you have determined the type parameters of the generic classes during Java compilation, you can certainly obtain them through the generic classes. For example, there is a generic class:
Abstract class BaseDao <T >{// obtain the type private Class in the T runtime <T> clz = Utils. getGenricClassType (getClass (); // obtain a record public void get (long id) {session based on the primary key. get (clz, id) ;}/// operation user table class UserDao extends BaseDao <String> {}
For the UserDao class, the compiler has clarified that its parameter type is String at compilation, so we can obtain its type through reflection. This is also the use case of the getGenricClassType method.
BaseDao and UserDao are regular customers in ORM. BaseDao implements basic database operations, such as adding, deleting, modifying, and querying, while UserDao is a specific database operation, it is used to operate the User table. If BaseDao can provide enough basic methods, such as adding, deleting, modifying, and querying a single table, which BaseDao sub-classes similar to UserDao can save a lot of development work. However, the problem is that the session object on the persistence layer (The Hibernate Session is simulated here) needs to specify a specific type for the operation. For example, for get query, two parameters need to be obtained: the object type (used to determine the ing data table) and the primary key are easy to handle. The problem is how to obtain the object type?
? It is troublesome and prone to errors.
Read configuration problems? Feasible, but inefficient.
The best way is to define the generics of the parent class, define the generic parameters of the subclass, and then read the corresponding type through reflection, so we have the clz variable in our code: obtain the generic type through reflection. After this implementation, UserDao does not need to define any methods. The inherited parent class operation methods have already met the basic requirements. This code structure is clear and readable.
Think about it. If the reflection efficiency problem is considered, there is no clz variable and reflection is not used, each BaseDao subclass must implement a query operation, and the code will be repeated in large quantities, this violates the most basic encoding rule "Don't Repeat Yourself", which increases the difficulty of Project reconstruction and optimization and increases the complexity of code.
Do not optimize or anticipate reflection efficiency issues in advance. This is basically worrying, there are few projects that cause system efficiency failures due to reflection issues (unless the spam code is copied), and according to the 80% principle, 20% of the performance is consumed on of the Code, this 20% of the Code is the focus of our attention. do not focus on reflection alone.
Note: low reflection efficiency is a true proposition, but it is a false proposition because it is not used.