AOP-oriented tangent programming
AOP (aspect-oriented programming, aspect-oriented programming), is a technology that can dynamically and uniformly add functionality to a program without modifying the source code, through precompilation and runtime dynamic proxy implementations. It is a new methodology that complements traditional OOP programming.
OOP is concerned with dividing the requirements function into different and relatively independent, well-encapsulated classes, and letting them have their own behavior, relying on inheritance and polymorphism to define each other's relationships; AOP is the ability to separate common requirements functions from unrelated classes, enabling many classes to share a single behavior, once changed , you do not have to modify many classes, but you only need to modify this behavior.
AOP is the use of facets (aspect) to modularize crosscutting concerns, and OOP is the use of classes to modularize state and behavior. In the world of OOP, programs are organized through classes and interfaces, and it is appropriate to use them to implement the core business logic of the program. However, it is difficult to achieve crosscutting concerns (functional requirements spanning multiple modules of the application), such as logging and validation.
/* Calculator Interface */
public interface Calculator
{
Public double Add (double NUM1, double num2) throws Exception;
Public double sub (double NUM1, double num2) throws Exception;
Public double div (double num1, double num2) throws Exception;
Public double Mul (double num1, double num2) throws Exception;
}
/* The implementation class for the calculator interface */
public class Arithmeticcalculator implements Calculator
{
@Override
Public double Add (double NUM1, double num2)
{
Double result = Num1 + num2;
return result;
}
@Override
Public double sub (double NUM1, double num2)
{
Double result = num1-num2;
return result;
}
/* The code temporarily does not consider the case of divisor 0 */
@Override
Public double div (double num1, double num2)
{
Double result = num1/num2;
return result;
}
@Override
Public double Mul (double num1, double num2)
{
Double result = NUM1 * NUM2;
return result;
}
}
Most applications have a common need to keep track of what is happening while the program is running. In order to add log functionality to the computer, the Arithmeticcalculator class changes as follows:
/* The implementation class for the Calculator interface, adding the logging function */
public class Arithmeticcalculator implements Calculator
{
@Override
Public double Add (double NUM1, double num2)
{
System.out.println ("The method [Add ()]" + "begin with Args (" +num1+ "," +num2+ ")");
Double result = Num1 + num2;
System.out.println ("The method [Add ()]" + "End With result (" +result+ ")");
return result;
}
@Override
Public double sub (double NUM1, double num2)
{
System.out.println ("The method [sub ()]" + "begin with Args (" +num1+ "," +num2+ ")");
Double result = num1-num2;
System.out.println ("The method [sub ()]" + "End With result (" +result+ ")");
return result;
}
/* The code temporarily does not consider the case of divisor 0 */
@Override
Public double div (double num1, double num2)
{
System.out.println ("The method [Div ()]" + "begin with Args (" +num1+ "," +num2+ ")");
Double result = num1/num2;
System.out.println ("The method [Div ()]" + "End With result (" +result+ ")");
return result;
}
@Override
Public double Mul (double num1, double num2)
{
System.out.println ("The method [Mul ()]" + "begin with Args (" +num1+ "," +num2+ ")");
Double result = NUM1 * NUM2;
System.out.println ("The method [Mul ()]" + "End With result (" +result+ ")");
return result;
}
}
If the Arithmeticcalculator rule can only calculate positive numbers, you also need to add a parameter validation method:
/* The implementation class for the Calculator interface, adding the logging function */
public class Arithmeticcalculator implements Calculator
{
@Override
Public double Add (double NUM1, double num2) throws Exception
{
This.argsvalidatior (NUM1);
This.argsvalidatior (NUM2);
/* Ditto */
}
@Override
Public double sub (double NUM1, double num2) throws Exception
{
This.argsvalidatior (NUM1);
This.argsvalidatior (NUM2);
/* Ditto */
}
/* The code temporarily does not consider the case of divisor 0 */
@Override
Public double div (double num1, double num2) throws Exception
{
This.argsvalidatior (NUM1);
This.argsvalidatior (NUM2);
/* Ditto */
}
@Override
Public double Mul (double num1, double num2) throws Exception
{
This.argsvalidatior (NUM1);
This.argsvalidatior (NUM2);
/* Ditto */
}
private void Argsvalidatior (double arg) throws Exception
{
if (Arg < 0)
throw new Exception ("parameter cannot be negative");
}
}
One of the most intuitive features of the above program is that there are a lot of repetitive code, and when adding more and more non-business requirements (such as logging and parameter Validation), the original calculator method becomes bloated and lengthy. There is a very painful thing to do, and it is not possible to use the original programming methods to get them out of the core business. For example, logging and parameter validation, AOP calls them crosscutting concerns (crosscutting concern), and their system-wide requirements often need to span multiple modules.
In the case of modular crosscutting concerns that cannot be idealized with traditional object-oriented programming, programmers cannot help but be able to place these crosscutting concerns in each of the modules intertwined with the core logic, which will cause crosscutting concerns to exist everywhere in each module. Using a non-modular approach to achieve crosscutting concerns will result in code clutter, code fragmentation, and code duplication. Think about it. If the log records need to be displayed in a different way, how much code you want to change, and if you miss one (high probability), it will cause the log record to be inconsistent. This kind of code is very maintainable. A variety of reasons indicate that the module needs only to focus on its own functional requirements and need a way to extract the crosscutting focus punch module.
The endurance of the Daniel proposed AOP, it is a concept, a specification, itself does not set specific language implementation, it is this feature makes it very popular, now there are many open-source AOP implementation framework. This is not an introduction to these frameworks, we will not use these frameworks, but instead use the underlying code to implement the most basic AOP to solve the above example problems. AOP is actually a continuation of the GOF design pattern, and the design pattern pursues the decoupling between the caller and the callee, and AOP can be said to be an implementation of this goal. AOP can be implemented using the "proxy mode".
The principle of proxy mode is to use a proxy to wrap the object, and then replace the original object with the proxy object, and any calls to the original object go through the proxy first. The proxy object is responsible for deciding whether and when to forward the method call information to the original object. At the same time, the proxy object can perform some extra work around the invocation of each method. You can see that the proxy mode is ideal for implementing crosscutting concerns.
Because I only know Java, so I think there are two ways to implement the proxy mode, one is static proxy, and the other is a dynamic agent. The difference is that they know who the agent is at compile time. In the more modular system, the static proxy is not appropriate and very inefficient, because the static proxy needs to specifically for each interface to design a proxy class, the system is relatively large and hundreds of interfaces is normal, static proxy mode is too labor-intensive. The dynamic agent is the proxy mode supported by the JDK, and it can implement the crosscutting concerns very well.
/* Use dynamic Proxy to implement Invocationhandler interface */
public class Arithmeticcalculatorinvocationhandler implements Invocationhandler
{
/* The object to be proxied, the dynamic agent only knows who the agent is at run time, so it is defined as object type, can proxy arbitrary object */
Private Object target = null;
/* Pass in the original object through the constructor */
Public Arithmeticcalculatorinvocationhandler (Object target)
{
This.target = target;
}
/*invocationhandler interface method, proxy represents the agent, method represents the original object is called methods, args represents the parameters of the method */
@Override
public object invoke (object proxy, Method method, object[] args)
Throws Throwable
{
/* Process log information before the original object method call */
System.out.println ("The Method [" +method.getname () + "]" + "begin with Args (" +arrays.tostring (args) + ")");
Object result = Method.invoke (This.target, args);
/* Process log information After the original object method call */
System.out.println ("The Method [" +method.getname () + "]" + "End With result (" +result+ ")");
return result;
}
/* Get proxy class */
Public Object GetProxy ()
{
Return Proxy.newproxyinstance (This.target.getClass (). getClassLoader (), This.getclass (). Getinterfaces (), this);
}
}
Scenario Class Invocation:
public class Client
{
public static void Main (string[] args) throws Exception
{
/* Get Agent */
Calculator arithmeticcalculatorproxy = (Calculator) New Arithmeticcalculatorinvocationhandler (
New Arithmeticcalculator ()). GetProxy ();
/* Call the Add method */
Arithmeticcalculatorproxy.add (10, 10);
}
}
Output from the console:
The method [Add]begin with Args ([10.0, 10.0])
The method [Add]end with result (20.0)
You can see that cross-cutting concerns are implemented using dynamic proxies.
If you need to add a parameter verification function, you only need to create a parameter Validation agent:
public class Arithmeticcalculatorargsinvocationhandler implements
Invocationhandler
{
/* The object to be proxied, the dynamic agent only knows who the agent is at run time, so it is defined as object type, can proxy arbitrary object */
Private Object target = null;
/* Pass in the original object through the constructor */
Public Arithmeticcalculatorargsinvocationhandler (Object target)
{
This.target = target;
}
/*invocationhandler interface method, proxy represents the agent, method represents the original object is called methods, args represents the parameters of the method */
@Override
public object invoke (object proxy, Method method, object[] args)
Throws Throwable
{
SYSTEM.OUT.PRINTLN ("Begin valid method [" +method.getname () + "] with args" +arrays.tostring (args));
for (Object Arg:args)
{
This.argvalidtor ((Double) arg);
}
Object result = Method.invoke (This.target, args);
return result;
}
/* Get proxy class */
Public Object GetProxy ()
{
Return Proxy.newproxyinstance (This.target.getClass (). getClassLoader (), This.target.getClass (). Getinterfaces (), this);
}
private void Argvalidtor (double arg) throws Exception
{
if (Arg < 0)
throw new Exception ("argument cannot be negative! ");
}
}
Scenario Class Invocation:
public class Client
{
public static void Main (string[] args) throws Exception
{
/* Get Agent */
Calculator arithmeticcalculatorproxy = (Calculator) New Arithmeticcalculatorinvocationhandler (
New Arithmeticcalculator ()). GetProxy ();
Calculator argvalidatorproxy = (Calculator) New Arithmeticcalculatorargsinvocationhandler (Arithmeticcalculatorproxy ). GetProxy ();
/* Call the Add method */
Argvalidatorproxy.add (10, 10);
}
}
Console output:
Begin valid method [add] with args [10.0, 10.0]
The method [Add]begin with Args ([10.0, 10.0])
The method [Add]end with result (20.0)
Enter a negative data:
public class Client
{
public static void Main (string[] args) throws Exception
{
/* Get Agent */
Calculator arithmeticcalculatorproxy = (Calculator) New Arithmeticcalculatorinvocationhandler (
New Arithmeticcalculator ()). GetProxy ();
Calculator argvalidatorproxy = (Calculator) New Arithmeticcalculatorargsinvocationhandler (Arithmeticcalculatorproxy ). GetProxy ();
/* Call the Add method */
Argvalidatorproxy.add (-10, 10);
}
}
Console output:
Begin valid method [add] with args [-10.0, 10.0]
Exception in thread "main" java.lang.Exception: parameter cannot be negative!
At Com.beliefbetrayal.aop.ArithmeticCalculatorArgsInvocationHandler.argValidtor ( ARITHMETICCALCULATORARGSINVOCATIONHANDLER.JAVA:46)
At Com.beliefbetrayal.aop.ArithmeticCalculatorArgsInvocationHandler.invoke ( ARITHMETICCALCULATORARGSINVOCATIONHANDLER.JAVA:29)
At $Proxy 0.add (Unknown Source)
At Com.beliefbetrayal.aop.Client.main (client.java:14)
Do not know if you have used Struts2, this structure and Struts2 interceptor very similar, one action object is like our original object business core, one interceptor is like here agent, universal function to implement as an interceptor, so that action can be shared, Struts2 interceptors are also an excellent implementation of AOP.
AOP-oriented tangent programming