Spring总结

一. 简介

1. 什么是Spring ?

Spring是一个开源的轻量级的IoC和AOP容器框架。简化了企业应用程序的开发,使得开发者只需关心业务需求。

2. 什么是IoC ?

Spring IoC即控制反转,其作用是管理所有的Bean,相当于一个容器。首先将javaBean加载到容器中,如果我需要使用javaBean时直接从容器中获取,使用结束也无需销毁,再放回容器中即可,避免了手动创建和销毁javaBean对象带来的麻烦。

3. 什么是AOP ?

AOP即面向切面编程,像我们日常使用的事务、日志、权限认证都用到来AOP技术,其实我个人的理解是,Spring AOP是基于动态代理模式对某些类的某些方法进行拦截,在方法执行前和执行后进行相应的操作,也就是@before、@after、@afterReturning、@afterThrowing等操作。

4. 什么是动态代理?

生成一个代理对象来代理真实对象,从而控制真实对象的访问。
4.1 JDK动态代理具体实现原理

  • 通过实现InvocationHandlet接口创建自己的调用处理器;
  • 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
  • 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
    JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

4.2 CGLib动态代理

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

4.3 两者对比

  • JDK动态代理是面向接口的。
    CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。
  • 性能:
    关于两者之间的性能的话,JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。

主要体现在如下的两个指标中:
CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;
但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;
因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

5. Spring的优点

a. 方便解耦,简化开发;
b. AOP编程的支持;
c. 声明事务的支持;
d. 对junit4的支持,可通过注解测试程序;
e. 帮助我们快速整合第三方框架。

6. SpringAOP的在实际应用中场景有哪些?

•Authentication 权限
•Caching 缓存
•Context passing 内容传递
•Error handling 错误处理
•Lazy loading 懒加载
•Debugging 调试
•logging,tracing,profiling and monitoring 记录跟踪 优化 校准
•Performance optimization 性能优化
•Persistence 持久化
•Resource pooling 资源池
•Synchronization 同步
•Transactions 事务
•Logging 日志

7. Spring IoC的在实际应用中场景有哪些?

如:set注入

8. Spring中用到的设计模式

a. 代理模式——AOP;
b. 单例模式——配置文件中的bean(默认情况);
c. 工厂模式——BeanFactory用来创建对象的实例。

9. Spring框架的主要组成

在这里插入图片描述

二. Spring IoC介绍

1. Spring IoC容器的设计

主要是基于BeanFactory和ApplicationContext两个接口,而ApplicationContext是BeanFactory的子接口,故BeanFactory是Spring IoC容器的最底层接口。
BeanFactory源码分析:

	public interface BeanFactory{
	    String FACTORY_BEAN_PREFIX = "&";
		Object getBean(String name) throws BeansException;
		<T> T getBean(String name, Class<T> requiredType) throws BeansException;
		Object getBean(String name, Object... args) throws BeansException;
		<T> T getBean(Class<T> requiredType) throws BeansException;
		<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
		<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
		<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
		boolean containsBean(String name);
		boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
		boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
		boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
		boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
		Class<?> getType(String name) throws NoSuchBeanDefinitionException;
		String[] getAliases(String name);
	
	}

·getBean的多个方法用于获取配置给Spring IoC容器的Bean;
·isSingleton用于判断是否为单例
·关于type的匹配,这是一个按照Java类型匹配的方式
·getAliases方法是获取别名的方法

2. Spring IoC容器的初始化和依赖注入

Bean的定义和初始化在Spring IoC容器是两个步骤,先定义,再初始化和依赖注入。

2.1 Bean定义的三个步骤:
a. Resource定位:根据开发者的配置进行资源定位;
b. BeanDefinition的载入:将Resource定位到的信息保存到BeanDefinition中,还未创建Bean实例;
c. BeanDefinition的注册:将BeanDefinition的信息发布到Spring IoC容器中,依旧还未创建Bean实例

2.2 初始化及依赖注入
Spring IoC默认会自动初始化Bean,即默认值为false;
若将其设置设置为true,则只有在我们使用Spring IoC容器的getBean方法获取它时,才会进行Bean的初始化,完成依赖注入。

