Spring注解驱动开发之AOP容器篇

  前言:现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。如果掌握这些底层原理、注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余

  本篇主要内容:Spring AOP的使用及其原理分析

一、AOP功能测试

AOP【动态代理】:指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式

步骤: 

  1、导入aop模块;Spring AOP:(spring-aspects)
  2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)   

public class MathCalculator {

	public int div(int i,int j){
		System.out.println("MathCalculator...div...");
		return i/j;
	}
}

  3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;

/
  切面类
  @author lfy

  @Aspect: 告诉Spring当前类是一个切面类

 /
@Aspect
public class LogAspects {

    //抽取公共的切入点表达式
    //1、本类引用
    //2、其他的切面引用
    @Pointcut("execution(public int com.atguigu.aop.MathCalculator.(..))")
    public void pointCut(){};

    //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
    }

    @After("com.atguigu.aop.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
    }

    //JoinPoint一定要出现在参数表的第一位
    @AfterReturning(value="pointCut()",returning="result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
    }

    @AfterThrowing(value="pointCut()",throwing="exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println(""+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}");
    }

}

  4、给切面类的目标方法标注何时何地运行(通知注解):

     前置通知(@Before):logStart:在目标方法(div)运行之前运行
      后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
      返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
      异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
      环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())

  5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;

@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {

    //业务逻辑类加入容器中
    @Bean
    public MathCalculator calculator(){
        return new MathCalculator();
    }
    //切面类加入到容器中
    @Bean
    public LogAspects logAspects(){
        return new LogAspects();
    }
}

  6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
   7、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】;在Spring中很多的类这种 @EnableXXX注解,开启特定功能                                 
   

二、SpringAOP原理分析:

1、首先我们知道要使用@EnableAspectJAutoProxy开启aop模式,那就应该从这个注解开始分析:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

  可以看到@EnableAspectJAutoProxy使用了@Import来导入组件,上一篇讲过,然后具体看AspectJAutoProxyRegistrar这个类:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /

      registerBeanDefinitions方法可以给容器注册组件/
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //使用registry给容器注册组件
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

  进入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法:

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

  最终来到registerOrEscalateApcAsRequired方法:

private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");      //首次启动容器,不存在AnnotationAwareAspectJAutoProxyCreator定义信息
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }     //给容器注册AnnotationAwareAspectJAutoProxyCreator(即:创建bean的定义信息,并添加到容器)
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

  总结:@EnableAspectJAutoProxy中的@Import(AspectJAutoProxyRegistrar.class),利用AspectJAutoProxyRegistrar自定义给容器中注册了一个internalAutoProxyCreator =AnnotationAwareAspectJAutoProxyCreator

2、接下来分析AnnotationAwareAspectJAutoProxyCreator:

  

  从继承树上我们可以得到两点信息

    1)、AnnotationAwareAspectJAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor(继承BeanPostProcessor),说明它是一个后置处理器

    2)、AnnotationAwareAspectJAutoProxyCreator实现了Aware接口中的BeanFactoryAware,容器启动过程会调用setBeanFactory自动装配BeanFactory

  以下是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程:

    1)、传入配置类,创建ioc容器

      AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);

    2)、注册配置类,调用refresh()刷新容器;

    3)、registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建;

      1)、先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor  

      2)、给容器中加别的BeanPostProcessor

      3)、优先注册实现了PriorityOrdered接口的BeanPostProcessor;

      4)、再给容器中注册实现了Ordered接口的BeanPostProcessor;

      5)、注册没实现优先级接口的BeanPostProcessor;

      6)、注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中(创建internalAutoProxyCreator的                                               BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator】);

        1)、创建Bean的实例

        2)、populateBean;给bean的各种属性赋值

        3)、initializeBean:初始化bean;

          1)、invokeAwareMethods():处理Aware接口的方法回调

          2)、applyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()

          3)、invokeInitMethods();执行自定义的初始化方法

          4)、applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization()

        4)、BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功;--》aspectJAdvisorsBuilder

      7)、把BeanPostProcessor注册到BeanFactory中==>beanFactory.addBeanPostProcessor(postProcessor)

主要源码分析:                                                                                 

