专栏首页java干货Small Spring系列八:aop (一)

Small Spring系列八:aop (一)

路漫漫其修远兮 吾将上下而求索。

概述

我们终于不辱使命完成了Spring的注解注入,接下来我们要实现更为关键aop部分,在这开始之前你需要了解什么事aop以及aop的常用术语,参考链接

准备工作

bean-v5.xml

我们使用xml配置的方式实现aop

<?xml version="1.0" encoding="UTF-8"?>
<!-- 增加namespace-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop">

    <!-- 扫描哪个包下面的文件 -->
    <context:component-scan base-package="com.niocoder.dao.v5,com.niocoder.service.v5">

    </context:component-scan>

    <!-- 模拟 TransactionManager-->
    <bean id="tx" class="com.niocoder.tx.TransactionManager"/>

    <!-- aop 配置-->
    <aop:config>
        <!-- aop 核心配置 依赖tx-->
        <aop:aspect ref="tx">
            <!-- 切入点配置 包下面的placeOrder 方法-->
            <aop:pointcut id="placeOrder"
                          expression="execution(* com.niocoder.service.v5.*.placeOrder(..))"/>
            <!-- 通知配置,-->
            <aop:before pointcut-ref="placeOrder" method="start"/>
            <aop:after-returning pointcut-ref="placeOrder" method="commit"/>
            <aop:after-throwing pointcut-ref="placeOrder" method="rollback"/>
        </aop:aspect>

    </aop:config>
</beans>

AccountDao

package com.niocoder.dao.v5;

import com.niocoder.stereotype.Component;

@Component
public class AccountDao {
}

ItemDao

package com.niocoder.dao.v5;

import com.niocoder.stereotype.Component;

@Component
public class ItemDao {
}

NioCoderService

新增placeOrder方法用于测试aop,MessageTracker用于测试TransactionManager是否执行。

package com.niocoder.service.v5;

import com.niocoder.beans.factory.Autowired;
import com.niocoder.dao.v5.AccountDao;
import com.niocoder.dao.v5.ItemDao;
import com.niocoder.stereotype.Component;
import com.niocoder.util.MessageTracker;

@Component("nioCoder")
public class NioCoderService {

    @Autowired
    AccountDao accountDao;

    @Autowired
    ItemDao itemDao;

    public NioCoderService() {

    }

    public AccountDao getAccountDao() {
        return accountDao;
    }

    public ItemDao getItemDao() {
        return itemDao;
    }

    public void placeOrder() {
        System.out.println("place order");
        MessageTracker.addMsg("place order");
    }
}

MessageTracker

工具类用于记录msg

package com.niocoder.util;

import java.util.ArrayList;
import java.util.List;

/**
 * 记录msg
 */
public class MessageTracker {
    private static List<String> MESSAGES = new ArrayList<>();

    public static void addMsg(String msg) {
        MESSAGES.add(msg);
    }

    public static void clearMsgs() {
        MESSAGES.clear();
    }

    public static List<String> getMsgs() {
        return MESSAGES;
    }

}

TransactionManager

模拟事务的执行。

package com.niocoder.tx;

import com.niocoder.util.MessageTracker;
import org.junit.Before;

/**
 * 用于测试AOP顺序
 */
public class TransactionManager {
    @Before
    public void setUp() {
        MessageTracker.clearMsgs();
    }

    public void start() {
        System.out.println("start tx");
        MessageTracker.addMsg("start tx");
    }

    public void commit() {
        System.out.println("commit tx");
        MessageTracker.addMsg("commit tx");
    }

    public void rollback() {
        System.out.println("rollback tx");
    }
}

Pointcut

<aop:pointcut id="placeOrder"
                          expression="execution(* com.niocoder.service.v5.*.placeOrder(..))"/>

我们先从最简单的Pointcut开始,很明显我们需要一个类来表达这个概念。当给定一个类的方法,判断该方法是否符合pointcut的表达式。设计类图如下:

关于expression表达式的解析,我们使用org.aspectj.aspectjweaver来实现。所以需要在pom.xml中添加依赖

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>

MethodMatcher

给定一个类的方法,判断是否匹配。

package com.niocoder.aop;

import java.lang.reflect.Method;

public interface MethodMatcher {

