19.手写Spring AOP

2020-08-12 01:19:17 浏览数 (1)

1.Spring AOP顶层设计

2.Spring AOP执行流程

下面是代码实现

3.在 application.properties中增加如下自定义配置:

代码语言:javascript复制
#托管的类扫描包路径#
scanPackage=com.gupaoedu.vip.demo

templateRoot=layouts

#切面表达式expression#
pointCut=public .* com.gupaoedu.vip.demo.service..*Service..*(.*)
#切面类
aspectClass=com.gupaoedu.vip.demo.aspect.LogAspect
#前置通知回调方法
aspectBefore=before
#后置通知回调方法
aspectAfter=after
#异常通知回调方法
aspectAfterThrow=afterThrowing
#异常类型捕获
aspectAfterThrowingName=java.lang.Exception

4.GPJdkDynamicAopProxy

代码语言:javascript复制
public class GPJdkDynamicAopProxy implements InvocationHandler {
    private GPAdvisedSupport config;

    public GPJdkDynamicAopProxy(GPAdvisedSupport config) {
        this.config = config;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Map<String,GPAdvice> advices = config.getAdvices(method,null);

        Object returnValue;
        try {
            invokeAdivce(advices.get("before"));

            returnValue = method.invoke(this.config.getTarget(),args);

            invokeAdivce(advices.get("after"));
        }catch (Exception e){
            invokeAdivce(advices.get("afterThrow"));
            throw e;
        }

        return returnValue;
    }

    private void invokeAdivce(GPAdvice advice) {
        try {
            advice.getAdviceMethod().invoke(advice.getAspect());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),this.config.getTargetClass().getInterfaces(),this);
    }
}

5.GPAdvisedSupport 配置解析

代码语言:javascript复制
/**
 * 解析AOP配置的工具类
 */
public class GPAdvisedSupport {
    private GPAopConfig config;
    private Object target;
    private Class targetClass;
    private Pattern pointCutClassPattern;

    private Map<Method,Map<String,GPAdvice>> methodCache;

    public GPAdvisedSupport(GPAopConfig config) {
        this.config = config;
    }

    //解析配置文件的方法
    private void parse() {

        //把Spring的Excpress变成Java能够识别的正则表达式
        String pointCut = config.getPointCut()
                .replaceAll("\.", "\\.")
                .replaceAll("\\.\*", ".*")
                .replaceAll("\(", "\\(")
                .replaceAll("\)", "\\)");


        //保存专门匹配Class的正则
        String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\(") - 4);
        pointCutClassPattern = Pattern.compile("class "   pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ")   1));


        //享元的共享池
        methodCache = new HashMap<Method, Map<String, GPAdvice>>();
        //保存专门匹配方法的正则
        Pattern pointCutPattern = Pattern.compile(pointCut);
        try{
            Class aspectClass = Class.forName(this.config.getAspectClass());
            Map<String,Method> aspectMethods = new HashMap<String, Method>();
            for (Method method : aspectClass.getMethods()) {
                aspectMethods.put(method.getName(),method);
            }

            for (Method method : this.targetClass.getMethods()) {
                String methodString = method.toString();
                if(methodString.contains("throws")){
                    methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                }

                Matcher matcher = pointCutPattern.matcher(methodString);
                if(matcher.matches()){
                    Map<String,GPAdvice> advices = new HashMap<String, GPAdvice>();

                    if(!(null == config.getAspectBefore() || "".equals(config.getAspectBefore()))){
                        advices.put("before",new GPAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectBefore())));
                    }
                    if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))){
                        advices.put("after",new GPAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfter())));
                    }
                    if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))){
                        GPAdvice advice = new GPAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfterThrow()));
                        advice.setThrowName(config.getAspectAfterThrowingName());
                        advices.put("afterThrow",advice);
                    }

                    //跟目标代理类的业务方法和Advices建立一对多个关联关系,以便在Porxy类中获得
                    methodCache.put(method,advices);
                }
            }


        }catch(Exception e){
            e.printStackTrace();
        }

    }



    //根据一个目标代理类的方法,获得其对应的通知
    public Map<String,GPAdvice> getAdvices(Method method, Object o) throws Exception {
        //享元设计模式的应用
        Map<String,GPAdvice> cache = methodCache.get(method);
        if(null == cache){
            Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
            cache = methodCache.get(m);
            this.methodCache.put(m,cache);
        }
        return cache;
    }

    //给ApplicationContext首先IoC中的对象初始化时调用,决定要不要生成代理类的逻辑
    public boolean pointCutMath() {
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Class getTargetClass() {
        return targetClass;
    }

    public Object getTarget() {
        return target;
    }
}