public static void registerBeanPostProcessors(
            ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
     //获取所有后置处理器的名称
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

        // 添加一些其他的后置处理器
        int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
        beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

       // 以下主要是对所有的BeanPostProcessor进行优先级分类
        List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
        List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>();
        List<String> orderedPostProcessorNames = new ArrayList<String>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
        for (String ppName : postProcessorNames) {        //首先创建实现了PriortyOrdered的后置处理器对象,并添加到对应的集合中
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
                priorityOrderedPostProcessors.add(pp);
                if (pp instanceof MergedBeanDefinitionPostProcessor) {
                    internalPostProcessors.add(pp);
                }
            }
            else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }
            else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }

        // 将实现了PriorityOrdered的后置处理器对象集合排序并添加到beanFactory
        sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

        // 创建实现了Ordered的后置处理器对象,排序并添加到beanFactory(AnnotationAwareAspectJAutoProxyCreator是实现了ordered接口的)
        List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();
        for (String ppName : orderedPostProcessorNames) {
            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
            orderedPostProcessors.add(pp);
            if (pp instanceof MergedBeanDefinitionPostProcessor) {
                internalPostProcessors.add(pp);
            }
        }
        sortPostProcessors(orderedPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, orderedPostProcessors);

        //创建未实现优先级接口的后置处理器对象,添加到beanFactory
        List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
        for (String ppName : nonOrderedPostProcessorNames) {
            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
            nonOrderedPostProcessors.add(pp);
            if (pp instanceof MergedBeanDefinitionPostProcessor) {
                internalPostProcessors.add(pp);
            }
        }
        registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

        // Finally, re-register all internal BeanPostProcessors.
        sortPostProcessors(internalPostProcessors, beanFactory);
        registerBeanPostProcessors(beanFactory, internalPostProcessors);

        // Re-register post-processor for detecting inner beans as ApplicationListeners,
        // moving it to the end of the processor chain (for picking up proxies etc).
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    }

  具体创建BeanPostProcessor对象的过程:

      beanFactory.getBean==>doGetBean==>getSingleton==>singletonFactory.getObject()==>>createBean==>doCreateBean,主要代码片段:

// Initialize the bean instance.
        Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);//对bean进行赋值操作
            if (exposedObject != null) {
                exposedObject = initializeBean(beanName, exposedObject, mbd);//初始化操作
            }
        }

  initializeBean初始化bean:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {
            invokeAwareMethods(beanName, bean);//处理Aware接口的方法回调
        }

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//执行后置处理器的postProcessBeforeInitialization()
        }

        try {
            invokeInitMethods(beanName, wrappedBean, mbd);//执行自定义的初始化方法
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }

        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);//执行后置处理器的postProcessAfterInitialization()
        }
        return wrappedBean;
    }

  接下分析AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】如何被执行:

      finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;创建剩下的单实例bean          1)、遍历获取容器中所有的Bean,依次创建对象getBean(beanName);           getBean->doGetBean()->getSingleton()->        2)、创建bean           【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,InstantiationAwareBeanPostProcessor会调用postProcessBeforeInstantiation()】           1)、先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建;              只要创建好的Bean都会被缓存起来           2)、createBean();创建bean;              AnnotationAwareAspectJAutoProxyCreator 会在任何bean创建之前先尝试返回bean的代理实例              【BeanPostProcessor是在Bean对象创建完成初始化前后调用的】              【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的】              1)、resolveBeforeInstantiation(beanName, mbdToUse);解析BeforeInstantiation                 希望后置处理器在此能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续                 1)、后置处理器先尝试返回对象;                    bean = applyBeanPostProcessorsBeforeInstantiation():                       拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor;                       就执行postProcessBeforeInstantiation                    if (bean != null) {                     bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);                  }

              2)、doCreateBean(beanName, mbdToUse, args);真正的去创建一个bean实例;和3.6流程一样;

  相关源码:

     createBean:

  

  

  

 

 AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】 的作用:

 1)、每一个bean创建之前,调用postProcessBeforeInstantiation();     关心MathCalculator和LogAspect的创建     1)、判断当前bean是否在advisedBeans中(保存了所有需要增强bean)     2)、判断当前bean是否是基础类型的Advice、Pointcut、Advisor、AopInfrastructureBean,        或者是否是切面(@Aspect)     3)、是否需要跳过        1)、获取候选的增强器(切面里面的通知方法)【List<Advisor> candidateAdvisors】           每一个封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor;           判断每一个增强器是否是 AspectJPointcutAdvisor 类型的;返回true        2)、永远返回false

 2)、创建对象 postProcessAfterInitialization;     return wrapIfNecessary(bean, beanName, cacheKey);//包装如果需要的情况下     1)、获取当前bean的所有增强器(通知方法)  Object[]  specificInterceptors        1、找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的)        2、获取到能在bean使用的增强器。        3、给增强器排序     2)、保存当前bean在advisedBeans中;     3)、如果当前bean需要增强,创建当前bean的代理对象;        1)、获取所有增强器(通知方法)        2)、保存到proxyFactory        3)、创建代理对象:Spring自动决定           JdkDynamicAopProxy(config);jdk动态代理;           ObjenesisCglibAopProxy(config);cglib的动态代理;     4)、给容器中返回当前组件使用cglib增强了的代理对象;     5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;

  3)、目标方法执行  ;     容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);     1)、CglibAopProxy.intercept();拦截目标方法的执行     2)、根据ProxyFactory对象获取将要执行的目标方法拦截器链;        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);        1)、List<Object> interceptorList保存所有拦截器 5           一个默认的ExposeInvocationInterceptor 和 4个增强器;        2)、遍历所有的增强器,将其转为Interceptor;           registry.getInterceptors(advisor);        3)、将增强器转为List<MethodInterceptor>;           如果是MethodInterceptor,直接加入到集合中           如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor;           转换完成返回MethodInterceptor数组;

     3)、如果没有拦截器链,直接执行目标方法;        拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制)     4)、如果有拦截器链,把需要执行的目标对象,目标方法,        拦截器链等信息传入创建一个 CglibMethodInvocation 对象,        并调用 Object retVal =  mi.proceed();     5)、拦截器链的触发过程;        1)、如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法;        2)、链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;           拦截器链的机制,保证通知方法与目标方法的执行顺序;