3. Spring Bean的生命周期

定义 -> 初始化 -> 依赖注入 -> 销毁

三. 装配Spring Bean

1. 依赖注入的三种方式

a. 构造器注入:使用构造方法来完成注入;
b. setter注入:setter方法来完成注入;
c. 接口注入。

2. 装配Bean

a. 在XML中显式配置;
b. 在Java的接口和类中实现配置(注解配置:@Resource:根据名称装配;@Autowired:根据类型装配 );
c. 隐式Bean的发现机制和自动装配原则(首选)。

3. Bean的作用域

Spring提供了四种作用域:
a. 单例(singleton):这是默认选项,在整个应用中,Spring只为其生成一个Bean的实例;
b. 原型(prototype):每次注入或者通过Spring IoC容器获取Bean时,Spring都会为其创建新的实例;
c. 会话(session):在web应用中使用,就是在会话过程中Spring只创建一个实例;
d. 请求(request):在web应用中使用,在一个请求中Spring会创建一个实例,但不同请求创建不同的实例

四. 面向切面编程

1. 什么是切面?

切面就是把非业务逻辑相关的代码抽取出来定位到具体的连接点上的一种实现方式。

2. 切面的构成

切点和增强(引介)组成,它既包括横切逻辑的定义,也包括连接点的定义。

  • 2.1 切点的表示
    Spring 通过 org.springframework.aop.Pointcut 接口描述切点,Pointcut 由ClassFilter 和 MethodMatcher而构成,它通过 ClassFilter 定位到某些特定类上,通过MethodMatcher 定位到某些特定方法上,这样 Pointcut 就拥有了描述某些类的某些特定方法的能力。
    在这里插入图片描述
    可以看到 ClassFilter 只定义了一个方法 matches(Class clazz),其参数代表一个被检测类,该方法判别被检测的类是否匹配过滤条件。
    Spring 支持两种方法匹配器:静态方法匹配器和动态方法匹配器

    • 静态方法匹配器:仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;
    • 动态方法匹配器:会在运行期检查方法入参的值。
    • 静态匹配仅会判别一次,而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断,因此,动态匹配对性能的影响很大。一般情况下,动态匹配不常使用。
    • isRuntime() 方法的返回值:返回 false 表示是静态方法匹配器,返回 true 表示是动态方法匹配器。

    Spring提供了6种类型的切点,下面分别针对它们的用途进行介绍。

    • 静态方法切点:org.springframework.aop.support.StaticMehtodMatcherPointcut 是静态方法切点的抽象基类,默认情况下它匹配所有的类。
      StaticMethodMatcherPointcut包括两个主要的子类:
      即NameMatchMethodPointcut 和 AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。
    • 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut 是动态方法切点的抽象基类,默认情况下它匹配所有的类。
    • 注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut 实现类表示注解切点。使用 AnnotationMatchingPointcut 支持在 Bean 中直接通过 Java 5.0 注解标签定义的切点。
    • 流程切点:org.springframework.aop.support.ControlFlowPointcut 实现类表示控制流程切点。ControlFlowPointcut 是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点。
3. 切面有哪些实现类

