The above two articles talk about handwritten IOC and DI. This article is about handwritten AOP. Because the code written in the above two articles is used in the content, the link is posted here:
Handwritten Spring IOC container: Click to enter
Handwriting Spring DI dependency Injection: Click to enter
AOP analysis
What is the AOP
Aspect Oriented Programming To enhance the methods of a class without changing the code of the class
So if you want to implement an AOP, all you need to do is provide AOP functionality to the users that use it, and be able to enhance the methods of the class through AOP technology implementations
Elements in AOP
- Advice, which is enhanced functionality
- Join points, which methods are available for enhancement
- Pointcut pointcuts that select method points to cut into, that is, which methods to enhance
- Aspact facets, multiple method points for selection + enhanced functionality, i.e. a combination of Advice and Pointcut
- Introduction Adding new methods or attributes to an existing class is called Introduction
- Weaving in means not changing the original code and adding enhanced functionality
For handwriting AOP, some of the above elements need to be provided by the user: Advice, Pointcut, Aspact, users need to provide enhanced functionality and Pointcut, aspect information; AOP frameworks need to provide Join points, Weaving
The functionality provided by AOP
- Enhancements are required, namely Advice notifications
- To enhance a class’s methods, you need an optional method point to enhance, the Pointcut
- Weaving in requires enhancement of functionality without changing the original class code
The illustration of AOP
Suppose we now play a game of King of Glory and choose a hero, Lu Ban, who needs to play the entire game to the end, regardless of whether he hangs up. At the beginning, there was no equipment. Suppose that each part of Lu Ban’s body was Join points, that is, these parts needed equipment to enhance them, and then each piece of equipment was the function enhanced by Advice, such as increasing attack speed and health, etc. Now when you buy a shoe, you choose to enhance the speed, that is, wear it on your feet. So that’s a Pointcut, and then the shoe plus the foot is an Aspact cut.
Characteristics analysis
- Advice
To realize an Advice, we need to know the characteristics of Advice. From the above we can know that Advice is provided by users, so there are many uncontrollable factors
- User: Enhanced functionality is provided by the user, that is, the code for enhanced functionality is written by the user
- Variability: Since the user provides, the enhanced logic is different for different requirements
- Optional timing: The user can choose to perform enhancements before, after, or during an exception
- Multiplicity: The same pointcut, that is, the same method, can have multiple enhancements, more than once
- Pointcut
Pointcuts, which know from above that they are also user-provided, are basically similar in character to Advice
- User-friendliness: Users specify which methods are enhanced
- Variability: Users can specify it flexibly
- Multipoint: Users can choose to enhance functionality at multiple method points
- Weaving
Weaving, the part of the code that needs to be provided by the framework, is the logic that we need to implement ourselves, through which we can add enhanced functionality to user-specified methods
- Non-invasive: Do not change the code of the original class to achieve functional enhancements
- We need to implement the logic ourselves in an AOP framework
Advice, Pointcut and Weaving are the three parts that need to be done. Why not implement Join points? These parts can be known during code writing
Advice to implement
Advice is implemented by the user, and this part of the logic needs to be written by the user and used when we implement AOP; We need to know the user’s stuff, the user needs to use the framework we write, and we need to insulate the user from variability
So what you need to do, you can define a standard interface, and the user can implement the interface to provide different enhancement logic, and that’s the way to deal with change, programming to the interface
Defining Advice Interface
Advice is a top-level interface that does not need to write any methods, and then implements the Advice interface according to the pre-position, post-position, surround, and exception enhancement parameters
Advice interface:
/ * * *@className: Advice
* @description: standard interface for notifications *@author TR
*/
public interface Advice {}Copy the code
First, we know that an enhancement is an enhancement to a method, so when you use Advice, all you need to give is some information about the method
- Lead to enhance
If the method is enhanced before execution, you know that the pre-enhancement does not require a return value. The following parameters are required:
- Method itself
- Method Object[]
- The class (Object) of the method
- The rear enhancement
The return value of the method is required, because if I need to do some processing on the return value, I need to use it, and post enhancement also does not need to return value, the required parameters are as follows:
- Method itself
- Method Object[]
- The class (Object) of the method
- The return value of the Object method
- Surrounding the enhancement
The return value of the enhanced method is the return value of the enhanced method. The parameters required are as follows:
- Method itself
- Method Object[]
- The class (Object) of the method
- Abnormal enhancement
Capture the exception information at method execution and then enhance it, and it also needs to be enhanced by wrapping methods, which can be implemented in wrap enhancement
Through the above know, need to define three methods: enhanced front, rear enhanced, surround and abnormal enhancement method, that the three methods are defined in an interface, or three interface, according to the single responsibility principle, or three interface to achieve better, but also can according to different interface type to distinguish between what is Advice
Define front, back, surround, and exception enhancement interfaces
Define the pre-enhanced interface MethodBeforeAdvice:
/ * * *@className: MethodBeforeAdvice
* @description: Front-end enhanced interface *@author TR
*/
public interface MethodBeforeAdvice extends Advice {
/** ** pre-enhanced method *@paramMethod: The method to be executed *@paramTarget: the target object on which the method is executed@paramArgs: arguments to the execution method *@return: void
**/
void before(Method method, Object target, Object[] args);
}
Copy the code
Define AfterReturningAdvice:
/ * * *@className: AfterReturningAdvice
* @description: Rear enhanced interface *@author TR
*/
public interface AfterReturningAdvice extends Advice {
/** * post-enhancement method *@paramMethod: The method to be executed *@paramTarget: the target object on which the method is executed@paramArgs: arguments to the execution method *@paramReturnValue: the returnValue * of the execution method@return: void
**/
void after(Method method, Object target, Object[] args, Object returnValue);
}
Copy the code
Define the surround, exception-enhanced interface MethodInterceptor:
/ * * *@className: MethodInterceptor
* @description: Surround, exception enhancement interface *@author TR
*/
public interface MethodInterceptor extends Advice {
/** * surround, exception-enhanced methods, in the method implementation need to call the target method *@paramMethod: The method to be executed *@paramTarget: the target object on which the method is executed@paramArgs: arguments to the execution method *@return: Java.lang. Object The return value of the execution method *@throws Throwable
**/
Object invoke(Method method, Object target, Object[] args) throws Throwable;
}
Copy the code
The class diagram is as follows:
Pointcut implementation
Pointcut features:
- User-friendliness: Users specify which methods are enhanced
- Variability: Users can specify it flexibly
- Multipoint: Users can choose to enhance functionality at multiple method points
We need to give the user something that gives them the flexibility to specify method points, and when we get it, we know which method points they specified
Specify which methods to enhance, what information to specify, which is actually one or more methods, and if there’s an overload, so this thing that you specify, is a complete method signature, right
The specified information can describe a class of methods, such as:
- A method of a class in a package
- All methods of a class under a package
- Methods that start with GET in all classes under a package
- Methods that start with get and end with sevice in all classes under a package
- Methods that start with GET and end with sevice in all classes under a package and its subpackages
You can then define an expression to describe the above information, which is the package name. Class name. Method name (parameter type)
How does each of these pieces require it?
- Package name: must have parent-child characteristics, can be fuzzy matching
- Class name: Can be fuzzy match
- Method name: Fuzzy matching is possible
- Parameter types: There can be multiple parameters
The expression defined is needed to determine whether a method of a class needs to be enhanced, so it needs to match the class and the matching method
Matching expressions:
- Regular expression
- AspactJ expression
AspactJ expression learning
/ * * *@className: AspectJTest
* @description: AspectJ test class */
public class AspectJTest {
public static void main(String[] args) throws NoSuchMethodException {
// Get the pointcut resolver
PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
// The pointcut parser resolves a type matcher according to the rules
TypePatternMatcher tp = pp.parseTypePattern("di.*");
// Generate a pointcut expression from the expression
PointcutExpression pe = pp.parsePointcutExpression("execution(* demo.beans.BeanFactory.get*(..) )");
// Matches MagicGril's getName methodClass<? > cl = MagicGril.class; Method method = cl.getMethod("getName".null);
// Match method execution
ShadowMatch sm = pe.matchesMethodExecution(method);
System.out.println("Match method:" + sm.alwaysMatches());
System.out.println(cl.getName() + ", match expression:" + pe.couldMatchJoinPointsInType(cl));
System.out.println(DefaultBeanFactory.class + ", match expression:" + pe.couldMatchJoinPointsInType(DefaultBeanFactory.class));
System.out.println(cl.getName() +"All methods under class:");
for(Method method1 : cl.getMethods()) { System.out.println(method1.getName()); }}}Copy the code
Output result:
From the above description, we can know, how to design the tangent point
The properties that pointcuts need are pointcuts expressions, and the functions that need to be provided are matching classes and matching methods
And we could define a top-level interface like Advice, because if there are other expressions that are more useful in the future and need to be extended, then we just inherit the top-level interface and implement the behavior of the matching classes and methods we define, regardless of how it is implemented internally. Right
Define the PointCut interface
/ * * *@className: PointCut
* @description: The pointcut matches the interface *@author: TR
*/
public interface PointCut {
/** * Match class *@author: jinpeng.sun
* @date: 2021/4/19 13:46
* @paramTargetClass: matched targetClass *@return: boolean
**/
boolean matchClass(Class
targetClass);
/** * match method *@author: jinpeng.sun
* @date: 2021/4/19 13:46
* @paramMethod: matching target method *@paramTargetClass: matched targetClass *@return: boolean
**/
boolean matchMethod(Method method, Class
targetClass);
}
Copy the code
An implementation class that defines regular expressions: RegExpressionPointcut
This is not implemented here, mainly using AspactJ
/ * * *@className: RegExpressionPointcut
* @description: regular expression implementation class *@author: TR
*/
public class RegExpressionPointcut implements PointCut {
@Override
public boolean matchClass(Class
targetClass) {
return false;
}
@Override
public boolean matchMethod(Method method, Class
targetClass) {
return false; }}Copy the code
Define AspectJ tangent point expression of implementation class: AspectJExpressionPointcut
/ * * *@className: AspectJExpressionPointcut
* @description: AspectJ pointcut expression implementation class *@author: TR
*/
public class AspectJExpressionPointcut implements PointCut {
/** get the pointcut parser */
PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
/** The string form of the pointcut expression */
private String expression;
/** Pointcut expressions in AspectJ */
private PointcutExpression pe;
public AspectJExpressionPointcut(String expression) {
super(a);this.expression = expression;
pe = pp.parsePointcutExpression(expression);
}
@Override
public boolean matchClass(Class
targetClass) {
return pe.couldMatchJoinPointsInType(targetClass);
}
@Override
public boolean matchMethod(Method method, Class
targetClass) {
ShadowMatch sm = pe.matchesMethodExecution(method);
return sm.alwaysMatches();
}
public String getExpression(a) {
returnexpression; }}Copy the code
Implementation steps:
- Maven introduces Aspectj jars
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.96.</version>
</dependency>
Copy the code
- Get the pointcut resolver
PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
Copy the code
- Parses the PointcutExpression to obtain the PointcutExpression
PointcutExpression pe = pp.parsePointcutExpression(
"execution(* edu.dongnao.courseware.beans.BeanFactory.get*(..) )");
Copy the code
- Using PointcutExpression to match classes is unreliable
pe.couldMatchJoinPointsInType(targetClass)
Copy the code
- The PointcutExpression matching method is reliable
ShadowMatch sm = pe.matchesMethodExecution(method);
return sm.alwaysMatches();
Copy the code
The class diagram is as follows:
Aspact implementation
Advice and Pointcut are implemented, so how do users use them?
Advice is a user-provided enhanced implementation that requires interface inheritance, so you can configure Advice as a bean. Pointcut is a Pointcut expression that matches classes and methods. You do not need to configure Advice as a bean. Then adviceBeanName+expression forms the cut surface
Let’s implement the aspect through the appearance pattern, combining Advice and Pointcut
Defining the Advisor interface
/ * * *@className: Advisor
* @description: Build the interface to the section, combining advice and pointcut *@author: TR
*/
public interface Advisor {
/** * Get the name of the notification bean *@return: java.lang.String
**/
String getAdviceBeanName(a);
/** * get the pointcut expression *@return: java.lang.String
**/
String getExpression(a);
}
Copy the code
Define the PointcutAdvisor interface
/ * * *@className: PointcutAdvisor
* @descriptionPointcut notifier, inherited from Advisor, extends pointcut *@author: TR
*/
public interface PointcutAdvisor extends Advisor {
/** * get the pointcut *@return: PointCut
**/
PointCut getPointCut(a);
}
Copy the code
Define the AspectJPointcutAdvisor implementation class
/ * * *@className: AspectJPointcutAdvisor
* @description: notifier implementation class * for AspectJ pointcut expressions@author: TR
*/
public class AspectJPointcutAdvisor implements PointcutAdvisor {
/** The name of the notification bean */
private String adviceBeanName;
/** expression */
private String expression;
Tangent point / * * * /
private PointCut pointCut;
public AspectJPointcutAdvisor(String adviceBeanName, String expression) {
this.adviceBeanName = adviceBeanName;
this.expression = expression;
this.pointCut = new AspectJExpressionPointcut(expression);
}
@Override
public PointCut getPointCut(a) {
return pointCut;
}
@Override
public String getAdviceBeanName(a) {
return adviceBeanName;
}
@Override
public String getExpression(a) {
returnexpression; }}Copy the code
The following classes are not implemented and use the form AspactJ
/ * * *@className: RegPointcutAdvisor
* @description: notifier implementation class * for regular pointcut expressions@author: TR
*/
public class RegPointcutAdvisor implements PointcutAdvisor {
/** The name of the notification bean */
private String adviceBeanName;
/** expression */
private String expression;
Tangent point / * * * /
private PointCut pointCut;
public RegPointcutAdvisor(String adviceBeanName, String expression) {
this.adviceBeanName = adviceBeanName;
this.expression = expression;
this.pointCut = new RegExpressionPointcut();
}
@Override
public PointCut getPointCut(a) {
return null;
}
@Override
public String getAdviceBeanName(a) {
return null;
}
@Override
public String getExpression(a) {
return null; }}Copy the code
The class diagram is as follows:
Figure seen from above, if we extend other the tangent point of expression, so in the implementation class, you can see, there are a lot of duplicate code, if so can also optimize the, can be on the upper deck of the implementation classes, and an abstract class, to inherit PointcutAdvisor, then the main point of tangency implementation inheritance this abstract class
Has implemented
Things to get done
It is up to us to add user-provided enhancements to the specified methods
When do I weave
When a Bean instance is created, weaving takes place after the Bean has been initialized
How do YOU know if the Bean is being enhanced
You need to traverse the Bean class and all of its methods, and then match the user-specified facets in turn. If there are matching facets, you need to enhance them
How to weave in, is need to use proxy!
The user needs to register the section, we also need to implement the logic to judge the match, weave, how to write this part of the code, need to write where
All you need to do now is add a handler to the Bean creation process. Later, more handlers may be added to the Bean creation process. If this part of the code is written in the BeanFactory, then this class will have a lot of code and will not be easy to extend later
Take a look at the Bean creation process:
The above diagram, each arrow is to add the extension points, there may be behind, need to add more processing logic on these points, you will need to design a kind of way, in the case of not changing the BeanFactory, flexible extension, you can use the observer pattern, there are several extension point, is there are several topics, six observer
Define the observer interface BeanPostProcessor
/ * * *@className: BeanPostProcessor
* @descriptionAfter the Bean is instantiated and dependency injection is completed@author: TR
*/
public interface BeanPostProcessor {
/** * The bean is processed before initialization *@author: TR
* @paramBean: Bean instance *@paramBeanName: bean name *@return: java.lang.Object
**/
default Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception {
return bean;
}
/** * Processing of bean after initialization *@author: TR
* @paramBean: Bean instance *@paramBeanName: bean name *@return: java.lang.Object
**/
default Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
returnbean; }}Copy the code
Define the notification interface Aware
/ * * *@className: Aware
* @description: Build notification interface *@date: 2021/4/19 thus *@author: jinpeng.sun
*/
public interface Aware {}Copy the code
/ * * *@className: BeanFactoryAware
* @description: Bean factory builds notification interface *@author: TR
*/
public interface BeanFactoryAware extends Aware {
/** * The interface implementer gets the bean factory method *@author: TR
* @param bf:
* @return: void
**/
void setBeanFactory(BeanFactory bf);
}
Copy the code
The above interface is mainly used to get Bean factory information
Define AdvisorRegistry interface for notifier registration
This interface is mainly used to register the Advisor into the implementation class of the enhancement
/ * * *@className: AdvisorRegistry
* @description: notifier registration interface *@author: TR
*/
public interface AdvisorRegistry {
/** * Registered notifier *@author: TR
* @param advisor:
* @return: void
**/
public void registerAdvisor(Advisor advisor);
/** * get the notifier list *@author: TR
* @date: 2021/4/19 16:45
*
* @return: java.util.List<demo.aop.advisor.Advisor>
**/
public List<Advisor> getAdvisors(a);
}
Copy the code
The observer implementation class that defines enhanced processing AdvisorAutoProxyCreator
/ * * *@className: AdvisorAutoProxyCreator
* @description: The user provides facets through the Advisor to inject DefaultBeanFactory into the implementation * inside the framework: DefaultBeanFactory injects the IOC container * DefaultBeanFactory calls the BeanPostProcessor interface related methods for enhancements *@author: TR
*/
public class AdvisorAutoProxyCreator implements BeanPostProcessor.BeanFactoryAware.AdvisorRegistry {
/** List of notifiers */
private List<Advisor> advisors;
/** The current bean */
private BeanFactory beanFactory;
public AdvisorAutoProxyCreator(a) {
this.advisors = new ArrayList<>();
}
@Override
public void registerAdvisor(Advisor advisor) {
this.advisors.add(advisor);
}
@Override
public List<Advisor> getAdvisors(a) {
return this.advisors;
}
@Override
public void setBeanFactory(BeanFactory bf) {
this.beanFactory = bf;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
return null; }}Copy the code
Registered BeanPostProcessor
Added a method to register BeanPostProcessor in the BeanFactory interface
/** Register BeanPostProcessor */
void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor);
Copy the code
Add the following methods to the DefaultBeanFactory implementation class:
private List<BeanPostProcessor> postProcessors = Collections.synchronizedList(new ArrayList<>());
@Override
public void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
postProcessors.add(beanPostProcessor);
if (beanPostProcessor instanceof BeanFactoryAware) {
((BeanFactoryAware) beanPostProcessor).setBeanFactory(this); }}Copy the code
Add handler before and after bean initialization to the getBean method:
@Override
public Object getBean(String beanName) throws Exception {
// Get the bean definition
BeanDefinition bd = beanDefinitionMap.get(beanName);
Object bean = doGetBean(beanName);
// Attribute injection
setPropertyDIValues(bd, bean);
// Bean processing before initialization
bean = this.applyPostProcessBeforeInitialization(bean, beanName);
// The bean's life cycle
if (StringUtils.isNotBlank(bd.getInitMethodName())) {
doInitMethod(bean,bd);
}
// Bean initialization after processing
bean = this.applyPostProcessAfterInitialization(bean, beanName);
return bean;
}
/** * Processing of bean after initialization *@author: TR
* @param bean:
* @param beanName:
* @return: java.lang.Object
**/
private Object applyPostProcessAfterInitialization(Object bean, String beanName) throws Exception {
for (BeanPostProcessor bp : this.postProcessors) {
bean = bp.postProcessBeforeInitialization(bean, beanName);
}
return bean;
}
/** * The bean is processed before initialization *@author: TR
* @param bean:
* @param beanName:
* @return: java.lang.Object
**/
private Object applyPostProcessBeforeInitialization(Object bean, String beanName) throws Exception {
for (BeanPostProcessor bp : this.postProcessors) {
bean = bp.postProcessAfterInitialization(bean, beanName);
}
return bean;
}
Copy the code
The next thing you need to do is determine if the bean needs to be enhanced, so you need to get all the methods of the bean, and then walk through it according to the registered advisors, and then get the cut point to match the class and method
Modify AdvisorAutoProxyCreator postProcessAfterInitialization method in the class:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
// Determine whether section enhancement is required and get notification implementation of the enhancement
List<Advisor> advisors = getMatchedAdvisors(bean, beanName);
// Return the enhanced object if it needs to be enhanced
if (CollectionUtils.isNotEmpty(advisors)) {
// Use proxy mode for feature enhancement
}
return bean;
}
Copy the code
Here is the method code to get a match:
/** * Get the matching method *@author: TR
* @paramBean: Bean instance *@paramBeanName: bean name *@return: void
**/
private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
if (CollectionUtils.isEmpty(advisors)) {
return null;
}
/ / classClass<? > beanClass = bean.getClass();// Get all the methods
List<Method> allMethods = getAllMethodForClass(beanClass);
// Store the matching Advisor
List<Advisor> advisors = new ArrayList<>();
for (Advisor ad : this.advisors) {
if (ad instanceof PointcutAdvisor) {
// Check whether a match is found
if(isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) { advisors.add(ad); }}}return advisors;
}
Copy the code
Get all methods, using a utility class provided by the Spring Framework, to find all methods under the class:
/** * get all methods *@author: TR
* @param beanClass:
* @return: void
**/
private List<Method> getAllMethodForClass(Class
beanClass) {
List<Method> allMethods = newLinkedList<>(); Set<Class<? >> classes =new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
classes.add(beanClass);
for(Class<? > cc : classes) {// Get all methods according to the utility class
Method[] methods = ReflectionUtils.getAllDeclaredMethods(cc);
for(Method m : methods) { allMethods.add(m); }}return allMethods;
}
Copy the code
Here is a way to determine if there is a matching PointCut rule, using the method defined in PointCut:
/** * a method to determine if there is a matching pointcut rule *@author: TR
* @paramAD: section *@paramBeanClass: specifies the class *@paramAllMethods: allMethods under the specified class *@return: void
**/
private boolean isPointcutMatchBean(PointcutAdvisor ad, Class
beanClass, List
allMethods)
{
PointCut p = ad.getPointCut();
/ / match
if(! p.matchClass(beanClass)) {return false;
}
for (Method m : allMethods) {
// Match method
if (p.matchMethod(m, beanClass)) {
return true; }}return false;
}
Copy the code
After deciding whether to enhance, it is necessary to implement proxy enhancement, so what is the implementation logic here
As you can see from the figure above, if you need to decide whether to use JDK dynamic proxies or CGLIB dynamic code, you can also abstract out an interface here. Then different proxy methods are implemented separately
Define the AopProxy proxy interface
/ * * *@className: AopProxy
* @description: AOP proxy interface for creating and getting proxy objects *@author: TR
*/
public interface AopProxy {
Object getProxy(a);
Object getProxy(ClassLoader classLoader);
}
Copy the code
JDK dynamic proxy implementation
We know that JDK dynamic proxies work on interfaces, so what data is needed in the implementation?
- The interface to implement
- The target object
- Match the Advisors,
- BeanFactory
Required parameters:
- The interface to implement
- InvocationHandler
/ * * *@className: JdkDynamicAopProxy
* @description: JDK dynamic proxy implementation *@author: TR
*/
public class JdkDynamicAopProxy implements AopProxy.InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(JdkDynamicAopProxy.class);
/ / the name of the bean
private String beanName;
// The bean object that needs to be proxied
private Object target;
// Notification list, which needs to be enhanced
private List<Advisor> advisors;
// The current bean
private BeanFactory beanFactory;
public JdkDynamicAopProxy(String beanName, Object target, List<Advisor> advisors, BeanFactory beanFactory) {
this.beanName = beanName;
this.target = target;
this.advisors = advisors;
this.beanFactory = beanFactory;
}
@Override
public Object getProxy(a) {
return this.getProxy(target.getClass().getClassLoader());
}
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("For" + target + "Create a JDK agent.");
}
return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
}
/** * The implementation of the InvocationHandler interface is used to perform proxy enhancement and return the actual result */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
returnAopProxyUtils.applyAdvices(target, method, args, advisors, proxy, beanFactory); }}Copy the code
CGLIB dynamic proxy implementation
The CGLIB dynamic proxy can perform operations on interfaces and classes, so it requires the following data:
- The class to inherit from
- The interface to implement
- The target object
- Match the Advisors,
- BeanFactory
- Construction parameter class
- Structural parameters
Required parameters:
- Inheritance of class
- The interface to implement
- Callback
- Construction parameter type
- Structural parameters
The construct parameter type and construct parameter are present when creating the instance
/ * * *@className: CglibDynamicAopProxy
* @description: Cglib dynamic proxy *@author: TR
*/
public class CglibDynamicAopProxy implements AopProxy.MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(CglibDynamicAopProxy.class);
private static Enhancer enhancer = new Enhancer();
/ / the name of the bean
private String beanName;
// The bean object that needs to be proxied
private Object target;
// Notification list, which needs to be enhanced
private List<Advisor> advisors;
// The current bean
private BeanFactory beanFactory;
public CglibDynamicAopProxy(String beanName, Object target, List<Advisor> advisors, BeanFactory beanFactory) {
this.beanName = beanName;
this.target = target;
this.advisors = advisors;
this.beanFactory = beanFactory;
}
@Override
public Object getProxy(a) {
return this.getProxy(target.getClass().getClassLoader());
}
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("For" + target + "Create the Cglib proxy."); } Class<? > superClass =this.target.getClass();
enhancer.setSuperclass(superClass);
enhancer.setInterfaces(this.getClass().getInterfaces());
enhancer.setCallback(this); Constructor<? > constructor =null;
try {
constructor = superClass.getConstructor(newClass<? > [] {}); }catch (NoSuchMethodException e) {
e.printStackTrace();
}
if(constructor ! =null) {
return enhancer.create();
} else {
BeanDefinition bd = ((DefaultBeanFactory)beanFactory).getBeanDefinition(beanName);
returnenhancer.create(bd.getConstructor().getParameterTypes(), bd.getConstructorArgumentRealValues()); }}@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
returnAopProxyUtils.applyAdvices(target, method, objects, advisors, o, beanFactory); }}Copy the code
AopProxyUtils what does it do, and as you can see, this part of the processing, which is used for enhancement, is done using Advice, so the logic for enhancement is the same, right
/ * * *@className: AopProxyUtils
* @description: AOP proxy tool classes *@author: TR
*/
public class AopProxyUtils {
/** * Apply advice to the method to get the final result *@author: TR
* @paramTarget: the object to be enhanced *@paramMethod: The method that needs to be enhanced *@paramArgs: arguments to the enhanced method *@paramAdvisors: Matched slice *@paramProxy: the enhanced proxy object * of the bean object@paramBeanFactory: Bean factory *@return: java.lang.Object
**/
public static Object applyAdvices(Object target, Method method, Object[] args, List
advisors, Object proxy, BeanFactory beanFactory)
throws Throwable {
// Get the enhanced advices for the current method
List<Object> advices = AopProxyUtils.getShouldApplyAdvices(target.getClass(), method, advisors, beanFactory);
if (CollectionUtils.isEmpty(advices)) {
return method.invoke(target, args);
} else {
// Use chain of responsibility for enhancement
AopAdviceChainInvocation ac = new AopAdviceChainInvocation(proxy, target, method, args, advices);
returnac.invoke(); }}/** * Get a list of Advice facets that match the method *@author: TR
* @param aClass:
* @param method:
* @param advisors:
* @param beanFactory:
* @return: java.util.List<demo.aop.advice.Advice>
**/
private static List<Object> getShouldApplyAdvices(Class
aClass, Method method, List
advisors, BeanFactory beanFactory)
throws Exception {
if (CollectionUtils.isEmpty(advisors)) {
return null;
}
List<Object> advices = new ArrayList<>();
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
if(((PointcutAdvisor)advisor).getPointCut().matchMethod(method, aClass)) { advices.add(beanFactory.getBean(advisor.getAdviceBeanName())); }}}returnadvices; }}Copy the code
How do I pass the data acquired when creating the bean instance into the initialized Aop, using ThreadLocal to hold parameter values in the BeanDefinition
BeanDefinition adds the following method:
/** Gets the construction parameter value */
public Object[] getConstructorArgumentRealValues();
/** Sets the construction parameter value */
public void setConstructorArgumentRealValues(Object[] values);
Copy the code
Corresponding implementation in the GeneralBeanDefinition class:
private ThreadLocal<Object[]> realConstructorArgumentValues = new ThreadLocal<>();
@Override
public Object[] getConstructorArgumentRealValues() {
return realConstructorArgumentValues.get();
}
@Override
public void setConstructorArgumentRealValues(Object[] values) {
this.realConstructorArgumentValues.set(values);
}
Copy the code
AopAdviceChainInvocation class
/ * * *@className: AopAdviceChainInvocation
* @description: AOP responsibility chain calls class *@author: TR
*/
public class AopAdviceChainInvocation {
/** AOP responsibility chain execution method */
private static Method invokeMethod;
static {
try {
invokeMethod = AopAdviceChainInvocation.class.getMethod("invoke".null);
} catch(NoSuchMethodException e) { e.printStackTrace(); }}// Proxy class object
private Object proxy;
// Target class object
private Object target;
// Invoke the executed object method
private Method method;
// Arguments to the execution method
private Object[] args;
// Method enhanced functionality: notification list
private List<Object> advices;
public AopAdviceChainInvocation(Object proxy, Object target, Method method, Object[] args, List<Object> advices) {
this.proxy = proxy;
this.target = target;
this.method = method;
this.args = args;
this.advices = advices;
}
// The index number of the chain of responsibility execution record
private int i = 0;
public Object invoke(a) throws Throwable {
if (i < this.advices.size()) {
Object advice = this.advices.get(i++);
if (advice instanceof MethodBeforeAdvice) {
// Perform pre-enhancement
((MethodBeforeAdvice)advice).before(method, target, args);
} else if (advice instanceof MethodInterceptor) {
// Perform surround enhancement and exception enhancement. The method and object given here are the Invoke method and chain object
((MethodInterceptor)advice).invoke(invokeMethod,this.null);
} else if (advice instanceof AfterReturningAdvice) {
// After the enhancement, get the result first, before the enhancement
Object returnValue = this.invoke();
((AfterReturningAdvice)advice).after(method, target, args, returnValue);
return returnValue;
}
/ / callback
return this.invoke();
} else {
returnmethod.invoke(target, args); }}}Copy the code
Define the AopProxyFactory interface
After the proxy approach is implemented, we need to use it. Here we use the factory pattern implementation:
/ * * *@className: AopProxyFactory
* @description: the AOP proxy's factory interface *@author: TR
*/
public interface AopProxyFactory {
/** * Get the implementation of the AOP proxy interface based on the argument *@param* bean: instance@paramBeanName: bean name *@paramAdvisors: List of advisors *@paramBeanFactory: Bean factory *@return: AopProxyFactory
**/
AopProxy createAopProxy(Object bean, String beanName, List<Advisor> advisors, BeanFactory beanFactory);
/** * get the default AopProxyFactory *@return: AopProxyFactory
**/
static AopProxyFactory getDefaultAopProxyFactory(a) {
return newDefaultAopProxyFactory(); }}Copy the code
Implementation class:
/ * * *@className: DefaultAopProxyFactory
* @description: Agent factory implementation class *@author: TR
*/
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(Object bean, String beanName, List<Advisor> advisors, BeanFactory beanFactory) {
// Use JDK dynamic proxy or CGLIB dynamic proxy
if (shouldUseJDKDynamicProxy(bean, beanName)) {
return new JdkDynamicAopProxy(beanName, bean, advisors, beanFactory);
} else {
return newCglibDynamicAopProxy(beanName, bean, advisors, beanFactory); }}private boolean shouldUseJDKDynamicProxy(Object bean, String beanName) {
return false; }}Copy the code
Here need to modify the AdvisorAutoProxyCreator postProcessAfterInitialization method in the class:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws Exception {
// Determine whether section enhancement is required and get notification implementation of the enhancement
List<Advisor> advisors = getMatchedAdvisors(bean, beanName);
// Return the enhanced object if it needs to be enhanced
if (CollectionUtils.isNotEmpty(advisors)) {
// Use proxy mode for feature enhancement
bean = this.createProxy(bean, beanName, advisors);
}
return bean;
}
private Object createProxy(Object bean, String beanName, List<Advisor> advisors) {
return AopProxyFactory.getDefaultAopProxyFactory()
.createAopProxy(bean, beanName, advisors, beanFactory)
.getProxy();
}
Copy the code
The way the constructor is cached in the DefaultBeanFactory class needs to be changed
The code cached in the determineConstructor method is commented out:
if(ct ! =null) {
// // For the prototype bean, cache it
// if (bd.isProtoType()) {
// bd.setConstructor(ct);
/ /}
return ct;
} else {
throw new Exception("No constructor found:" + bd);
}
Copy the code
Add the code to createBeanByConstructor
/** Build bean */ with constructor
private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
Object object = null;
if (CollectionUtils.isEmpty(bd.getConstructorArgumentValues())) {
// If the constructor parameter value is empty, no arguments are passed
object = bd.getBeanClass().newInstance();
} else {
// If the obtained parameter value is empty, no parameter is passed
Object[] args = getConstructorArgumentValues(bd);
if (args == null) {
object = bd.getBeanClass().newInstance();
} else {
// Determine the constructor based on the parameter matching, and then instantiate the callbd.setConstructorArgumentRealValues(args); Constructor<? > ct =this.determineConstructor(bd, args);
// The cache constructor was moved here from determineConstructor, caching whether the prototype is cached or not, because AOP needs it later
bd.setConstructor(ct);
returnct.newInstance(args); }}return object;
}
Copy the code
Test the
Pre-enhanced implementation:
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object target, Object[] args) {
System.out.println(this + "To" + target + "Pre-enhanced!"); }}Copy the code
Post-enhancement implementation:
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void after(Method method, Object target, Object[] args, Object returnValue) {
System.out.println(this + "To" + target + "Post-enhanced, return value ="+ returnValue); }}Copy the code
Surround enhancement implementation:
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(Method method, Object target, Object[] args) throws Throwable {
System.out.println(this + "对 " + target + "Circled forward enhancement.");
Object ret = method.invoke(target, args);
System.out.println(this + "对 " + target + "After the surround - enhanced. The return value of the method is: + ret);
returnret; }}Copy the code
The test class:
public class AopTest {
static PreBuildBeanFactory bf = new PreBuildBeanFactory();
@Test
public void testCirculationDI(a) throws Throwable {
GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setBeanClass(Lad.class);
List<Object> args = new ArrayList<>();
args.add("Sun Wukong");
args.add(new BeanReference("baigujing"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("swk", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(MagicGirl.class);
args = new ArrayList<>();
args.add(Bone fairy);
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("baigujing", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(Renminbi.class);
bf.registerBeanDefinition("renminbi", bd);
// Pre-enhance advice bean registration
bd = new GeneralBeanDefinition();
bd.setBeanClass(MyBeforeAdvice.class);
bf.registerBeanDefinition("myBeforeAdvice", bd);
// Surround enhanced advice bean registration
bd = new GeneralBeanDefinition();
bd.setBeanClass(MyMethodInterceptor.class);
bf.registerBeanDefinition("myMethodInterceptor", bd);
// Post-enhanced advice bean registration
bd = new GeneralBeanDefinition();
bd.setBeanClass(MyAfterReturningAdvice.class);
bf.registerBeanDefinition("myAfterReturningAdvice", bd);
// Register AOP's BeanPostProcessor in BeanFactory
AdvisorAutoProxyCreator aapc = new AdvisorAutoProxyCreator();
bf.registerBeanPostProcessor(aapc);
// register Advisor with AdvisorAutoProxyCreator
aapc.registerAdvisor(
new AspectJPointcutAdvisor("myBeforeAdvice"."execution(* demo.di.MagicGirl.*(..) )"));
// register Advisor with AdvisorAutoProxyCreator
aapc.registerAdvisor(
new AspectJPointcutAdvisor("myMethodInterceptor"."execution(* demo.di.Lad.say*(..) )"));
// register Advisor with AdvisorAutoProxyCreator
aapc.registerAdvisor(new AspectJPointcutAdvisor("myAfterReturningAdvice"."execution(* demo.di.Renminbi.*(..) )"));
bf.preInstantiateSingletons();
System.out.println("-----------------myBeforeAdvice---------------");
MagicGirl gril = (MagicGirl) bf.getBean("baigujing");
gril.getFriend();
gril.getName();
System.out.println("----------------myMethodInterceptor----------------");
Boy boy = (Boy) bf.getBean("swk");
boy.sayLove();
System.out.println("-----------------myAfterReturningAdvice---------------");
Renminbi rmb = (Renminbi) bf.getBean("renminbi"); rmb.pay(); }}Copy the code
Running results:
Other points in this article:
- Java design pattern triggered by a security guard: appearance pattern
- What? Java design Mode: Observer Mode
- Drumming is reminiscent of the Java design pattern: the Chain of Responsibility pattern
- Nuwa creates a Thought-provoking Java design pattern: Factory Pattern
- Tony: Java Design pattern: Proxy pattern