最后的总结:     1)、  @EnableAspectJAutoProxy 开启AOP功能     2)、 @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator     3)、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;     4)、容器的创建流程:        1)、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象        2)、finishBeanFactoryInitialization()初始化剩下的单实例bean           1)、创建业务逻辑组件和切面组件           2)、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程           3)、组件创建完之后,判断组件是否需要增强              是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib);     5)、执行目标方法:        1)、代理对象执行目标方法        2)、CglibAopProxy.intercept();           1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)           2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行;           3)、效果:              正常执行:前置通知-》目标方法-》后置通知-》返回通知              出现异常:前置通知-》目标方法-》后置通知-》异常通知

/

  

  

    

      

原文地址:https://www.cnblogs.com/qzlcl/p/10960712.html

时间: 2024-10-28 12:48:08

Spring注解驱动开发之AOP容器篇的相关文章

Spring注解驱动开发之IOC

1.最简单的注解驱动开发实例: <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.8.RELEASE</version> </dependency> public class Student { String name; public String ge

Spring注解驱动开发之web

前言:现今SpringBoot.SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解.原理,比如@Conditional.@Import.@EnableXXX等.如果掌握这些底层原理.注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余 一.servlet3.0规范 1.新增的注解支持 在servlet3.0之前的话,我们要添加Servlet.Filter.Listener都需要在web.xml中注册,而在servlet3.0添加了注

Spring注解驱动开发(四)-----aop、声明式事务

AOP 概念 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式:-----基于动态代理 一个aop示例 1.导入aop模块:Spring AOP:(spring-aspects)-----导入相关jar包 2.MathCalculator-----一个业务逻辑的类-----在业务逻辑运行的时候将日志进行打印(方法之前.方法运行结束.方法出现异常,xxx) package com.atguigu.aop; public class MathCalculator { publ

Spring注解驱动第八讲--容器中bean的生命周期

bean的生命周期指的就是bean在容器中的: 创建-->初始化-->销毁; 以上的过程都是由容器来进行管理. 我们可以自定义初始化和销毁方法,的那个进行到当前bean的生命周期的时候,调用我们自己定义的初始化方法和销毁方法.那么自定义初始化和销毁方法有以下四种方式: 1,指定初始化和销毁方法: 在以往使用xml配置文件的时候可以在<bean>标签中加上"init-method"和"destory-method"属性来指定自定义的初始化和销毁

Spring注解驱动开发(二)--组件注入

一.前言 上一篇我们搭建了一个简单的Spring项目,并简单的使用了 组件注册.这一篇中,我们来详细的讲解组件注入. 二.组件注入 1. @ComponentScan 在上一篇中,我们使用了@Configuration和@Bean实现了组件注入.但是如果需要注入的组件很多的情况下,每个组件都需要通过一个@Bean注解进行注入,这样就会很麻烦.所以Spring提供了@ComponentScan注解. @ComponentScan可以指定需要扫描的包,在这些包下,@Component注解标注的组件都

敏捷开发之Scrum扫盲篇

敏捷开发之Scrum扫盲篇 现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP... 为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述Scrum中的各个环节,主要目的有两个,一个是进行知识的总结,另外一个是觉得网上很多学习资料的讲述方式让初学者不太容易理解:所以我决定写一篇扫盲性的博文,同时试着也与园内的朋友一起分享交流一下,希望对初学者有帮助.  什么是敏捷开发? 敏捷开发(Agile Development)是

iOS开发笔记--敏捷开发之Scrum扫盲篇

敏捷开发之Scrum扫盲篇 现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP... 为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述Scrum中的各个环节,主要 目的有两个,一个是进行知识的总结,另外一个是觉得网上很多学习资料的讲述方式让初学者不太容易理解:所以我决定写一篇扫盲性的博文,同时试着也与园内的 朋友一起分享交流一下,希望对初学者有帮助.  什么是敏捷开发? 敏捷开发(Agile Development

Spring 注解驱动(一)基本使用规则

Spring 注解驱动(一)基本使用规则 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 @Configuration @ComponentScan(basePackages = "com.github.binarylei", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class})

Spring 注解驱动(二)WEB 注解开发

Spring 注解驱动(二)WEB 注解开发 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 在 Servlet 3.0 时支持注解启动,不再需要 web.xml 配制文件. 1.1 Servlet 3.0 注解 Servlet 3.0 常用注解: @WebServlet @WebFilter @WebInitParam @WebListener @WebServlet("/hello") pu