学习笔记-Spring-aop中proxy代理模式

从前面学习的Proxy代理模式中,发现,spring-aop中相关功能与proxy是重合的,所以本次通过从动态代理的方向,去了解spring-aop

(个人理解)AOP面向切面编程,一个 工程其中包括很多个组件,每个组件都有自己的独特的功能,除了每个组件中自己的独特功能外,他们可能还共同承担这其它相似的功能,比如日志、事务管理和安全这样的核心服务,在调用不同组件的过程中,触发的这些逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SxxOwev-1589125555318)(proxy代理模式_springaop.assets/1589108775495.png)]

我们知道如果通过java api中的proxy类进行代理的化,可以通过实现同一个接口,然后通过接口去动态生成代理类,进行代理

这里我猜想:
spring的aop模块是封装了proxy

前置环境

为了更有效率的学习,建议将Spring-frameworker源码编译到本地,便于自己进行注释做笔记

我选择的环境是spring5.0.x版本,编译的源码选择从码云下载的zip包

https://gitee.com/mirrors/Spring-Framework/tree/5.0.x/ (选择自己所需的版本)

选择的构建工具是gradle,由于spring 2.0后都是通过gradle进行编译的,所以选择该构建工具

https://gradle.org/releases/ (选择自己所需的版本)

gradle选择的版本是5.6.4,其它的版本踩了不少坑…

构建方式分几种,

? 1.可以先预先编译后,在导入idea

? 2.可以通过idea自动编译,

预先编译踩了挺多坑,后面还是选择了idea自动编译

gradle编译spring源码网上有很多博客都有分享可以借鉴学习

gradle的学习笔记(待补充)

构建成功后,可以自己新建一个模块,添加对应的依赖后,写个测试代码,测试,如果跑通了,就说明成功了

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package it.luke.dao;

public class UserDao {
    String name;
    public void query(){
        System.out.println("query");
    }

    public UserDao() {
    }

    public UserDao(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package it.luke.test;

import it.luke.dao.UserDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();

        applicationContext.register(UserDao.class);
        applicationContext.refresh();
        UserDao userDao = (UserDao)applicationContext.getBean("userDao");
        System.out.println(userDao.getName());
    }
}
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="it.luke.dao.UserDao">
        <property name="name" value="张三"></property>
    </bean>

</beans>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-miHuOhPX-1589125555325)(proxy代理模式_springaop.assets/1589107512459.png)]

到这,说明spring源码构建成功了

这次主要是对spring中的aop模块进行学习

springaop可以实现和之前学习代理模式 proxy一样的功能,对目标类增强,是一种面向切面的思想

aop

  1. 目标类target:就是我们需要增强的那个类,targetclass
  2. 代理类proxy:就是我们自定义的那个代理的对象proxy
  3. 连接点joinPoint:连接点说白了就是我们的目标类里面所有的方法。
  4. 切入点pointCut:切入点说白了就是那些在目标类中我们实际上增强的方法。
  5. 织入weave:说白了就是将我们的代理类中需要增强的方法放入到目标类中去执行的过程,就叫织入
  6. 引介Introduction:是对我们的类中的方法或者属性进行一些创建的过程
  7. 通知advice:说白了就是将我们代理对象中的方法应用到目标类的过程中产生的结果。
  8. 切面aspect:说白了就是我们的所有的连接点和代理对象的方法组成在一起 构成了我们的切面

先实现一个简单的aop,然后对他进行源码的学习分析

由于要调用aop进行增强,除了spring自带的模块之外,还需要其它几个第三方的依赖,和maven工程一样,可以添加阿里的路径,让添加依赖下载的速度快点,不用每次都访问国外的仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
repositories {
    maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" }
    mavenCentral()
}
dependencies {
    compile project(':spring-context')
    compile project(':spring-core')
    compile project(':spring-beans')
    compile project(':spring-expression')
    compile project(':spring-aop')
    compile project(':spring-aspects')


    // https://mvnrepository.com/artifact/aopalliance/aopalliance
    compile group: 'aopalliance', name: 'aopalliance', version: '1.0'

// https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
    compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.6.8'


    testCompile group: 'junit', name: 'junit', version: '4.12'
}

除了spring几个依赖模块之外,还添加了aopalliance,aspectjweaver这两个依赖

aspectjweaver:简单理解,支持切入点表达式,支持aop相关注解等等(待了解)

aopalliance:提供springaop动态织入功能的框架的相关api依赖

检查模块

环境准备好后,我写一个aop的测试来分析

1.准备目标类(实现接口)

2.准备增强操作类

3.编写xml

目标类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package it.luke.aop;

public class UserDao implements UserDaoInter {
    String name;
    @Override
    public void query(){
        System.out.println("query");
    }