增强包括:横切代码和部分连接点信息(方法前、方法后主方位信息),所以可以仅通过增强类生成一个切面。但切面仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面。Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面可以分为 3 类:一般切面、切点切面和引介切面,可以通过 Spring 所定义的切面接口清楚地了解切面的分类。

  • Advisor:代表一般切面,仅包含一个 Advice。因为 Advice 包含了横切代码和连接点信息,所以 Advice 本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太广泛,所以一般不会直接使用。
  • PointcutAdvisor:代表具体切点的切面,包含 Advice和 Pointcut 两个类,这样就可以通过类、方法名及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。
  • IntroductionAdvisor:代表引介切面,引介切面是对应引介增强的特殊切面,它应用于类层面上,所以引介切点使用 ClassFilter 进行定义。
    在这里插入图片描述
    下面再来看一下 PointcutAdvisor 的主要实现类体系:
    在这里插入图片描述
    PointcutAdvisor 主要有 6 个具体的实现类,分别介绍如下:
    • DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意 Pointcut 和 Advice 定义一个切面,唯一不支持的是引介切面类型,一般可以通过扩展该类实现自定义的切面。
    • NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面。
    • RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。RegexpMethodPointcutAdvisor允许用户以正则表达式模式串定义方法匹配的切点,其内部通过 JDKRegexpMehtodPointcut 构造出正则表达式方法名切点。
    • StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的目标类。
    • AspectJDKExpressionPointcutAdvisor:用于 AspectJ 切点表达式定义切点的切面。
    • AspectJPointcutAdvisor:用于 AspectJ 语法定义切点的切面。
      这些 Advisor 的实现类都可以在Pointcut中找到对应物,实际上,它们都是通过扩展对应的 Pointcut 实现类并实现 PointcutAdvisor 接口进行定义的。
4. 切面有哪些类型

在这里插入图片描述
4.1 静态普通方法名匹配切面
StaticMethodMatcherPointcutAdvisor 代表一个静态方法匹配切面,它通过 StaticMethodMatcherPointcut 来定义切点,并通过类过滤和方法名来匹配所定义的切点。

	public class Waiter {
	    public void greetTo(String name) {
	        System.out.println("Waiter green to " + name + "...");
	    }
	 
	    public void serveTo(String name) {
	        System.out.println("Waiter serving " + name + "...");
	    }
	}
	 
	public class Seller {
	    public void greetTo(String name) {
	        System.out.println("Seller green to " + name + "...");
	    }
	}

Waiter和Seller拥有相同的greetTo方法,希望通过 StaticMethodMatcherPointcutAdvisor 定义一个切面,在 Waiter##greetTo 方法调用前织入一个增强。

	public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
	    public boolean matches(Method method, Class<?> aClass) { //①
	        return "greetTo".equals(method.getName());
	    }
	 
	    @Override
	    public ClassFilter getClassFilter() { //②
	        return new ClassFilter() {
	            public boolean matches(Class<?> aClass) {
	                return Waiter.class.isAssignableFrom(aClass);
	            }
	        };
	    }
	}

StaticMethodMatcherPointcutAdvisor 抽象类唯一需要定义的是 matches 方法。在默认情况下,该切面匹配所有类,这里通过覆盖 getClassFilter( )方法,让它仅匹配 Waiter 类及其子类。

当然,Advisor 还需要一个增强类的配合。接下类定义一个前置增强:

	public class GreetingBeforeAdvice implements MethodBeforeAdvice {
	    public void before(Method method, Object[] objects, Object o) throws Throwable {
	        String clientName = (String) objects[0];
	        System.out.println("how are you! Mr." + clientName);
	    }
	}

下面使用 Spring 配置来定义切面:

	<bean id="greetingAdvice" class="com.smart.Advice.GreetingBeforeAdvice"/>
	<bean id="greetingAdvisor" class="com.smart.Advisor.GreetingAdvisor"
	      p:advice-ref="greetingAdvice"/>①
	 
	<bean id="waiterTarget" class="com.smart.Advice.Waiter"/>
	<bean id="sellerTarget" class="com.smart.Advice.Seller"/>
	 
	<bean id="parent" abstract="true"
	      class="org.springframework.aop.framework.ProxyFactoryBean"
	      p:interceptorNames="greetingAdvisor"
	      p:proxyTargetClass="true"/>
	 
	<bean id="waiter" parent="parent" p:target-ref="waiterTarget"/>
	<bean id="seller" parent="parent" p:target-ref="sellerTarget"/>

在 ① 处将 greetingAdvice 增强装配到 greetingAdvisor 切面中。StaitcMatcherPointcutAdvisor 除具有 advice 属性外,还可以定义另外两个属性。
classFilter:类匹配过滤器,在 GreetingAdvisor 中用编码的方式设定了 classFilter。
order:切面织入时的顺序,该属性用于定义 Ordered 接口表示的顺序。
由于需要分别为 waiter 和 seller 两个 Bean 定义代理器,且两者有很多公共的配置信息,所以使用一个父 简化配置,通过引用父 轻松地定义两个织入切面的代理。

