preface

Text has been included to my lot warehouse, welcome Star:https://github.com/bin392328206 plant a tree is the best time ten years ago, followed by now

six-finger-web

The wheels of a Web backend framework roll themselves (easy) from handling Http requests [Netty request-level Web servers] to MVC [interface encapsulating and forwarding], to IOC [dependency injection], to AOP [aspect], to RPC [remote procedure calls] and finally to ORM [database operations].

github

Why the wheel

Actually is such, small six six oneself peacetime? Sometimes like to look at the source code such as Spring, but the level may not how, every time dazed at all, and then feel the inside of the details too difficult, then I can only view the overall thought, then I think if I can according to each elder some thought, rolled out a simple wheel, Is it easier for me to understand the author’s ideas? So six-Finger-Web came out, and it was really a learning process for me, and THEN I opened it up to help those of you who are having trouble learning the source code. In addition, we can exercise our coding ability, because we usually use Java API for CRUD. As time goes by, we are not familiar with many framework API classes, so we take this opportunity to exercise.

The characteristics of

  • Built-in HTTP server written by Netty, without additional dependence on Web services such as Tomcat.
  • Code is simple to understand (small 66 can not write their own framework big guy that kind of high clustering, low coupling code), ability a little bit stronger to see the code can understand, weakness also does not matter, small 66 has a supporting from 0 build tutorial.
  • Support MVC-related annotations to ensure that they are used similarly to SpringMVC
  • Support for Spring IOC and Aop related features
  • Support similar to Mybatis related functions
  • Supports RPC-related functionality similar to Dubbo
  • For data returns, only the Json format is supported

omg

The front is already written chapters, I will give you one by one to go through the construction process

  • Suitable for beginners and intermediate Java programmer training manual to build the entire Web project from 0 (A)
  • Build the whole Web project from 0 (2)
  • Build the whole Web project from 0 (3)
  • Build the whole Web project from 0 (4)

Let me summarize what we’ve done. We’ve done Netty based Http server, and spring IOC related features for SpringMVC. What about today? Let’s try to write aop for Spring

The overall structure


The above is the structure after writing, let’s look at our process one by one


In fact, this side of small 66 met some problems, but failed to solve, so first.

JoinPoint

One of the input arguments to the notification method is JoinPoint, which can be used to retrieve the object, method, parameter, etc., and even set a parameter for context passing, which can be determined from the interface method.

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;

import java.lang.reflect.Method;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 16:26 * One of the input arguments to the notification method is < KBD >JoinPoint</ KBD >. Through this method, we can get the current propped object, method, parameter, etc., and even set a parameter for context passing, which can be determined from the interface method.* /public interface JoinPoint {   Object getThis();   Object[] getArguments();   Method getMethod();   void setUserAttribute(String key, Object value);   Object getUserAttribute(String key); } Copy the code

Its implementation class is the outer interceptor object mentioned in the introduction, which is responsible for executing the entire interceptor chain. The main logic is to iterate through the chain before executing the propped method. This is actually the realization of the chain of responsibility model.

package com.xiaoliuliu.six.finger.web.spring.aop.intercept;

import com.xiaoliuliu.six.finger.web.spring.aop.aspect.JoinPoint;