    public UserDao() {
    }

    public UserDao(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

接口

1
2
3
4
5
package it.luke.aop;

public interface UserDaoInter {
    void query();
}

增强

1
2
3
4
5
6
7
package it.luke.aop;

public class UserProxy {
    public void check(){
        System.out.println("权限校验");
    }
}

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!-- 定义我们的目标类 -->
    <bean id="userDao" class="it.luke.aop.UserDao"></bean>
    <!-- 定义我们自己的切面类 -->
    <bean id="UserProxyAspect" class="it.luke.aop.UserProxy"></bean>


    <aop:config >
        <!-- 这里的expression就是我们的切入点表示式,表示的是我们要对哪些类的哪些方法进行增强 -->
        <aop:pointcut expression="execution(* it.luke.aop.UserDao.query*(..))" id="pointCut1"/>
        <!-- 通过我们的aop:aspect来定义我们的切面类 -->
        <aop:aspect ref="UserProxyAspect">
            <!-- 这里的method指的是我们代理对象中的哪个方法 -->
            <aop:before method="check"  pointcut-ref="pointCut1"/>
        </aop:aspect>
    </aop:config>
</beans>

编写测试主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package it.luke.aop;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Aop_demo {
    public static void main(String[] args) {
        //读取配置
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        //获取目标bean
        UserDaoInter userDao = (UserDaoInter)context.getBean("userDao");
        System.out.println("123");
        //调用目标类的方法,查看有没有增强....配置中我们用的是before,所以会在调用query之前调用
        userDao.query();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KB6e4WDh-1589125555329)(proxy代理模式_springaop.assets/1589117621079.png)]

可以看到,程序成功运行了

1
2
3
4
    <!-- 定义我们的目标类 -->
    <bean id="userDao" class="it.luke.aop.UserDao"></bean>
    <!-- 定义我们自己的切面类 -->
    <bean id="UserProxyAspect" class="it.luke.aop.UserProxy"></bean>

在这里可以猜测:首先配置将我们配置中的定义的两个类添加到了容器中,让我们可以通过getbean获取

然后在加载aop相关的配置,获取相关的信息.获得到构建代理类的一些条件,复习一下proxy.newinstance的参数

1
2
3
4
5
 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {}

前面说的8个点现在已经准备好了三个:

1.目标类target

2.代理类proxy

3.连接点joinPoint

后面关于aop的一些配置,其实是对切入位置切入方法的一些配置

剩下还有5个:

4.切入点pointCut

5.织入weave

6.引介Introduction

7.通知advice

8.切面aspect

我们着重分析,这个过程中的代理类是如何构成的

ConfigBeanDefinitionParser.parse()

注册入口类和解析子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//BeanDefinition aop的注册的过程就是把它封装成BeanDefinition对象
public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compositeDef =
                new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compositeDef);

        configureAutoProxyCreator(parserContext, element);
        //解析出来aop:config 得到里面的<aop:pointcut>、<aop:advisor>、<aop:aspect> ,下面进行匹配
        List<Element> childElts = DomUtils.getChildElements(element);
        for (Element elt: childElts) {
            String localName = parserContext.getDelegate().getLocalName(elt);
            //<aop:pointcut>
            if (POINTCUT.equals(localName)) {
                parsePointcut(elt, parserContext);
            }
            //<aop:advisor>
            //Advisor Advice和Pointcut组成的独立的单元,用来定义只有一个通知和一个切入点的切面。
            else if (ADVISOR.equals(localName)) {
                parseAdvisor(elt, parserContext);
            }
            //<aop:aspect>
            else if (ASPECT.equals(localName)) {
                parseAspect(elt, parserContext);
            }
        }

        parserContext.popAndRegisterContainingComponent();
        return null;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aILkOSv5-1589125555332)(proxy代理模式_springaop.assets/1589120363955.png)]

1
List<Element> childElts = DomUtils.getChildElements(element);

代码执行到这的时候,开始遍历解析我们aop配置后里面的子节点,也就是pointcut和aspect

解析pointcut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
        String id = pointcutElement.getAttribute(ID);
        String expression = pointcutElement.getAttribute(EXPRESSION);

        AbstractBeanDefinition pointcutDefinition = null;

        try {
            this.parseState.push(new PointcutEntry(id));
            pointcutDefinition = createPointcutDefinition(expression);
            pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

            String pointcutBeanName = id;
            if (StringUtils.hasText(pointcutBeanName)) {

                parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
            }
            else {
                pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
            }
//注册到beanDefinitionNames容器
            parserContext.registerComponent(
                    new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
        }
        finally {
            this.parseState.pop();
        }

        return pointcutDefinition;
    }