    /**
     * 给定一个方法判断是否匹配
     *
     * @param method
     * @return
     */
    boolean matches(Method method /*,Class<?> targetClass*/);
}

Pointcut

获取expressionMethodMatcher

package com.niocoder.aop;

public interface Pointcut {

    /**
     * 获取MethodMatcher 判断方法时候匹配
     *
     * @return
     */
    MethodMatcher getMethodMatcher();

    /**
     * 获取expression表达式
     *
     * @return
     */
    String getExpression();
}

AspectJExpressionPointcut

实现MethodMatcherPointcut,使用aspectj实现。

package com.niocoder.aop.aspectj;

import com.niocoder.aop.MethodMatcher;
import com.niocoder.aop.Pointcut;
import com.niocoder.util.ClassUtils;
import com.niocoder.util.StringUtils;
import org.aspectj.weaver.reflect.ReflectionWorld;
import org.aspectj.weaver.tools.*;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;


public class AspectJExpressionPointcut implements Pointcut, MethodMatcher {
    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();

    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

    /**
     * 条件表达式 即 expression="execution(* com.niocoder.service.v5.*.placeOrder(..))"
     */
    private String expression;

    private PointcutExpression pointcutExpression;

    private ClassLoader pointcutClassLoader;

    public AspectJExpressionPointcut() {

    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return this;
    }

    @Override
    public String getExpression() {
        return this.expression;
    }

    public void setExpression(String expression) {
        this.expression = expression;
    }

    @Override
    public boolean matches(Method method/*, Class<?> targetClass*/) {

        // 判断是否设置条件表达式
        checkReadyToMatch();

        // 根据传入的method 返回shadowatch
        ShadowMatch shadowMatch = getShadowMatch(method);

        // 判断是否匹配
        if (shadowMatch.alwaysMatches()) {
            return true;
        }

        return false;
    }

    private ShadowMatch getShadowMatch(Method method) {

        ShadowMatch shadowMatch = null;
        try {
            shadowMatch = this.pointcutExpression.matchesMethodExecution(method);
        } catch (ReflectionWorld.ReflectionWorldException ex) {

            throw new RuntimeException("not implemented yet");
        }
        return shadowMatch;
    }

    private void checkReadyToMatch() {
        if (getExpression() == null) {
            throw new IllegalStateException("Must set property 'expression' before attempting to match");
        }
        if (this.pointcutExpression == null) {
            this.pointcutClassLoader = ClassUtils.getDefaultClassLoader();
            this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
        }
    }

    private PointcutExpression buildPointcutExpression(ClassLoader classLoader) {

        PointcutParser parser = PointcutParser
                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
                        SUPPORTED_PRIMITIVES, classLoader);

        return parser.parsePointcutExpression(replaceBooleanOperators(getExpression()),
                null, new PointcutParameter[0]);
    }


    private String replaceBooleanOperators(String pcExpr) {
        String result = StringUtils.replace(pcExpr, " and ", " && ");
        result = StringUtils.replace(result, " or ", " || ");
        result = StringUtils.replace(result, " not ", " ! ");
        return result;
    }
}

PointcutTest

测试Pointcut

public class PointcutTest {

    @Test
    public void testPointCutTest() throws Exception {
        String expression = "execution(* com.niocoder.service.v5.*.placeOrder(..))";
        AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
        pc.setExpression(expression);

        MethodMatcher mm = pc.getMethodMatcher();

        {
            Class<?> targetClass = NioCoderService.class;

            Method placeOrder = targetClass.getMethod("placeOrder");
            Assert.assertTrue(mm.matches(placeOrder));

            Method getAccountDao = targetClass.getMethod("getAccountDao");
            Assert.assertFalse(mm.matches(getAccountDao));
        }

        {
            Class<?> targetClass = com.niocoder.service.v4.NioCoderService.class;
            Method placeOrder = targetClass.getMethod("getAccountDao");
            Assert.assertFalse(mm.matches(placeOrder));
        }
    }
}

我们已经实现了一个简单Pointcut表达式,关于更多Pointcut可以参考链接

定位Method

<!-- 模拟 TransactionManager-->
    <bean id="tx" class="com.niocoder.tx.TransactionManager"/>
    <!-- aop 配置-->
    <aop:config>
        <!-- aop 核心配置 依赖tx-->
        <aop:aspect ref="tx">
            <!-- 通知配置,-->
            <aop:before pointcut-ref="placeOrder" method="start"/>
        </aop:aspect>

    </aop:config>

