preface

These two days review Retrofit source code, saw some design patterns, usually write business logic more or less will use, a simple record ^^- ^^

directory

One, general agent

Define an interface IGamePlayer for all players who like online games, and then define a specific implementation class GamePlayer that implements the actions that each gamer needs to perform in order to play the game.

public interface IGamePlayer{ public void login(String user,String pwd); Public void killBoss(); Public class GamePlayer implements IGamePlayer{private String name = ""; public GamePlayer(String name){ this.name = name; } public void login(String user,String PWD){public void killBoss(){// killBoss}}Copy the code

Players fight monsters to upgrade

IGamePlayer player = new GamePlayer(IGamePlayer); Player.login (" login "," password "); player.killBoss();Copy the code

At this time the player tired, to find a boost

public class GamePlayerProxy implements IGamePlayer{ private IGamePlayer gamePlayer = null; public GamePlayerProxy(IGamePlayer gamePlayer){ this.gamePlayer = gamePlayer; } public void login(String user,String password){ this.gamePlayer.login(user,password); } public void killBoss(){ this.gamePlayer.killBoss(); }}Copy the code

After finding the boost, give him account number, password, boost also want to know clearly is who wants me to help him hit strange, and then start

IGamePlayer player = new GamePlayer(IGamePlayer); IGamePlayer proxy = new GamePlayerProxy(player); proxy.login("user","pwd"); proxy.killBoss();Copy the code

Agent of the real player role, responsible for the real account, the role of the application. The actual result is on the player’s account, which can be interpreted as the player. This kind of you know this promotion, promotion also know you, substitute fight you also rest assured. Here is how to make the promotion also know you? Specify the actual player character through the constructor.

One of the benefits is to lighten your load. This is also the advantage of the agency model. But it says here, a bit of a problem, every time you find an agent to play games, do you have to warm up beforehand, and then give it to the agent? In this case, the high level business logic, the caller, knows who the real character is. To make the high level business influence the real character, we can change the constructor of GamePlayerProxy to pass the account and password. And then internally new GamePlayer.

Ii. Compulsory agency

The forced agent is quite different, usually through the agent to find the real role, but the forced agent must be through the real role to find the agent role, just like when you go to the star cooperation, the star will give the agent, that is, the agent information to you, let you communicate, the forced agent is similar.

public interface IGamePlayer{ public void login(String user,String pwd); Public void killBoss(); Public IGamePlayer getProxy(); } public class GamePlayer implements IGamePlayer{private String name = ""; private IGamePlayer proxy = null; Public GamePlayer(String name){this.name = name; } public IGamePlayer getProxy(){ this.proxy = new GamePlayerProxy(this); return this.proxy; } public void login(String user,String PWD){if(this.isProxy()){if(this.isProxy()){else{// Please find my proxy}} public void login(String user,String PWD){ If (this.isProxy()){}else{// proxy failed}} private Boolean isProxy(){return this.proxy == null; }}Copy the code

In fact, it is almost the same as before the general agent, the only difference is to do the role restriction, the management of the agent is completed by the real role.

IGamePlayer player = new GamePlayer(IGamePlayer); IGamePlayer proxy = player.getProxy(); proxy.login("user","pwd"); proxy.killBoss();Copy the code

Three, dynamic proxy and source code parsing

Public class GamePlayIH implements InvocationHandler{// Class CLS = null; Object obj = null; Public GamePlayIH(Object obj){this.obj = obj; } public Object invoke(Object proxy,Mehtod method,Object[] args){Object result = method.invoke(this.obj,args); If (method.getname ().equals("login")){return result; if(method.getname ().equals("login")); } // Call IGamePlayer palyer = new GamePlayer(" three "); InvocationHandler handler = new GamePlayIH(player); IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(palyer.getClass().getClassLoader(),new Class[]{IGamePlayer.class},handler);Copy the code

Dynamic proxy is used. How does the underlying implementation work? Proxy The source code for creating a Proxy is as follows

@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<? >[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm ! = null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* *1\. Generate proxy Class object */ Class<? > cl = getProxyClass0(loader, intfs); . final Constructor<? > cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (! Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {  cons.setAccessible(true); return null; }}); Return cons. NewInstance (new Object[]{h}); . }Copy the code

GetProxyClass0 generates a proxy Class object and returns the proxy Class object. **getProxyClass0

private static Class<? > getProxyClass0(ClassLoader loader, Class<? >... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } return proxyClassCache.get(loader, interfaces); }Copy the code

ProxyClassCache is an object of type WeachCache and initialized as follows

private static final WeakCache<ClassLoader, Class<? >[], Class<? >> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());Copy the code

You can follow up with the GET method to see if it is fetching data

Public V get(K key, P parameter) {public V get(K key, P parameter) {public V get(K key, P parameter) { expungeStaleEntries(); Object CacheKey = cacheKey.valueof (key, refQueue); // Obtain the key-value pair valuesMap from cacheKey. The key of valuesMap is a wrapper class for the list of interfaces. ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap ! = null) { valuesMap = oldValuesMap; SubKey = object.requirenonNULL (subkeyFactory.apply (key, parameter)); // Supplier<V> Supplier = valuesmap.get (subKey); // Supplier<V> Supplier = valuesmap.get (subKey); // Create dynamic proxy class Factory Factory = null; while (true) { if (supplier ! = null) {// Notice here that supplier is created even if supplier is empty. V value = supplier.get(); if (value ! = null) { return value; } } if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // successfully installed Factory supplier = factory; } // else retry with winning supplier } else { ..... }}}Copy the code

You can see that Factory’s get method is called inside the get method

@Override public synchronized V get() { // serialize access // re-check Supplier<V> supplier = valuesMap.get(subKey); if (supplier ! = this) { return null; } // else still us (supplier == this) // create new value V value = null; Value = objects.requirenonNULL (valuefactory.apply (key, parameter)); // Create a dynamic proxy class with the apply method of valueFactory. } finally { if (value == null) { // remove us on failure valuesMap.remove(subKey, this); }}... return value; }}Copy the code

The get method of the Factory is used to generate the dynamic proxy class using the apply method of the ProxyClassFactory

@Override public Class<? > apply(ClassLoader loader, Class<? >[] interfaces) { Map<Class<? >, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); //1. Obtain the Class object of the proxy Class interface and verify whether the interface for (Class<? > intf : interfaces) { Class<? > interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass ! = intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); }... } //2. The package name of the proxy class to be generated String proxyPkg = null; / / package to define the proxy class in / / class Modifier int accessFlags = Modifier. The PUBLIC | Modifier. The FINAL; for (Class<? Intf: interfaces) {int flags = intF.getModifiers (); if (! Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (! pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } / / 4 \. Prepare the proxy class name of the class long num = nextUniqueNumber. GetAndIncrement (); String proxyName = proxyPkg + proxyClassNamePrefix + num; / / 5. Generated bytecode file byte [] proxyClassFile = ProxyGenerator. GenerateProxyClass (proxyName, interfaces, accessFlags); Native method return defineClass0(Loader, proxyName, proxyClassFile, 0, proxyClassfile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); }}Copy the code

The main method is the ProxyGenerator in the above code. GenerateProxyClass () and defineClass0() methods, one is generated according to the class name, interface, class access, according to the class file format byte stream, the other is generated according to the byte stream proxy class object.

Dynamic proxy advantages:

Methods are enhanced without modifying the source code, and a new class object is created in the process

Disadvantages of dynamic proxy:

Sometimes we write classes where there may be many internal methods that call each other, but if I want to enhance them simultaneously, I can’t do that with a dynamic proxy. Because dynamic proxies enhance only the methods that are called first, subsequent methods that are called internally to each other are not enhanced.

Such as

public Object invoke(Object proxy,Mehtod method,Object[] args){ Object result = method.invoke(this.obj,args); If (method.getName().equals("login")){else if(method.getName().equals("killBoss")){else if(method.getName().equals("killBoss")){} return result; } public void login(String user,String pwd){ if(this.isProxy()){ killBoss(); }else{// proxy failed}}Copy the code

Dynamic proxies do not enhance the killBoss() method if the killBoss() method is called within the login method during the enhancement

Fourth, the role and application scenarios of the proxy mode

Now add a requirement to add validation or print capabilities before and after all class methods. What would you do? Definitely take advantage of the proxy pattern, add a proxy class to an existing class, implement before-and-after printing in the proxy class, that’s what it does.

In the proxy pattern, some functions can be extended by extending the proxy class without modifying the code of the proxy object. For dynamic proxies, only the InvocationHandler extension of the proxy can be implemented.

The proxy pattern (in addition to the DYNAMIC proxy provided by the JDK, CGLB’s DYNAMIC proxy based on ASM technology) is commonly seen in AOP aspect programming, such as ASpectJ.