运行结果:

	how are you! Mr.John
	Waiter green to John...
	 
	Seller green to John...	

4.2 静态正则表达式方法匹配切面
在 StaticMethodMatcherPointcutAdvisor 中,仅能通过方法名定义切点,这种描述方式不够灵活。假设目标类中有多个方法。且它们都满足一定的命名规范,使用正则表达式进行匹配描述就要灵活多了。RegexpMethodPointcutAdvisor 是正则表达式方法匹配的切面实现类,该类已经是功能齐备的实现类,一般情况下无须扩展该类。

	<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
	      p:advice-ref="greetingAdvice">
	    <property name="patterns">
	        <list>
	            <value>.*greetTo.*</value>②
	        </list>
	    </property>
	</bean>
	 
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	      p:target-ref="waiterTarget"
	      p:interceptorNames="regexpAdvisor"
	      p:proxyTargetClass="true">
	</bean>

在 ② 处定义了一个匹配模式串“.greet.”,该模式匹配 Waiter.greetTo() 方法。值得注意的是,匹配模式串的是目标方法的全限定名,即带类名的方法名。

运行结果:

	how are you! Mr.John
	Waiter green to John...
	 
	Waiter serving John...

可见 Waiter.greetTo() 方法被织入了切面,而 Waiter.serveTo() 方法没有被织入切面。除了使用 patterns 和 advice 属性外,还有另外两个属性,分别介绍如下:

pattern:如果只有一个匹配模式,则可以使用该属性进行配置。patterns属性用于定义多个匹配模式串,这些匹配模式串之间是“或”的关系。
order:切面在织入时对应的顺序。
只要程序的类包具有良好的命名规范,就可以使用简单的正则表达式描述出目标方法。由于需要使用全限定名来定义方法名,所以不但方法名需要具有良好的规范性,包名也需要具体良好的规范性。对包名、类名、方法名按其功用进行规范命名并不是一件坏事,相反,规范命名可以增强程序的可读性和团队开发的协作性,降低沟通成本,是值得实践和提倡的编程方法。

4.3 动态切面
DynamicMethodMatcherPointcut 是一个抽象类,它将 isRuntime( )标识为 final 且返回 true,这样其子类就一定是一个动态切点。该抽象类默认匹配所有的类和方法,因此需要通过扩展该类编写符合要求的动态切点。

	public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
	    private static List<String> specialClientList = new ArrayList<String>();
	    static {
	        specialClientList.add("John");
	        specialClientList.add("Tom");
	    }
	 
	    @Override
	    public ClassFilter getClassFilter() {
	        return new ClassFilter() {
	            public boolean matches(Class<?> aClass) {
	                System.out.println("调用getClassFilter()对" + aClass.getName() + "做静态检查.");
	                return Waiter.class.isAssignableFrom(aClass);
	            }
	        };
	    }
	 
	    /**
	     * 对方法进行静态切点检查
	     */
	    @Override
	    public boolean matches(Method method, Class<?> targetClass) {
	        System.out.println("调用matches(method, class)" + targetClass.getName() + "." + method.getName() + "做静态检查.");
	        return "greetTo".equals(method.getName());
	    }
	 
	    /**
	     * 对方法进行动态切点检查
	     */
	    public boolean matches(Method method, Class<?> aClass, Object[] objects) {
	        System.out.println("调用matches(method, class)" + aClass.getName() + "." + method.getName() + "做动态检查.");
	        String clientName = (String) objects[0];
	        return specialClientList.contains(clientName);
	    }
	}

GreetingDynamicPointcut 类既有用于静态切点检查的方法,又有用于动态检查的方法。由于动态切点检查会对性能造成很大的影响,所以应当尽量避免在运行时每次都对目标类的各个方法进行动态检查。Spring 采用这样的机制:在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查;如果静态切点检查是匹配的,则在运行时才进行动态切点检查。在动态切点类中定义静态切点检查的方法可以避免不必要的动态检查操作,从而极大地提高运行效率。