aop中,我们需要根据beanNametx,方法名称为start来定位到TransactionManager.start()方法。因此我们需要一个类,根据targetBeanNamemethodName返回Method对象。因需要根据beanName返回对象,所以在此类中需要设置BeanFactory,并在BeanFactory中新增Class<?> getType(String name)方法。

BeanFactory

新增Class<?> getType(String name) 方法,根据beanName返回Class对象。

package com.niocoder.beans.factory;

/**
 * 创建bean的实例
 *
 * @author zhenglongfei
 */
public interface BeanFactory {

    /**
     * 获取bean的实例
     *
     * @param beanId
     * @return
     */
    Object getBean(String beanId);

    /**
     * 根据bean 名称 返回 class 对象
     *
     * @param name
     * @return
     */
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
}

DefaultBeanFactory

DefaultBeanFactory中实现getType方法。

public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory, BeanDefinitionRegistry {
.......
    @Override
    public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        BeanDefinition bd = this.getBeanDefinition(name);
        if (null == bd) {
            throw new NoSuchBeanDefinitionException(name);
        }
        resolveBeanClass(bd);

        return bd.getBeanClass();
    }
}

AbstractApplicationContext

因为ApplicationContext继承BeanFactory接口,所以在抽象类AbstractApplicationContext也需要实现getType方法。

public abstract class AbstractApplicationContext implements ApplicationContext{
 @Override
    public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return this.factory.getType(name);
    }
}

MethodLocatingFactory

根据targetBeanNamemethodName返回Method对象。

package com.niocoder.aop.config;

import com.niocoder.beans.BeanUtils;
import com.niocoder.beans.factory.BeanFactory;
import com.niocoder.util.StringUtils;

import java.lang.reflect.Method;

public class MethodLocatingFactory {

    private String targetBeanName;

    private String methodName;

    private Method method;

    public void setTargetBeanName(String targetBeanName) {
        this.targetBeanName = targetBeanName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }


    /**
     * 设置beanFactory 只有beanFactory才能根据bean的名称返回bean的class 对象
     * 设置时需要前置判断,beanName 和 methodName
     *
     * @param beanFactory
     */
    public void setBeanFactory(BeanFactory beanFactory) {

        if (!StringUtils.hasText(this.targetBeanName)) {
            throw new IllegalArgumentException("Property 'targetBeanName' is required");
        }
        if (!StringUtils.hasText(this.methodName)) {
            throw new IllegalArgumentException("Property 'methodName' is required");
        }

        Class<?> beanClass = beanFactory.getType(this.targetBeanName);
        if (beanClass == null) {
            throw new IllegalArgumentException("Can't determine type of bean with name '" + this.targetBeanName + "'");
        }

        // 给method 赋值
        this.method = BeanUtils.resolveSignature(this.methodName, beanClass);

        if (this.method == null) {
            throw new IllegalArgumentException("Unable to locate method [" + this.methodName +
                    "] on bean [" + this.targetBeanName + "]");
        }
    }

    /**
     * 返回Method对象
     *
     * @return
     */
    public Method getObject() {
        return this.method;
    }
}

MethodLocatingFactoryTest

测试 MethodLocatingFactory

public class MethodLocatingFactoryTest {

    @Test
    public void testGetMethod() throws Exception {
        DefaultBeanFactory factory = new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinition(new ClassPathResource("bean-v5.xml"));

        MethodLocatingFactory methodLocatingFactory = new MethodLocatingFactory();
        methodLocatingFactory.setTargetBeanName("tx");
        methodLocatingFactory.setMethodName("start");
        methodLocatingFactory.setBeanFactory(factory);

        Method start = methodLocatingFactory.getObject();

        Assert.assertTrue(TransactionManager.class.equals(start.getDeclaringClass()));
        Assert.assertTrue(start.equals(TransactionManager.class.getMethod("start")));

        TransactionManager tx = (TransactionManager) factory.getBean("tx");
        start.invoke(tx);
    }
}

代码下载

代码下载

参考资料


