飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

Spring的轻量级实现

时间:2021-12-04  作者:greyzeng  

作者: Grey

原文地址:Spring的轻量级实现

本文是参考公众号:码农翻身 的从零开始造Spring 教程的学习笔记

源码

github

开发方法

使用TDD的开发方法,TDD的开发流程是:

  1. 写一个测试用例

  2. 运行:失败

  3. 写Just enough的代码,让测试通过

  4. 重构代码保持测试通过,

然后循环往复。

说明

  • 仅实现核心功能

  • 基于spring-framework-域名.RELEASE版本

通过XML实例化一个对象

解析XML文件,拿到Bean的id和完整路径,通过反射方式实例化一个对象。

XML格式如下,文件名为:bean-域名

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://域名/schema/beans"
       xmlns:xsi="http://域名/2001/XMLSchema-instance"
       xsi:schemaLocation="http://域名/schema/beans
       http://域名/schema/beans/spring-域名">
    <bean id="userService" class="域名域名Service"></bean>
</beans>

需要解析上述XML并生成userService对象,调用者只需要做如下调用即可:

public class BeanFactoryV1Test {
    @Test
    public void testGetBean() {
        BeanFactory factory = new DefaultBeanFactory("bean-域名");
        UserService userService = (UserService) 域名ean("userService");
        assertNotNull(userService);
    }
}

思路为:

解析XML,并把XML中的类通过反射方式生成对象,最后,把这个生成的对象放到一个Map中,其中Map的key为beanId,如上例就是:userService, Map的Value是UserService的全路径域名域名Service

实现细节参考代码见:step1

基础工作和基本封装

  • 增加日志支持:log4j2 + SLF4j
  • 增加异常处理,所有异常的父类设计为BeansException
  • 封装BeanDefinition

由于DefaultBeanFactory中的BEAN_MAP目前只包括了beanClassName信息,后续如果要扩展其他的信息,肯定需要增加字段,所以我们需要抽象出一个接口BeanDefinition,方便后续扩展其他的字段。

  • 封装Resource

在BeanFactory初始化的时候,传入的是XML格式的配置信息,比如bean-域名, Spring会把这个抽象成一个Resource,常见Resource有
FileSystemResource: 从文件地址读配置
ClassPathResource: 从classpath下读配置
BeanFactory在创建Bean的时候,只关注Resource即可。

实现细节参考代码见:vstep4-2-resource

封装XML的解析逻辑和Bean的注册逻辑

设计XmlBeanDefinitionReader,用于解析XML,传入Resource,即可获取所有BeanDefinition

public void loadBeanDefinitions(Resource resource) {
    // 从Resource中获取所有的BeanDefinition
    // 注册到BEAN_MAP中
}

由于要把BeanDefinition放入BEAN_MAP中,所以XmlBeanDefinitionReader需要持有一个DefaultBeanFactory,且DefaultBeanFactory需要有注册BeanDefinition和获取BeanDefintion的能力,这样DefaultBeanFactory的职责就不单一了,所以需要抽象出一个BeanDefinitionRegistry,这个BeanDefinitionRegistry专门负责注册BeanDefinition和获取BeanDefintion

public interface BeanDefinitionRegistry {
    /**
     * 注册Bean
     * @param beanId
     * @param beanDefinition
     */
    void registerBeanDefinition(String beanId, BeanDefinition beanDefinition);
}

XmlBeanDefinitionReader只需要持有BeanDefinitionRegistry,即可将解析生成的BeanDefinition注入BEAN_MAP中。

实现细节参考代码见:vstep5-final

单例多例模式的配置实现

XML文件中会增加一个属性,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://域名/schema/beans"
       xmlns:xsi="http://域名/2001/XMLSchema-instance"
       xsi:schemaLocation="http://域名/schema/beans
       http://域名/schema/beans/spring-域名">

    <bean id="userService" class="域名域名Service"></bean>
    <bean id="orgService" class="域名域名ervice" scope="prototype"></bean>
</beans>

其中orgService这个bean配置成了prototype的属性,所以在BeanDefinition这个数据结构要增加是否单例,是否多例的逻辑