在编写好动态切点后,就可以着手在Spring配置文件中装配出一个动态切面,代码如下:

	<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	    <property name="pointcut">
	        <bean class="com.smart.Advisor.GreetingDynamicPointcut"/>①
	    </property>
	    <property name="advice">
	        <bean class="com.smart.Advice.GreetingBeforeAdvice"/>②
	    </property>
	</bean>
	 
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	      p:target-ref="waiterTarget"
	      p:interceptorNames="dynamicAdvisor"
	      p:proxyTargetClass="true">
	</bean>

动态切面的配置和静态切面的配置没什么区别。使用 DefaultPointcutAdvisor 定义切面,在 ① 处使用内部 Bean 方式注入动态切点 GreetingDynamicPointcut,在 ② 处注入增强。此外,DefaultPointcutAdvisor 还有一个 order 属性,用于定义切面的织入顺序。

接下来定义一个测试类:

	public class BeanFactoryTest {
	 
	    @Test
	    public void getBean() throws IOException {
	        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
	        Resource[] resource = resolver.getResources("classpath:com/smart/bean1.xml");
	 
	        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
	        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
	        reader.loadBeanDefinitions(resource);
	        Waiter waiter = (Waiter) factory.getBean("waiter");
	        waiter.greetTo("John");
	        waiter.greetTo("Pitty");
	        waiter.serveTo("John");
	    }
	}

运行结果:

	调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做静态检查.
	调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做静态检查.
	调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.toString做静态检查.
	调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.clone做静态检查.
	调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
	how are you! Mr.John
	Waiter green to John...
	调用getClassFilter()对com.smart.Advice.NaiveWaiter做静态检查.
	调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做静态检查.
	Waiter serving John...
	调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
	Waiter green to Pitty...
	Waiter serving Pitty...

通过以上输出信息,对照 DynamicMethodMatcherPointcut 切点类,可以很容易发现,Spring 会在创建代理织入切面时,对目标类的所有方法进行静态切点检查;在生成织入切面的代理对象后,第一次调用代理类的每一个方法时都会进行一次静态切点检查,如果本次检查就能从候选者列表中将该方法排除,则以后对该方法的调用就不再执行静态切点检查;对于那些在静态切点检查时匹配的方法,在后续调用该方法时,将执行动态切点检查。

如果将 getClassFilter() 方法和 matches(Method, Class) 方法注释掉,重新运行代码结果如下:

	调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
	how are you! Mr.John
	Waiter green to John...
	调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做动态检查.
	how are you! Mr.John
	Waiter serving John...
	调用matches(method, class)com.smart.Advice.NaiveWaiter.greetTo做动态检查.
	Waiter green to Pitty...
	调用matches(method, class)com.smart.Advice.NaiveWaiter.serveTo做动态检查.
	Waiter serving Pitty...

可以发现,每次调用代理对象的任何一个方法,都会执行动态切点检查,这将导致很大的性能问题。所以,在定义动态切点时,切勿忘记同时覆盖 getClassFilter() 方法和 matches(Method, Class) 方法,通过静态切点检查排除大部分方法。

动态代理的“动态”是相对于那些编译期生成代理 Class 文件和类加载期生成代理 Class 文件而言的。动态代理是运行时动态产生的代理。在 Spring 中,不管是静态切面还是动态切面,都是通过动态代理技术实现的。所谓静态切面,是指在生成代理对象时就确定了增强是否需要织入目标类的连接点上;而动态切面是指必须在运行期根据方法入参的值来判断增强是否需要织入目标类的连接点上。

4.4 流程切面
Spring 的流程切面由 DefaultPointcutAdvisor 和 ControlFlowPointcut 实现。流程切点代表由某个方法直接或间接发起调用的其他方法。来看下面的实例,假设通过一个 WaiterDelegate 类代理 Waiter 所有的方法,代码如下:

	public class WaiterDelegate {
	    private Waiter waiter;
	 
	    public void service(String clientName) {
	        waiter.greetTo(clientName);
	        waiter.serveTo(clientName);
	    }
	 
	    public void setWaiter(Waiter waiter) {
	        this.waiter = waiter;
	    }
	}