import java.lang.reflect.Method;
import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects;  / * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:10 * /public class MethodInvocation implements JoinPoint {  / * * * @author: linliangkun  * @Date: 2020/10/26 17:12 * @description: proxied object* / private Object proxy;  /** Proxied object */ private Object target;  /** Class */ of the proxied objectprivate Class<? > targetClass;  /** Proxy method */ private Method method;  /** The input parameter of the proxied method */ private Object [] arguments;  /** Interceptor chain */ private List<Object> interceptorsAndDynamicMethodMatchers;  /** User parameters */ private Map<String, Object> userAttributes;  /** Records the current interceptor execution position */ private int currentInterceptorIndex = -1;   public MethodInvocation(Object proxy,  Object target,  Method method,  Object[] arguments, Class<? > targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {   this.proxy = proxy;  this.target = target;  this.targetClass = targetClass;  this.method = method;  this.arguments = arguments;  this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;  }   @Override  public Object getThis() {  return this.target;  }   @Override  public Object[] getArguments() {  return this.arguments;  }   @Override  public Method getMethod() {  return this.method;  }   @Override  public void setUserAttribute(String key, Object value) {  if(value ! = null) { if (this.userAttributes == null) {  this.userAttributes = new HashMap<>();  }  this.userAttributes.put(key, value);  }  else {  if(this.userAttributes ! = null) { this.userAttributes.remove(key);  }  }  }   @Override  public Object getUserAttribute(String key) {  return(this.userAttributes ! = null ? this.userAttributes.get(key) : null); }   public Object proceed() throws Throwable{ // When the interceptor completes, it actually executes the proxied method if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {  return this.method.invoke(this.target,this.arguments);  }  // Get an interceptor Object interceptorOrInterceptionAdvice =  this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);  if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {  MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice; // Execute the notification method return mi.invoke(this);  } else { // Skip and call the next interceptor return proceed();  }  } }  Copy the code

MethodInterceptor

Create a new interceptor MethodInterceptor interface

package com.xiaoliuliu.six.finger.web.spring.aop.intercept;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:19 * /public interface MethodInterceptor {  Object invoke(MethodInvocation invocation) throws Throwable;  }  Copy the code

Advice

Before subclassing the interceptor MethodInterceptor, create Advice as the top-level interface to the different notification methods.

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:22 * Before implementing a subclass of the < KBD >MethodInterceptor</ KBD >, create a new < KBD >Advice</ KBD > as a top-level interface to the different notification methods.* /public interface Advice { }  Copy the code

Then write an abstract subclass to encapsulate the common logic of the different notification types: the input parameter is assigned before the notification method is called so that the user can get the actual value when writing the notification method.

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;

import java.lang.reflect.Method;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:23 * /public abstract class AbstractAspectAdvice implements Advice{ /** Notification method */ private Method aspectMethod;  /** Section class */ private Object aspectTarget;   public AbstractAspectAdvice(Method aspectMethod, Object aspectTarget) {  this.aspectMethod = aspectMethod;  this.aspectTarget = aspectTarget;  }  / * ** Call the notification method* / public Object invokeAdviceMethod(JoinPoint joinPoint, Object returnValue, Throwable tx) throws Throwable { Class<? >[] paramTypes = this.aspectMethod.getParameterTypes(); if (null == paramTypes || paramTypes.length == 0) {  return this.aspectMethod.invoke(aspectTarget);  } else {  Object[] args = new Object[paramTypes.length];  for (int i = 0; i < paramTypes.length; i++) {  if (paramTypes[i] == JoinPoint.class) {  args[i] = joinPoint;  } else if (paramTypes[i] == Throwable.class) {  args[i] = tx;  } else if (paramTypes[i] == Object.class) {  args[i] = returnValue;  }  }  return this.aspectMethod.invoke(aspectTarget, args);  }  } }  Copy the code

Implement multiple notification types

An interceptor is essentially a wrapper around various notification methods, so AbstractAspectAdvice is inherited to implement MethodInterceptor. The following implements pre-notification, post-notification, and exception notification respectively.

Pre notice

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:37 * / import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInterceptor; import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;  import java.lang.reflect.Method;  / * ** Pre-notification* /public class MethodBeforeAdviceInterceptor extends AbstractAspectAdvice implements MethodInterceptor {   private JoinPoint joinPoint;   public MethodBeforeAdviceInterceptor(Method aspectMethod, Object aspectTarget) {  super(aspectMethod, aspectTarget);  }   private void before(Method method, Object[] args, Object target) throws Throwable {  super.invokeAdviceMethod(this.joinPoint, null, null);  }   @Override  public Object invoke(MethodInvocation mi) throws Throwable {  this.joinPoint = mi; // Perform pre-notification before calling the next interceptor before(mi.getMethod(), mi.getArguments(), mi.getThis());  return mi.proceed();  }  } Copy the code

The rear notice

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:40 * / import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInterceptor; import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;  import java.lang.reflect.Method;  / * ** Post notifications* /public class AfterReturningAdviceInterceptor extends AbstractAspectAdvice implements MethodInterceptor {   private JoinPoint joinPoint;   public AfterReturningAdviceInterceptor(Method aspectMethod, Object aspectTarget) {  super(aspectMethod, aspectTarget);  }   @Override  public Object invoke(MethodInvocation mi) throws Throwable { // Call the next interceptor first Object retVal = mi.proceed(); // call post-notification again this.joinPoint = mi;  this.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());  return retVal;  }   private void afterReturning(Object retVal, Method method, Object[] arguments, Object aThis) throws Throwable {  super.invokeAdviceMethod(this.joinPoint, retVal, null);  } }  Copy the code

Abnormal notice

package com.xiaoliuliu.six.finger.web.spring.aop.aspect;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:40 * / import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInterceptor; import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;  import java.lang.reflect.Method;  / * ** Exception notification* /public class AfterThrowingAdviceInterceptor extends AbstractAspectAdvice implements MethodInterceptor {    private String throwingName;   public AfterThrowingAdviceInterceptor(Method aspectMethod, Object aspectTarget) {  super(aspectMethod, aspectTarget);  }   @Override  public Object invoke(MethodInvocation mi) throws Throwable {  try { // Call the next interceptor directly, and do not call the exception notification if no exception occurs return mi.proceed();  } catch (Throwable e) { // Call the notification method in exception capture invokeAdviceMethod(mi, null, e.getCause());  throw e;  }  }   public void setThrowName(String throwName) {  this.throwingName = throwName;  } } Copy the code

AopProxy

Create AopProxy, the top-level interface for creating proxies

package com.xiaoliuliu.six.finger.web.spring.aop;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:41 * /public interface AopProxy {   Object getProxy();   Object getProxy(ClassLoader classLoader); }  Copy the code

Spring’s proxy creation logic is to use Cglib’s dynamic proxy if the propped class has a native JDK dynamic proxy that implements the interface. So AopProxy has two subclasses JdkDynamicAopProxy and CglibAopProxy to implement both creation logic.

JdkDynamicAopProxy uses JDK dynamic proxies to create proxies, so you need to implement the InvocationHandler interface.

package com.xiaoliuliu.six.finger.web.spring.aop;

import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;
import com.xiaoliuliu.six.finger.web.spring.aop.support.AdvisedSupport;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List;  / * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:42 * /public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {   private AdvisedSupport advised;   public JdkDynamicAopProxy(AdvisedSupport config) {  this.advised = config;  }   @Override  public Object getProxy() {  return getProxy(this.advised.getTargetClass().getClassLoader());  }   @Override  public Object getProxy(ClassLoader classLoader) { //JDK dynamic proxy return Proxy.newProxyInstance(classLoader, this.advised.getTargetClass().getInterfaces(), this);  }   @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Get interceptor chain List<Object> interceptorsAndDynamicMethodMatchers =  this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass()); // The outer interceptor controls the execution of the interceptor chain MethodInvocation invocation = new MethodInvocation(  proxy,  this.advised.getTarget(),  method,  args,  this.advised.getTargetClass(),  interceptorsAndDynamicMethodMatchers  ); // Start the call to the connector chain return invocation.proceed();  } }   Copy the code

And we use cglib a lot

package com.xiaoliuliu.six.finger.web.spring.aop;

import com.xiaoliuliu.six.finger.web.spring.aop.intercept.MethodInvocation;
import com.xiaoliuliu.six.finger.web.spring.aop.support.AdvisedSupport;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;  import java.lang.reflect.Method; import java.util.List;  / * ** @author little six six* @ version 1.0 * @date 2020/10/12 11:03 * Dynamic proxies for Cglib* /public class CglibAopProxy implements AopProxy, MethodInterceptor {  private AdvisedSupport advised;   public CglibAopProxy(AdvisedSupport config) {  this.advised = config;  }   @Override  public Object getProxy() {  return getProxy(this.advised.getTargetClass().getClassLoader());  }   @Override  public Object getProxy(ClassLoader classLoader) {  / / additional ways Enhancer enhancer = new Enhancer();  enhancer.setSuperclass(this.advised.getTargetClass());  enhancer.setCallback(this);  return enhancer.create();  }   @Override  public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // Get interceptor chain List<Object> interceptorsAndDynamicMethodMatchers =  this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass()); // The outer interceptor controls the execution of the interceptor chain MethodInvocation invocation = new MethodInvocation(  proxy,  this.advised.getTarget(),  method,  args,  this.advised.getTargetClass(),  interceptorsAndDynamicMethodMatchers  ); // Start the call to the connector chain return invocation.proceed();  } }  Copy the code

The member variable AdvisedSupport encapsulates all the resources needed to create the proxy, as you can see from the code above that it encapsulates at least the proxyed target instance, chain of interceptors, and so on, and is actually responsible for parsing the AOP configuration and creating interceptors.

package com.xiaoliuliu.six.finger.web.spring.aop.support;

import com.xiaoliuliu.six.finger.web.spring.aop.aspect.AfterReturningAdviceInterceptor;
import com.xiaoliuliu.six.finger.web.spring.aop.aspect.AfterThrowingAdviceInterceptor;
import com.xiaoliuliu.six.finger.web.spring.aop.aspect.MethodBeforeAdviceInterceptor;
import com.xiaoliuliu.six.finger.web.spring.aop.config.AopConfig;  import java.lang.reflect.Method; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern;  / * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:43 * /public class AdvisedSupport {  /** Proxied class*/private Class<? > targetClass; /** The power of the proxied object */ private Object target;  /** The set of interceptors corresponding to the proxied method */ private Map<Method, List<Object>> methodCache;  /**AOP external configuration */ private AopConfig config;  /** cut point regular expression */ private Pattern pointCutClassPattern;   public AdvisedSupport(AopConfig config) {  this.config = config;  }  public Class<? >getTargetClass() {  return this.targetClass;  }   public Object getTarget() {  return this.target;  }  / * ** Get interceptors* /public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<? > targetClass) throws Exception { List<Object> cached = methodCache.get(method);  if (cached == null) {  Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());  cached = methodCache.get(m);  this.methodCache.put(m, cached);  }   return cached;  }  public void setTargetClass(Class<? > targetClass) { this.targetClass = targetClass;  parse();  }  / * *Parse the AOP configuration to create interceptors* / private void parse() { // Compile the pointcut expression as regular String pointCut = config.getPointCut()  .replaceAll("\ \."."\ \ \ \.")  .replaceAll("\ \ \ \ \ \ *".". *")  .replaceAll("\\("."\ \ \ \")  .replaceAll("\\)"."\ \ \ \"); //pointCut=public .* com.lqb.demo.service.. *Service.. * (. *) String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);  pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(  pointCutForClassRegex.lastIndexOf("") + 1));   try { // Save all notification methods for the slice Map<String, Method> aspectMethods = new HashMap<>();  Class aspectClass = Class.forName(this.config.getAspectClass());  for (Method m : aspectClass.getMethods()) {  aspectMethods.put(m.getName(), m);  }  // Iterates over all methods of the propped class, creating interceptors for methods that match the pointcut expression methodCache = new HashMap<>();  Pattern pattern = Pattern.compile(pointCut);  for (Method m : this.targetClass.getMethods()) {  String methodString = m.toString(); Throws xxxException on the end of the function signature if (methodString.contains("throws")) {  methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();  }   Matcher matcher = pattern.matcher(methodString);  if (matcher.matches()) { // Actuator chain List<Object> advices = new LinkedList<>();  // Create a pre-interceptor if(! (null == config.getAspectBefore() ||"".equals(config.getAspectBefore()))) { // Create Advivce MethodBeforeAdviceInterceptor beforeAdvice = new MethodBeforeAdviceInterceptor(  aspectMethods.get(config.getAspectBefore()),  aspectClass.newInstance()  );  advices.add(beforeAdvice);  } // Create a post-interceptor if(! (null == config.getAspectAfter() ||"".equals(config.getAspectAfter()))) {  AfterReturningAdviceInterceptor returningAdvice = new AfterReturningAdviceInterceptor(  aspectMethods.get(config.getAspectAfter()),  aspectClass.newInstance()  );  advices.add(returningAdvice);  } // Create an exception interceptor if(! (null == config.getAspectAfterThrow() ||"".equals(config.getAspectAfterThrow()))) {  AfterThrowingAdviceInterceptor throwingAdvice = new AfterThrowingAdviceInterceptor(  aspectMethods.get(config.getAspectAfterThrow()),  aspectClass.newInstance()  );  throwingAdvice.setThrowName(config.getAspectAfterThrowingName());  advices.add(throwingAdvice);  }  // Save the relationship between the proxied method and the chain of actuators methodCache.put(m, advices);  }  }  } catch (Exception e) {  e.printStackTrace();  }   }   public void setTarget(Object target) {  this.target = target;  }  / * ** Determine if a class needs to be proxied* / public boolean pointCutMatch() {  return pointCutClassPattern.matcher(this.targetClass.toString()).matches();  } } Copy the code

AopConfig saves the configured cuts, pointcuts, and notifications.

package com.xiaoliuliu.six.finger.web.spring.aop.config;

import lombok.Data;

/ * ** @author little six six* @ version 1.0 * @date 2020/10/26 17:43 * /@Data public class AopConfig {  // Pointcut expression private String pointCut;  // Pre-notification method private String aspectBefore;  // Post notification method private String aspectAfter;  / / cut class private String aspectClass;  // Exception notification method private String aspectAfterThrow;  // The type of exception thrown private String aspectAfterThrowingName;  } Copy the code

test

As for the test, it’s the same as before


Just inject AOP.

Problems encountered

In fact, there is a pit here, but they did not fix it, is so ha


When I tested the class, the UserServiceImpl we were injecting was supposed to be a proxy object, but the proxy object generated after we passed the dynamic proxy kept reporting errors.

But his interceptor chain did work. But aop proxy objects keep reporting errors.

So the injection is always empty, always depressed, don’t understand why.

At the end

In fact, Spring AOP boils down to dynamic proxies + our various pre – mid – post processors (implementation of the chain of responsibility pattern). Although not completely done but for spring AOP is a bit of help, you can refer to it.

Daily for praise

Ok, everybody, that’s all for this article, you can see people here, they are real fans.

Creation is not easy, your support and recognition, is the biggest power of my creation, our next article

Six pulse excalibur | article “original” if there are any errors in this blog, please give criticisms, be obliged!