public interface BeanDefinition {
    ...
    boolean isSingleton();
    boolean isPrototype();
    ...
}

DefaultBeanFactory调用getBean的时候,判断是否单例,如果是单例,则复用对象,如果是多例,则new新的对象。

@Override
    public Object getBean(String beanId) {
        // TODO bean存在与否判断
        // TODO 异常处理
        // TODO 构造函数带参数
        BeanDefinition definition = 域名(beanId);
        if (域名ngleton()) {
            Object bean = 域名ingleton(beanId);
            if(bean == null){
                bean = createBean(definition);
                域名sterSingleton(beanId, bean);
            }
            return bean;
        }
        return createBean(definition);
    }

抽象SingletonBeanRegistry这个接口,专门用于注册和获取单例对象,

public interface SingletonBeanRegistry {
    void registerSingleton(String beanName, Object singletonObject);
    Object getSingleton(String beanName);
}

DefaultSingletonBeanRegistry实现这个接口,实现对单例对象的注册

public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
    // TODO 考虑线程安全的容器
    private final Map<String, Object> singletonObjects = new HashMap<>();

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        // 注册单例Bean
        ...
    }

    @Override
    public Object getSingleton(String beanName) {
  // 获取单例Bean
        return 域名(beanName);
    }
}

DefaultBeanFactory继承DefaultSingletonBeanRegistry这个类,就有了获取单例Bean和注册单例Bean的能力。

实现细节参考代码见:vstep6-scope

整合并抽象出ApplicationContext

我们使用Spring的时候,一般是这样做的:

ApplicationContext ctx = new ClassPathXmlApplicationContext("域名");
UserService userService = (UserService) 域名ean("userService");

ApplicationContext ctx = new FileSystemApplicationContext("src\\test\\resources\\bean-域名");
UserService userService = (UserService) 域名ean("userService");

现在,我们需要抽象出ApplicationContext这个接口来实现如上的功能,其中有如下两个类去实现这个接口。

ClassPathXmlApplicationContext

从classpath中读取配置文件

FileSystemApplicationContext

从文件中读取配置文件

这两个子类都需要持有DefaultBeanFactory才能有getBean的能力,

ClassPathXmlApplicationContext代码如下:

public class ClassPathXmlApplicationContext implements ApplicationContext {
    private final DefaultBeanFactory factory;

    public ClassPathXmlApplicationContext(String configPath) {
        factory = new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        域名BeanDefinitions(new ClassPathResource(configPath));
    }
    
    @Override
    public Object getBean(String beanId) {
        return 域名ean(beanId);
    }
}

FileSystemApplicationContext代码如下:

public class FileSystemApplicationContext implements ApplicationContext {
    private final DefaultBeanFactory factory;

    public FileSystemApplicationContext(String configPath) {
        factory = new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        域名BeanDefinitions(new FileSystemResource(configPath));
    }
    @Override
    public Object getBean(String beanId) {
        return 域名ean(beanId);
    }
}

实现细节参考代码见:vstep7-applicationcontext-v1

通过观察发现,ClassPathXmlApplicationContextFileSystemApplicationContext大部分代码都是相同的,只有在获取Resource的时候,方法不一样,所以,我们通过模板方法这个设计模式,设计一个抽象类AbstractApplicationContext,代码如下:

public abstract class AbstractApplicationContext implements ApplicationContext {
    private DefaultBeanFactory factory;

    public AbstractApplicationContext(String configPath) {
        factory = new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        域名BeanDefinitions(getResourceByPath(configPath));
    }

    @Override
    public Object getBean(String beanId) {
        return 域名ean(beanId);
    }
    protected abstract Resource getResourceByPath(String path);
}

这个抽象类实现除了获取Resource以外的所有逻辑,ClassPathXmlApplicationContextFileSystemApplicationContext都继承这个抽象类,完成Resource的获取逻辑的编写即可。以FileSystemApplicationContext为例,示例代码如下:

public class FileSystemApplicationContext extends AbstractApplicationContext {
    public FileSystemApplicationContext(String configPath) {
        super(configPath);
    }