如果希望所有由 WaiterDelegate#service() 方法发起调用的其他方法都织入 GreetingBeforeAdvice 增强,就必须使用流程切面来完成目标。下面使用 DefaultPointcutAdvisor 配置一个流程切面来完成这一需求:

	<bean id="waiterTarget" class="com.smart.Advice.NaiveWaiter"/>
	<bean id="greetingAdvice" class="com.smart.Advice.GreetingBeforeAdvice"/>
	<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
	    <constructor-arg type="java.lang.Class" value="com.smart.Advisor.WaiterDelegate"/>
	    <constructor-arg type="java.lang.String" value="service"/>
	</bean>
	 
	<bean id="controllFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
	      p:pointcut-ref="controlFlowPointcut"
	      p:advice-ref="greetingAdvice"/>
	 
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	      p:target-ref="waiterTarget"
	      p:interceptorNames="controllFlowAdvisor"
	      p:proxyTargetClass="true">
	</bean>

ControlFlowPointcut 有两个构造函数,分别是 ControlFlowPointcut(Class clazz) 和 ControlFlowPointcut(Class clazz, String methodName)。第一个构造函数指定一个类作为流程切点;而第二个构造函数指定一个类和一个方法作为流程切点。在这里,指定 WaiterDelegate#service() 方法作为切点,表示所有通过该方法直接或间接发起的调用匹配切点。

	PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
	Resource[] resource = resolver.getResources("classpath:com/smart/bean1.xml");
	 
	DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
	XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
	reader.loadBeanDefinitions(resource);
	Waiter waiter = (Waiter) factory.getBean("waiter");
	waiter.greetTo("John");
	 
	WaiterDelegate wd = new WaiterDelegate();
	wd.setWaiter(waiter);
	wd.service("John");

运行上面的代码,在控制台输出一下信息:

	Waiter green to John...①
	②
	how are you! Mr.John
	Waiter green to John...
	how are you! Mr.John
	Waiter serving John...

① 处的信息直接调用 greetTo() 方法的输出,此时增强没有起作用;② 处通过 WaiterDelegate#service() 调用 Waiter 的 greetTo() 和 serveTo() 方法输出,这时发现 Waiter 的两个方法都织入了增强。

流程切面和动态切面从某种程度上说可以算是一类切面,因为二者都需要在运行期判断动态环境。对于流程切面来说,代理对象在每次调用目标类方法时,都需要判断方法调用堆栈中是否有满足流程切点要求的方法。因此,和动态切面一样,流程切面对性能的影响也很大。

4.5 引介切面
引介切面是引介增强的封装器,通过引介切面,可以更容易地为现有对象添加任何接口的实现。下图是引介增强的类继承关系图:
在这里插入图片描述
IntroductionAdvisor 接口同时继承 Advisor 和 IntroductionInfo 接口,IntroductionInfo 接口描述了目标类需要实现的新接口。IntroductionAdvisor 和 PointcutAdvisor 接口不同,它仅有一个类过滤器 ClassFilter 而没有 MethodMatcher,这是因为引介切面的切点是类级别的,而 Pointcut 的切点是方法级别的。

IntroductionAdvisor 有两个实现类,分别是 DefaultIntroductionAdvisor 和 DeclareParentsAdvisor,前者是引介切面最常用的实现类,后者用于实现使用 AspectJDK 语言的 DeclareParent 注解表示的引介切面。

下面通过 DefaultIntroductionAdvisor 来增加引介增强配置切面,会发现这种方式比前面的方式更简洁、更清晰。

	<bean id="introductionAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
	    <constructor-arg>
	        <bean class="com.smart.Advice.ControllablePerformanceMonitor"/>
	    </constructor-arg>
	</bean>
	 
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
	      p:target-ref="waiterTarget"
	      p:interceptorNames="introductionAdvisor"
	      p:proxyTargetClass="true"/>

