SpringMvc手写简单实现篇 - AOP切面编程篇

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

为看过上篇文章的,请移步上篇文章吧。 本文适合使用过AOP切面编程的人阅读。

什么是Aop

AOP全称(Aspect Oriented Programming)面向切面编程的简称。

AOP通过预编译方式和运行期动态代理实现,这种在运行时,在不修改源代码的情况下,动态地将代码织入到类的指定方法、指定位置上的编程思想就是面向切面的编程

AOP的作用:

做一些很多类都在做的,重复做的事,减少重复代码

日志记录、性能统计、事务处理、异常处理

AOP场景的通知:

  • 前置通知,方法执行前调用,注解@Before
  • 后置通知,方法执行后调用,注解@After
  • 环绕通知,方法执行前、后调用,注解@Around
  • 返回通知,方法返回后调用,注解@AfterReturning
  • 异常通知,方法执行异常调用,注解@AfterThrowing

大概了解这是个什么东西后,我们就在上节的代码中实现AOP吧

AOP切面手写简单版

1.预先准备

  • 结构变化,新加切面类LogAspect,aop调用链TAdvice,aop切面信息TAopConfig,代理封装类TJDKDynamicAopProxy,model暂时未织入,仅当实体类使用

AOP结构图

  • application.properties 配置添加,这里直接配置写死,省去解析注解得到对应的内容
#AOP切点
pointCut=public .* com.xxx.demo.service.impl..*(.*)
#AOP执行的类
aspectClass=com.xxx.demo.aspect.LogAspect
#AOP前置方法
aspectBefore=before
#AOP后置方法
aspectAfter=after
#AOP异常方法
aspectAfterThrowing=afterThrowing
  • 新增切面类 LogAspect
public class LogAspect {
    //方法之前执行before方法
    public void before(JoinPoint point) {
        System.out.println("方法之前调用:"+point.toString());
    }
    //方法之后执行after方法
    public void after(JoinPoint point) {
        System.out.println("方法之后调用:"+point.toString());
    }
    public void afterThrowing(JoinPoint point) {
        System.out.println("出现异常调用:"+point.toString());
    }
}
  • aop目录,存储原始的class信息
/**
 * AOP切面调用链封装
 */
@Data
public class TAdvice {
    private Object aspectClass;
    private Method aspectMethod;
    private String throwName;

    public TAdvice(Object aspectClass, Method aspectMethod) {
        this.aspectClass = aspectClass;
        this.aspectMethod = aspectMethod;
    }
}
  • 切面请求信息 JoinPoint,源码后完善
@Data
public class JoinPoint {
    Object target;
    Method method;
    Object[] args;
    Object result;
    String throwName;
    public Object proceed() throws Exception {
        return method.invoke(target,this);
    }
}