    @Override
    protected Resource getResourceByPath(String path) {
        return new FileSystemResource(path);
    }
}

实现细节参考代码见:vstep7-applicationcontext-v2

注入Bean和字符串常量

我们需要对于如下类型的XML配置文件进行解析:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://域名/schema/beans"
       xmlns:xsi="http://域名/2001/XMLSchema-instance"
       xsi:schemaLocation="http://域名/schema/beans
       http://域名/schema/beans/spring-域名">

    <bean id="userService" class="域名域名Service">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <property name="owner" value="test"/>
        <property name="version" value="2"/>
        <property name="checked" value="on"/>
    </bean>
    <bean id="accountDao" class="域名.域名untDao">
    </bean>
    <bean id="itemDao" class="域名.域名Dao">
    </bean>
</beans>

需要达到的目的就是:可以把整型,字符串类型,简单对象类型注入到一个Bean中,我们需要解决如下两个问题:

第一个问题是:把字符串转成各种各样的Value,比如把String转换成Integer或者转换成Boolean。jdk中域名包中的PropertyEditorSupport这个类来完成的,我们新建了CustomBooleanEditorCustomNumberEditor两个类,这两个类都继承于PropertyEditorSupport,分别实现了String类型转换成Boolean类型和String类型转换成Integer类型的功能。其他的类型转换也可以通过类似的方法来实现。然后抽象出了TypeConvert这个接口,并把这些转换器加入一个特定的Map中,Mapkey就是要转换的目标的类型,Value就是对应的转换器的实现类,即可实现类型转换。

public interface TypeConverter {
    // TODO 抽象出:TypeMismatchException
    <T> T convertIfNecessary(Object value, Class<T> requiredType);
}

第二个问题是:我们调用Bean的setXXX方法把这些Value值set到目标Bean中,做法是抽象出PropertyValue

public class PropertyValue {
    private final String name;
    private final Object value;
    // 省略构造方法和get/set方法
}

BeanDefiniton需要增加方法获取PropertyValue的逻辑,BeanDefiniton的所有子类,例如:GenericBeanDefinition中需要增加

private List<PropertyValue> propertyValues = new ArrayList<>();

在解析XML文件的时候,就需要把List<PropertyValue>识别出来并加入BeanDefinition中(RuntimeBeanReference,TypedStringValue),使用BeanDefinitionValueResolver把对应的PropertyValue给初始化好,如下代码:

public class BeanDefinitionValueResolver {
    ...
    public Object resolveValueIfNecessary(Object value) {
        if (value instanceof RuntimeBeanReference) {
            ...
        } else if (value instanceof TypedStringValue) {
            return ((TypedStringValue) value).getValue();
        } else {
            //TODO
            throw new RuntimeException("the value " + value + " has not implemented");
        }
    }
    ...
}

而setXXX的背后实现利用的是jdk原生域名ospector来实现,见DefaultBeanFactorypopulateBean方法