虽然引介切面和其他切面由很多的不同,但却可以采用相似的Spring配置方式配置引介切面。

五. 深入Spring数据库事务管理

1. Spring数据库事务管理器的设计

Spring中数据库事务都是通过PlatformTransactionManager进行管理

2. Spring支持的事务管理类型

a. 编程式事务(声明式事务管理和应用程序的关联关系较少,故多用声明式事务管理)
b. 声明式事务

3. 数据库事务的特性

a. 原子性(Atomicity):整个事务的所有操作,要么全部完成,要么全部失败,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前状态;
b. 一致性(Consistency):事务可以改变封装状态。不管在任何给定时间并发事务有多少,事务必须始终保持系统处于一致的状态;
c. 隔离性(Isolation):两个事务之间的隔离程度;
d. 持久性(Durability):在事务完成后,该事务对数据库所做的更改持久保存在数据库中,不会被回滚。

4. 事务的隔离级别

a. 脏读(READ_UNCOMMITED):允许一个事务去读取另一个事务中未提交的数据(读写均不加锁);
b. 读/写提交(READ_COMMITED ):一个事务只能读取到另一个事务已经提交的数据(只对写加行级锁);
c. 可重复读(REPEATABLE READ):使得同一条数据库记录的读/写按照一个序列化进行操作(对读和写都加行级锁);
d. 序列化(SERIALIZABLE ):让SQL按照顺序读/写,消除数据库事务之间并发产生数据不一致的问题(表级锁)。

5. 传播机制

5.1 为什么会有传播机制

  • spring 对事务的控制,是使用 aop 切面实现的,我们不用关心事务的开始,提交 ,回滚,只需要在方法上加@Transactional 注解,这时候就有问题了。

    场景一: serviceA 方法调用了 serviceB 方法,但两个方法都有事务,这个时候如果 serviceB 方法异常,是让 serviceB 方法提交,还是两个一起回滚。
    场景二:serviceA 方法调用了 serviceB 方法,但是只有 serviceA 方法加了事务,是否把 serviceB 也加入 serviceA 的事务,如果 serviceB 异常,是否回滚 serviceA 。
    场景三:serviceA 方法调用了 serviceB 方法,两者都有事务,serviceB 已经正常执行完,但 serviceA 异常,是否需要回滚 serviceB 的数据。

5.2 传播机制生效条件

  • 因为 spring 是使用 aop 来代理事务控制 ,是针对于接口或类的,所以在同一个 service 类中两个方法的调用,传播机制是不生效的

5.3 传播机制类型

  • 下面的类型都是针对于被调用方法来说的,理解起来要想象成两个 service 方法的调用才可以。

  • PROPAGATION_REQUIRED (默认)
    支持当前事务,如果当前没有事务,则新建事务;如果当前存在事务,则加入当前事务,合并成一个事务。

  • REQUIRES_NEW
    新建事务,如果当前存在事务,则把当前事务挂起这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交。

  • NESTED
    如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交。如果当前没有事务,则新建事务;如果它异常,父级可以捕获它的异常而不进行回滚,正常提交;但如果父级异常,它必然回滚,这就是和 REQUIRES_NEW 的区别。

  • SUPPORTS
    如果当前存在事务,则加入事务;如果当前不存在事务,则以非事务方式运行,这个和不写没区别。

  • NOT_SUPPORTED
    以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • MANDATORY
    如果当前存在事务,则运行在当前事务中;如果当前无事务,则抛出异常,也即父级方法必须有事务。

  • NEVER
    以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务。

    一般用得比较多的是 PROPAGATION_REQUIRED ,REQUIRES_NEW;REQUIRES_NEW 一般用在子方法需要单独事务。

6. @Transactional的自调用失效问题

【问题】注解@Transactional的底层实现原理是Spring AOP技术,而Spring AOP是基于动态代理,故对于静态方法和非public方法,注解@Transactional是失效的。自调用(一个类的一个方法调用自身另外一个方法)也会使@Transactional失效
【解决】法一:为其生成一个代理对象; 法二:直接从容器中获取代理对象

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值