2.TestApplicationContext上下文内容修改

  • 初始化 doInstance() 代码修改 initAopConfig
 private void doInstance() {
     for (String className : this.classNames) {
         try {
             Class<?> clazz = Class.forName(className);

             //只有加了注解的类才初始化
             if (!clazz.isAnnotationPresent(TController.class) && !clazz.isAnnotationPresent(TService.class)) {
                 continue;
             }
             //如果满足AOP切面条件 此处应该生成代理对象 真实调用应该有invoke实现
             Object instance = initAopConfig(clazz);

             //保存到IOC容器 beanName -> instance 生成bean name 首字母小写
             String beanName = toFristLowerCase(clazz.getSimpleName());
             //如果是service 看是否有自定义名称
             if(clazz.isAnnotationPresent(TService.class)){
                 TService service = clazz.getAnnotation(TService.class);
                 if(!"".equals(service.value())){
                     beanName = service.value();
                 }
             }

             //比如beanName 相同的暂不考虑 主要是体现思想
             this.ioc.put(beanName,instance);

             //如果是接口实现的 需要把接口全路径对应到service
             Class<?>[] interfaces = clazz.getInterfaces();
             for (Class<?> i : interfaces) {
                 this.ioc.put(i.getName(),instance);
             }

         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 }
  • initAopConfig(clazz); AOP初始化
private Object initAopConfig(Class<?> clazz) throws Exception {
    Object instance = clazz.newInstance();

    //获取切面的切点 正则匹配全路径类名 匹配成功则生成代理类 不写配置可在这里写死
    String pointCut = this.contextConfig.getProperty("pointCut");
    //把字符串里面的符号 替换成 可识别的正则符号
 //public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
    String regxMethodStr = pointCut.replaceAll("\\.","\\\\.")
            .replaceAll("\\\\.\\*",".*")
            .replaceAll("\\(","\\\\(")
            .replaceAll("\\)","\\\\)");
    //类名是 class com.xxx.demo.service.impl.HelloServiceImp 所以匹配类名做如下处理
    //public .* com\.xxx\.demo\.service\.impl\..*
    String regxClassStr = regxMethodStr.substring(0,regxMethodStr.lastIndexOf("\\("));
    //class com\.xxx\.demo\.service\.impl\..*
    Pattern classPattern = Pattern.compile("class " + regxClassStr.substring(regxClassStr.lastIndexOf(" ")+1));
    //类的正则
    if(classPattern.matcher(instance.getClass().toString()).matches()){
        //匹配成功就创建代理对象 保存切面方法
        TAopConfig config = new TAopConfig();
        config.setPointCut(pointCut);
        config.setAspectClass(this.contextConfig.getProperty("aspectClass"));
        config.setAspectBefore(this.contextConfig.getProperty("aspectBefore"));
        config.setAspectAfter(this.contextConfig.getProperty("aspectAfter"));
        config.setAspectAfterThrowing(this.contextConfig.getProperty("aspectAfterThrowing"));
        //匹配方法的正则 public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
        Pattern methodPattern = Pattern.compile(regxMethodStr);
        config.setMethodPattern(methodPattern);
        config.setTarget(instance);
        //保存原始类时解析切面信息
        config.setTargetClass(clazz);
        //匹配上的生成代理对象
        instance = new TJDKDynamicAopProxy(config).getProxy();
    }
    return instance;
}

3.保存原始类以及切面信息

@Data
public class TAopConfig {
    //切面信息保存
    String pointCut;
    String aspectClass;
    String aspectBefore;
    String aspectAfter;
    String aspectAfterThrowing;

    Pattern methodPattern;

    //原始实例和clazz信息
    Object target;
    Class<?> targetClass;

    Map<Method,Map<String,TAdvice>> adviceCacheMap = new HashMap<Method, Map<String, TAdvice>>();

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        //把切面信息组成一个map 给invoke调用
        parse();
    }

    //把切面信息组成一个map 给invoke调用
    /**
     * 初始化切面类
     * 获取对应方法存入map
     * 通过
     */
    private void parse() {
        try {
         //初始化切面类
            Class<?> clazz = Class.forName(this.aspectClass);
            Method[] methods = clazz.getMethods();
            Map<String,Method> aspectMap = new HashMap<String, Method>();
            for (Method method : methods) {
                aspectMap.put(method.getName(),method);
            }
            Map<String,TAdvice> adviceMap = new HashMap<String, TAdvice>();
            Object aspectInstance = clazz.newInstance();
            //取出满足切点类的所有方法
            for (Method method : this.targetClass.getMethods()) {
                String methodName = method.toString();
                if(methodName.contains("throws")){
                    methodName = methodName.substring(0,methodName.lastIndexOf("throws")).trim();
                }
                //如果方法满足切点,就织入代码
                //public .* com\.xxx\.demo\.service\.impl\..*\(.*\)
                if(methodPattern.matcher(methodName).matches()){
                    if(null != this.aspectBefore){
                        adviceMap.put("before",new TAdvice(aspectInstance,aspectMap.get(this.aspectBefore)));
                    }

                    if(null != this.aspectAfter){
                        adviceMap.put("after",new TAdvice(aspectInstance,aspectMap.get(this.aspectAfter)));
                    }

                    if(null != this.aspectAfterThrowing){
                        adviceMap.put("afterThrow",new TAdvice(aspectInstance,aspectMap.get(this.aspectAfterThrowing)));
                    }
                    this.adviceCacheMap.put(method,adviceMap);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //method 实际为代理对象的方法 这里需要去原targetClass拿到原始方法 才能在Map中找出映射关系
    public  Map<String, TAdvice> getAdviceMap(Method method) throws NoSuchMethodException {

            Map<String, TAdvice> adviceMap = adviceCacheMap.get(method);
            if(adviceMap == null){
                Method method1 = this.targetClass.getMethod(method.getName(), method.getParameterTypes());
                adviceMap = adviceCacheMap.get(method1);
                this.adviceCacheMap.put(method,adviceMap);
            }
            if(adviceMap == null){
                return new HashMap<String, TAdvice>();
            }
            return adviceMap;
    }
}

4.代理封装类

public class TJDKDynamicAopProxy implements InvocationHandler {

    private TAopConfig config;

    public TJDKDynamicAopProxy(TAopConfig config) {
        this.config = config;
    }

    /**
     * 代理会走到这个方法
     * 1.获取aspect AopConfig配置,切面信息和原始实例类
     * 2.保存method -> list<TAopConfig>配置 到map 可能多个切面
     * 3.依次调用 invoke
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        Map<String, TAdvice> adviceMap = new HashMap<String, TAdvice>();

        Object result = null;
        JoinPoint joinPoint = new JoinPoint();
        joinPoint.setArgs(args);
        try {
         //获取aopConfig中存储的切面信息map
            adviceMap = config.getAdviceMap(method);
            invokeAspect(adviceMap.get("before"), joinPoint);
   //真实方法调用
            result = method.invoke(config.getTarget(), args);
            joinPoint.setResult(result);
            invokeAspect(adviceMap.get("after"), joinPoint);
        } catch (Exception e) {
            joinPoint.setThrowName(e.getCause().getMessage());
            invokeAspect(adviceMap.get("afterThrow"), joinPoint);
            throw e;
        }
        return result;
    }

    private void invokeAspect(TAdvice advice, JoinPoint joinPoint) {
        try {
            if (advice == null) return;
            //正常来说是 JoinPoint.invoke 来实现,这里简化 源码后再补充
            advice.getAspectMethod().invoke(advice.getAspectClass(), joinPoint);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * JDK通过接口创建代理对象 无接口的暂时不考虑
     *
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                this.config.getTargetClass().getInterfaces(), this);
    }
}

5.运行test

还是之前的test

public static void main(String[] args) {
    TestApplicationContext context = new TestApplicationContext("classpath:application.properties");
    HelloAction action = (HelloAction) context.getBean(HelloAction.class);
    String hello = action.hello("张三");
    System.out.println(hello);
}

//打印输出
application is init
方法之前调用:JoinPoint(target=null, method=null, args=[张三], result=null, throwName=null)
方法之后调用:JoinPoint(target=null, method=null, args=[张三], result=my name is 张三, throwName=null)
my name is 张三

总结

在实例化对象时判断该类满不满足切面切点,如果满足则创建代理类,在对应的代理类中存有原始类的信息,从而在invoke中获取原始类的信息调用真实的方法

以上内容实现了,接下来MVC还是问题吗?

以上就是本章的全部内容了。本文为学习记录,不支持转载

上一篇:SpringMvc手写简单实现篇 - IOC容器、DI依赖注入篇 下一篇:SpringMvc手写简单实现篇 - MVC完结篇

少壮不努力,老大徒伤悲

猜你喜欢

转载自juejin.im/post/7126350114527903758
今日推荐