从零开始造Spring

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Small Spring系列十一:aop (四)

    在前四篇,我们已经实现了使用Cglib实现了aop动态代理。但是在spring中如果代理对象实现了接口,则默认使用jdk动态代理,也可以通过配置强制使用cgli...

    java干货
  • Small Spring系列九:aop (二)

    在Small Spring系列八:aop (一)中,我们实现了Pointcut和MethodLocatingFactory,Pointcut根据给定一个类的方法...

    java干货
  • Small Spring系列一:BeanFactory(一)

    Spring是一个开放源代码的设计层面框架,它解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。

    java干货
  • 《Spring 手撸专栏》| 开篇介绍,我要带新人撸 Spring 啦!

    是的,在写了4篇关于Spring核心源码的面经内容后,我决定要去手撸一个Spring了。为啥这么干呢?因为所有我想写的内容,都希望它是以理科思维理解为目的的学会...

    小傅哥
  • Small Spring系列二:BeanFactory(二)

    在Small Spring系列一:BeanFactory(一)中,我们用DefaultBeanFactory读取bean.xlm中的bean信息,并且也实现了B...

    java干货
  • 使用aop统一处理controller中的异常及日志

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技...

    呼延十
  • 《Spring 手撸专栏》第 13 章:行云流水,把AOP动态代理,融入到Bean的生命周期

    在电视剧《楚汉传奇》中有这么一段刘邦与韩信的饮酒对话,刘邦问韩信我那个曹参读过书见过世面能带多少兵,韩信说能带一万五,又补充说一万五都吃力。刘邦又一一说出樊哙、...

    小傅哥
  • small-spring 代码贡献者3个月,敢说精通Spring了,分享我的总结!

    这个与我们码农朝夕相处的 Spring,就像睡在你身边的媳妇,你知道找她要吃、要喝、要零花钱、要买皮肤。但你不知道她的仓库共有多少存粮、也不知道她是买了理财还是...

    小傅哥
  • 目录:SpringBoot 核心技术

    恒宇少年
  • 手写Spring框架,是时候撸个AOP与Bean生命周期融合了!

    在电视剧《楚汉传奇》中有这么一段刘邦与韩信的饮酒对话,刘邦问韩信我那个曹参读过书见过世面能带多少兵,韩信说能带一万五,又补充说一万五都吃力。刘邦又一一说出樊哙、...

    小傅哥
  • Java开发Spring笔记第二天

    今日内容 AOP的概述 AOP 的底层实现 Spring 的AOP 使用AspectJ 实现AOP Spring JdbcTemplate 使用 1.2 Spr...

    Java帮帮
  • 32天高效突击:开源框架+性能优化+微服务架构+分布式,面阿里获P7(脑图、笔记、面试考点全都有)

    今年似乎因为疫情影响,时间过得特别快,对于需要跳槽换工作的人来,更觉得有些突然,似乎金三银四和金九银四还没开始准备好,就匆匆过去。加上今年的大环境不佳,所以大部...

    Java程序猿阿谷
  • Spring IOC 容器源码分析系列文章导读

    Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已...

    田小波
  • 面试官:展开说说,Spring中Bean对象是如何通过注解注入的?

    你听过扰动函数吗?你写过斐波那契(Fibonacci)散列吗?你实现过梅森旋转算法吗?怎么 没听过这些写不了代码吗!不会的,即使没听过你一样可以写的了代码,比如...

    小傅哥
  • 吃透互联网大厂必问的100道【Spring全家桶】高频面试真题,金九银十稳了!

    这篇文章主要是记录一下自己的面试经历,分享一些小伙伴们都很关注的面试题,然后文章末尾也会推荐一些电子书籍,完全免费推荐的哈,我个人感觉不错的,可以提升技术的,当...

    Java程序猿
  • Spring框架系列之AOP思想

    微信公众号:compassblog 欢迎关注、转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1、AOP概述 (1)、什么是 AOP AOP 为 As...

    compassblog
  • Spring IOC 容器源码分析系列文章导读

    Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已...

    田小波
  • [ SSH框架 ] Spring框架学习之二(Bean的管理和AOP思想)

    Kevin_Zhang
  • 【Spring开发】—— Spring Core

    前言  最近由于一些工作的需要,还有自己知识的匮乏再次翻开spring。正好整理了一下相关的知识,弥补了之前对spring的一些错误认知。这一次学习,更加...

    用户1154259

扫码关注云+社区

领取腾讯云代金券