解析aspect(aspect是一个切面。切面里面包含切入点和通知)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
private void parseAspect(Element aspectElement, ParserContext parserContext) {
        String aspectId = aspectElement.getAttribute(ID);
    //获取到我们配置的引用
        String aspectName = aspectElement.getAttribute(REF);//UserProxyAspect

        try {
            this.parseState.push(new AspectEntry(aspectId, aspectName));
            List<BeanDefinition> beanDefinitions = new ArrayList<>();
            List<BeanReference> beanReferences = new ArrayList<>();

            List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
            for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
                Element declareParentsElement = declareParents.get(i);
                beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
            }

            // We have to parse "advice" and all the advice kinds in one loop, to get the
            // ordering semantics right.
            //获取aspect下的子节点,进行遍历判断
            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                //判断是否为Advice --before...
                if (isAdviceNode(node, parserContext)) {
                    if (!adviceFoundAlready) {
                        adviceFoundAlready = true;
                        if (!StringUtils.hasText(aspectName)) {
                            parserContext.getReaderContext().error(
                                    "<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
                                    aspectElement, this.parseState.snapshot());
                            return;
                        }
                        beanReferences.add(new RuntimeBeanReference(aspectName));
                    }
                    //parseAdvice 解析节点数据后封装成AbstractBeanDefinition
                    AbstractBeanDefinition advisorDefinition = parseAdvice(
                            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                    //将信息封装进beanDefinitions中
                    beanDefinitions.add(advisorDefinition);
                }
            }
            //将解析出来的封装成AspectComponentDefinition
            AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
            parserContext.pushContainingComponent(aspectComponentDefinition);
            //这里的pointcuts 是aspect作用域中的pointcut
            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                parsePointcut(pointcutElement, parserContext);
            }

            parserContext.popAndRegisterContainingComponent();
        }
        finally {
            this.parseState.pop();
        }
    }

解析完后的信息会被封装起来,存到容器中,后面会进行判断,是否有需要进行aop的类的时候会调用检验

既然信息都封装起来了,那么他什么时候会进行创建代理类呢

1
2
3
4
5
6
7
8
9
10
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

通过wrapIfNecessary这个方法进行的目标类和代理类的替换

其实这里是有一个循环判断,Boolean.FALSE.equals(this.advisedBeans.get(cacheKey)如果不是adviseBeans他便会返回,去下一个bean进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

这边我们代理的目标类是UserDao,所以我们的UserDao这个bean就进入这段逻辑了

1
2
3
4
5
6
7
8
9
10
    protected Object[] getAdvicesAndAdvisorsForBean(
            Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {

        List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
        //通过这个判断,来选择是否需要代理
        if (advisors.isEmpty()) {
            return DO_NOT_PROXY;
        }
        return advisors.toArray();
    }

很明显这里我们是需要代理的,所以我们也正常的进入了下面的逻辑…

Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

看到createProxy()就可以猜到了,创建代理类的逻辑十有八九在这里面了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
            @Nullable Object[] specificInterceptors, TargetSource targetSource) {

        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        //如果当前容器有proxyfactory 就用当前的,如果没有则为初始化的factory
        proxyFactory.copyFrom(this);

        //这里对proxyFactory进行了一系列的构建,最后通过getProxy这个方法进行下一步的创建
        if (!proxyFactory.isProxyTargetClass()) {
            if (shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            }
            else {
                evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);

        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

    //传入代理类的类加载器作为Proxy.newinstance的参数之一(推测...)
        return proxyFactory.getProxy(getProxyClassLoader());
    }
1
2
3
4
5
6
7
8
9
@Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

代码走到这里可以看到,调用了java的proxy这个类来创建代理类了,走到这里就验证了我们最开始的猜想.

前面还有很多关于容器配置的一些构建,我都跳过了,只贴了一部分的关键代码,提供自己阅读,在走读过程中适当的在源码中添加注释,这样可以减少二次走读的理解难度.

后面便是将原有的UserDao给替换成新的代理的bean,这样再通过我们在通过getbean的时候,调用对象的方法便会进行一系列的检测,然后跳到代理类的方法里面去

roxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

1
2
3
4
5
代码走到这里可以看到,调用了java的proxy这个类来创建代理类了,走到这里就验证了我们最开始的猜想.

前面还有很多关于容器配置的一些构建,我都跳过了,只贴了一部分的关键代码,提供自己阅读,在走读过程中适当的在源码中添加注释,这样可以减少二次走读的理解难度.

后面便是将原有的UserDao给替换成新的代理的bean,这样再通过我们在通过getbean的时候,调用对象的方法便会进行一系列的检测,然后跳到代理类的方法里面去