6.GPAdvice通知接口定义

代码语言:javascript复制
@Data
public class GPAdvice {
    private Object aspect;
    private Method adviceMethod;
    private String throwName;

    public GPAdvice(Object aspect, Method adviceMethod) {
        this.aspect = aspect;
        this.adviceMethod = adviceMethod;
    }

}

7.接入getBean()方法与IOC容器衔接

找到GPApplicationContext的 getBean()方 法 ,我们知道getBean()中负责Bean初始化的方法其实 就是instantiateBean() ,我们在初始化时就可以确定是否返回原生Bean还是Proxy Bean。 代码实现 如下:

代码语言:javascript复制
    //创建真正的实例对象
    private Object instantiateBean(String beanName, GPBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            if(this.factoryBeanObjectCache.containsKey(beanName)){
                instance = this.factoryBeanObjectCache.get(beanName);
            }else {
                Class<?> clazz = Class.forName(className);
                //2、默认的类名首字母小写
                instance = clazz.newInstance();

                //==================AOP开始=========================
                //如果满足条件,就直接返回Proxy对象
                //1、加载AOP的配置文件
                GPAdvisedSupport config = instantionAopConfig(beanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);

                //判断规则,要不要生成代理类,如果要就覆盖原生对象
                //如果不要就不做任何处理,返回原生对象
                if(config.pointCutMath()){
                    instance = new GPJdkDynamicAopProxy(config).getProxy();
                }

                //===================AOP结束========================
                this.factoryBeanObjectCache.put(beanName, instance);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }

    private GPAdvisedSupport instantionAopConfig(GPBeanDefinition beanDefinition) {
        GPAopConfig config = new GPAopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new GPAdvisedSupport(config);
    }

8.LogAspect自定义切面配置

代码语言:javascript复制
@Slf4j
public class LogAspect {

    //在调用一个方法之前,执行before方法
    public void before(){
        //这个方法中的逻辑,是由我们自己写的
        log.info("Invoker Before Method!!!");
    }
    //在调用一个方法之后,执行after方法
    public void after(){
        log.info("Invoker After Method!!!");
    }

    public void afterThrowing(){

        log.info("出现异常");
    }
}

9.IModifyService业务接口定义

代码语言:javascript复制
/**
 * 增删改业务
 */
public interface IModifyService {

	/**
	 * 增加
	 */
	public String add(String name, String addr) throws Exception;
	
	/**
	 * 修改
	 */
	public String edit(Integer id, String name);
	
	/**
	 * 删除
	 */
	public String remove(Integer id);
	
}

10.ModifyService切面业务逻辑实现

代码语言:javascript复制
/**
 * 增删改业务
 */
@GPService
public class ModifyService implements IModifyService {

	/**
	 * 增加
	 */
	public String add(String name,String addr) throws Exception{
		throw new Exception("这是Tom故意抛出来的异常");

//		return "modifyService add,name="   name   ",addr="   addr;
	}

	/**
	 * 修改
	 */
	public String edit(Integer id,String name) {
		return "modifyService edit,id="   id   ",name="   name;
	}

	/**
	 * 删除
	 */
	public String remove(Integer id) {
		return "modifyService id="   id;
	}
	
}

至此AOP模块就大功告成。

参考资料:

1.《spring 5核心原理与30个类手写实战》谭勇德著

0 人点赞