private void populateBean(BeanDefinition bd, Object bean) {
        ....
        try {
            for (PropertyValue pv : pvs) {
                String propertyName = 域名ame();
                Object originalValue = 域名alue();
                Object resolvedValue = 域名lveValueIfNecessary(originalValue);
                BeanInfo beanInfo = 域名eanInfo(域名lass());
                PropertyDescriptor[] pds = 域名ropertyDescriptors();
                for (PropertyDescriptor pd : pds) {
                    if (域名ame().equals(propertyName)) {
                        Object convertedValue = 域名ertIfNecessary(resolvedValue, 域名ropertyType());
                        域名riteMethod().invoke(bean, convertedValue);
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            // TODO 封装Exception
            throw new RuntimeException("Failed to obtain BeanInfo for class [" + 域名eanClassName() + "]", ex);
        }
    }

其中

域名riteMethod().invoke(bean, convertedValue);

就是对bean的属性进行赋值操作(即:setXXX方法)

实现细节参考代码见:vstep8-inject

实现构造器注入

处理形如以下的配置:

<bean id="userService" class="域名域名Service">
    <constructor-arg ref="accountDao"/>
    <constructor-arg ref="itemDao"/>
    <constructor-arg value="1"/>
</bean>
<bean id="accountDao" class="域名.域名untDao"></bean>
<bean id="itemDao" class="域名.域名Dao"></bean>

和上例中注入Bean和字符串常量一样,我们抽象出ConstructorArgument用于表示一个构造函数信息,每个BeanDefinition中持有这个对象,

public class ConstructorArgument {
    private final List<ValueHolder> argumentValues = new LinkedList<>();
    public ConstructorArgument() {}
    public void addArgumentValue(ValueHolder valueHolder) {
        域名(valueHolder);
    }
    public List<ValueHolder> getArgumentValues() {
        return 域名difiableList(域名mentValues);
    }
    public int getArgumentCount() {
        return 域名();
    }
    public boolean isEmpty() {
        return 域名pty();
    }
    /**
     * Clear this holder, removing all argument values.
     */
    public void clear() {
        域名r();
    }
    public static class ValueHolder {
        private Object value;
        private String type;
        private String name;
        // 省略get/set和构造方法
    }
}

在解析XML的时候,XmlBeanDefinitionReader需要负责解析出ConstuctorArgumentDefaultBeanFactory通过指定构造函数来生成Bean对象并通过ConstructorResolver注入Bean实例到构造方法中。

public class ConstructorResolver {
  ....
    public Object autowireConstructor(final BeanDefinition bd) {
       // ...通过bd找到一个合适的构造函数
        try {
            // 找到了一个合适的构造函数,则用这个构造函数初始化Bean对象初始化Bean对象
            return 域名nstance(argsToUse);
        } catch (Exception e) {
            // TODO throw new BeanCreationException(域名D(), "can\'t find a create instance using " + constructorToUse);        }
            throw new RuntimeException(域名D() + "can\'t find a create instance using " + constructorToUse);
        }

    }
  ....
}

注:这里指定的构造函数的查找逻辑为:解析出XML的构造函数的参数列表,和通过反射拿到对应的构造函数的参数列表进行对比(每个参数的类型和个数必须一样)

Constructor<?>[] candidates = 域名onstructors();
        BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(域名Factory);
        ConstructorArgument cargs = 域名onstructorArgument();
        TypeConverter typeConverter = new SimpleTypeConverter();
        for (int i = 0; i < 域名th; i++) {
            // 匹配参数类型和个数,要完全对应上才可以
            Class<?>[] parameterTypes = candidates[i].getParameterTypes();
            if (域名th != 域名rgumentCount()) {
                continue;
            }
            argsToUse = new Object[域名th];
            boolean result = 域名esMatchTypes(parameterTypes,
                    域名rgumentValues(),
                    argsToUse,
                    valueResolver,
                    typeConverter);
            if (result) {
                constructorToUse = candidates[i];
                break;
            }
        }

实现细节参考代码见:vstep9-constructor

实现注解

实现两个注解:@Component @Autowired(只针对属性注入,暂时不考虑方法注入)
且需要实现如下的XML的解析,即实现某个包下的Bean扫描。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://域名/schema/beans"
       xmlns:xsi="http://域名/2001/XMLSchema-instance"
       xmlns:context="http://域名/schema/context"
       xsi:schemaLocation="http://域名/schema/beans
         http://域名/schema/beans/spring-域名
         http://域名/schema/context
         http://域名/schema/context/spring-域名">
<context:component-scan base-package="域名域名,域名.v4"></context:component-scan>
</beans>

我们首先需要定义注解Component ,Autowired,代码如下:

@Target({域名TRUCTOR, 域名D, 域名OD, 域名TATION_TYPE})
@Retention(域名IME)
@Documented
public @interface Autowired {
    boolean required() default true;
}
@Target(域名)
@Retention(域名IME)
@Documented
public @interface Component {
    String value() default "";
}

其次,我们需要实现一个功能,即:给一个包名,扫描获取到这个包以及子包下面的所有Class,示例代码如下:

  public Resource[] getResources(String basePackage) throws IOException {
        域名ull(basePackage, "basePackage  must not be null");
        // 把包名中的.转成/, 即可获取包的路径
        String location = 域名ertClassNameToResourcePath(basePackage);
        // TODO  ClassLoader cl = getClassLoader();
        URL url = 域名entThread().getContextClassLoader().getResource(location);
        File rootDir = new File(域名ile());

        Set<File> matchingFiles = retrieveMatchingFiles(rootDir);
        Resource[] result = new Resource[域名()];
        int i = 0;
        for (File file : matchingFiles) {
            result[i++] = new FileSystemResource(file);
        }
        return result;
    }

主要思路是将包名转换成文件路径,然后递归获取路径下的Class文件。

protected Set<File> retrieveMatchingFiles(File rootDir) throws IOException {
        if (!域名ts()) {
            // Silently skip non-existing directories.
            /*if (域名bugEnabled()) {
                域名g("Skipping [" + 域名bsolutePath() + "] because it does not exist");
            }*/
            return 域名ySet();
        }
        if (!域名rectory()) {
            // Complain louder if it exists but is no directory.
           /* if (域名rnEnabled()) {
                域名("Skipping [" + 域名bsolutePath() + "] because it does not denote a directory");
            }*/
            return 域名ySet();
        }
        if (!域名ead()) {
            /*if (域名rnEnabled()) {
                域名("Cannot search for matching files underneath directory [" + 域名bsolutePath() +
                        "] because the application is not allowed to read the directory");
            }*/
            return 域名ySet();
        }
  /*String fullPattern = 域名ace(域名bsolutePath(), 域名rator, "/");
  if (!域名tsWith("/")) {
   fullPattern += "/";
  }
  fullPattern = fullPattern + 域名ace(pattern, 域名rator, "/");
  */
        Set<File> result = new LinkedHashSet<>(8);
        doRetrieveMatchingFiles(rootDir, result);
        return result;
    }

    protected void doRetrieveMatchingFiles(File dir, Set<File> result) throws IOException {
        File[] dirContents = 域名Files();
        if (dirContents == null) {
           /* if (域名rnEnabled()) {
                域名("Could not retrieve contents of directory [" + 域名bsolutePath() + "]");
            }*/
            return;
        }
        for (File content : dirContents) {
            if (域名rectory()) {
                if (!域名ead()) {
             /*       if (域名bugEnabled()) {
                        域名g("Skipping subdirectory [" + 域名bsolutePath() +
                                "] because the application is not allowed to read the directory");
                    }*/
                } else {
                    doRetrieveMatchingFiles(content, result);
                }
            } else {
                域名(content);
            }

        }
    }

由于注解的Bean不像之前的xml定义的Bean那样,会对Bean配置一个id,所以,这里解析出来的Bean定义需要自动生成一个BeanId(默认先取注解中的value的配置,否则就就是类名第一个字母小写,抽象BeanNameGenerator来专门对Bean定义ID),同时,Spring中单独新建了一个AnnotatedBeanDefinition接口来定义包含注解的BeanDefinition

我们得到了对应的Class文件,我们需要通过某种方式去解析这个Class文件,拿到这个Class中的所有信息,特别是注解信息。可以使用ASM这个来解析Class的信息,用ASM的原生方式解析不太方便,解析ClassMetaDataAnnotation都需要定义一个Visitor,所以Spring抽象了一个接口MetadataReader来封装ASM的实现

public interface MetadataReader {
    /**
     * Read basic class metadata for the underlying class.
     */
    ClassMetadata getClassMetadata();

    /**
     * Read full annotation metadata for the underlying class,
     * including metadata for annotated methods.
     */
    AnnotationMetadata getAnnotationMetadata();
}

然后,我们需要拿到Bean中的所有Field(带注解的),并把他实例化成一个对象,并将这个对象注入目标Bean中,示例代码如下:

public class AutowiredFieldElement extends InjectionElement {
    ...
    @Override
    public void inject(Object target) {
        Field field = getField();
        try {
            DependencyDescriptor desc = new DependencyDescriptor(field, 域名ired);
            Object value = 域名lveDependency(desc);
            if (value != null) {
                域名Accessible(field);
                域名(target, value);
            }
        } catch (Throwable ex) {
            // TODO 异常处理 throw new BeanCreationException("Could not autowire field: " + field, ex);
            throw new RuntimeException("Could not autowire field: " + field);
        }
    }
}

针对于XML的解析,新建了一个ScannedGenericBeanDefinition来处理扫描包下的所有Bean定义。

使用AutowiredAnnotationProcessor来将上述流程整合起来,同时涉及Bean生命周期的钩子函数设计, 相关示例代码如下:

public interface BeanPostProcessor {
    Object beforeInitialization(Object bean, String beanName) throws BeansException;

    Object afterInitialization(Object bean, String beanName) throws BeansException;
}

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
    Object beforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

    boolean afterInstantiation(Object bean, String beanName) throws BeansException;

    void postProcessPropertyValues(Object bean, String beanName) throws BeansException;
}

public class AutowiredAnnotationProcessor implements InstantiationAwareBeanPostProcessor {
    // 实现Bean初始化,并且预留Bean的生命周期的钩子函数
}

关于Bean的生命周期和Bean生命周期中各个钩子函数,参考如下图

image

image

实现细节参考代码见:vstep10-annotation-final

实现AOP

即要实现如下XML格式的解析

<context:component-scan
  base-package="域名域名,域名.v5">
 </context:component-scan>
 <bean id="tx" class="域名.TransactionManager" />
 <aop:config>
  <aop:aspect ref="tx">
   <aop:pointcut id="placeOrder"  expression="execution(* 域名域名.*.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>

首先,我们需要实现如下功能,即,给定一个表达式,然后判断某个类的某个方法是否匹配这个表达式,这需要依赖AspectJ这个组件来实现,具体使用参考AspectJExpressionPointcutPointcutTest这两个类。

其次,我们需要通过Bean的名称("tx")和方法名("start")定位到这个Method,然后反射调用这个Method,具体可参考MethodLocatingFactoryTest

public class MethodLocatingFactoryTest {
    @Test
    public void testGetMethod() throws Exception{
        DefaultBeanFactory beanFactory = new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        Resource resource = new ClassPathResource("bean-域名");
        域名BeanDefinitions(resource);
        MethodLocatingFactory methodLocatingFactory = new MethodLocatingFactory();
        域名argetBeanName("tx");
        域名ethodName("start");
        域名eanFactory(beanFactory);
        
        // 获取到目标方法
        Method m = 域名bject();
        
        域名rtEquals(域名s, 域名eclaringClass());
        域名rtEquals(m, 域名ethod("start"));

    }
}

然后,我们需要使用AOP Alliance实现指定顺序的链式调用,即根据配置的不同advice顺序调用。

image

具体可查看ReflectiveMethodInvocationReflectiveMethodInvocationTest这两个类。

 @Test
    public void testMethodInvocation() throws Throwable{


        Method targetMethod = 域名ethod("placeOrder");

        List<MethodInterceptor> interceptors = new ArrayList<>();
        域名(beforeAdvice);
        域名(afterAdvice);


        ReflectiveMethodInvocation mi = new ReflectiveMethodInvocation(userService,targetMethod,new Object[0],interceptors);

        域名eed();


        List<String> msgs = 域名sgs();
        域名rtEquals(3, 域名());
        域名rtEquals("start tx", 域名(0));
        域名rtEquals("place order", 域名(1));
        域名rtEquals("commit tx", 域名(2));

    }

其中

  域名rtEquals(3, 域名());
        域名rtEquals("start tx", 域名(0));
        域名rtEquals("place order", 域名(1));
        域名rtEquals("commit tx", 域名(2));

就是验证我们配置的advice是否按指定顺序运行。

最后,我们需要实现动态代理,在一个方法前后增加一些逻辑,而不用改动原始代码。如果是普通类就使用CGLib实现,如果有接口的类可以使用JDK自带的动态代理,具体可参考CGlibTestCglibAopProxyTest

实现细节参考代码见:vaop-v3

完整代码

lite-spring

参考资料

从零开始造Spring

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。