本章主要内容:java
若是你有幸能看到。git
谈一些我的感觉github
软件系统中的一些功能就像咱们家里的电表同样。则核心功能须要用到应用程序的多个地方。可是咱们又不想在每一个点都明确调用它。日志、安全、事务管理的确很重要。但它们是否为应用对象主动参与的行为呢?若是让应用对象只关注与本身所针对的业务领域问题,而其余方面的问题由其余应用对象来处理,这样不更好吗?express
在软件开发中,散布于应用中多出功能被称为横切关注点(crosscutting concern)。一般来说横切关注点从概念上是与应用的业务逻辑分离的。但每每是耦合在一块儿的,把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。编程
依赖注入(DI)管理咱们的应用对象,DI有助于应用对象之间解耦。而AOP能够实现横切关注点与它们所影响的对象之间的耦合。安全
切面可以帮咱们模块化横切关注点。简而言之,横切关注点能够被描述为影响应用多处的功能。例如 安全,事务、日志等功能。app
若是要重用对象的话,最多见的面向对象技术是继承、委托、组合。可是,若是整个应用中都使用相同的基类,继承每每会致使一个脆弱的对象体系。而使用委托可能须要委托对象进行复杂的调用。框架
切面提供了取代继承和委托的另外一种可选方案。在使用面向切面编程时,咱们仍然在一个地方定义通知功能,而无需修改受影响的类。横切关注点能够被模块化为特殊的类,这些类被称为切面(aspect). 这样作带来两个好处:每一个关注点都集中到一个地方,而不是分散到多处代码中:其次,服务模块更简洁,由于它只包含了主要关注点(核心功能)的代码。而次要关注的代码被移到切面中了。编程语言
描述切面的经常使用术语有:通知(advice)、切点(pointcut)、(链接点)。ide
通知(advice)
通知定义了切面是什么以及什么时候使用。除了描述切面要完成的工做外,通知还解决了什么时候执行这个工做问题。它应该在某个方法被调用以前?以后?以前和以后都调用?仍是只在方法抛出异常时调用?
Spring切面能够应用5中类型的通知:
链接点
咱们的应用可能有数以千计的时机应用通知,这些时机被称为链接点。链接点是在应用执行过程当中可以插入的一个点。这个点能够是调用方法时,抛出异常时,甚至修改一个字段时。切面能够利用这些点插入到应用的正常流程之中,并添加新的行为。
切点 若是说通知定义了切面的的“什么”和“什么时候”,那么切点定义了“何处”。切点的定义会匹配通知所要织入的一个或多个链接点。
切面 切面是通知和切点的结合。通知和切点经过定义了切面的所有 内容——他是什么,在何时和在哪里完成其功能。
引入 引入容许咱们向现有的类添加新的方法或者属性。
织入
织入是把切面应用到目标对象并建立新的代理对象的过程。切面在指定的链接点被织入到目标对象。在目标对象的生命周期里有多个点能够进行织入:
通知包含了须要用于多个应用对象的横切关注点。链接点是程序执行过程当中可以应用通知的全部点。切点定义了通知被应用的具体位置(在哪些链接点),其中关键是切点定义了哪些链接点会获得通知。
并非全部的AOP框架都是相同的,他们在链接点模型上可能有强弱之分。有些容许在字段修饰符级别的通知,而另外一些只支持与方法调用相关的链接点。它们织入切面的方式和时机也有所不一样。可是,不管如何,建立切点来定义切面所织入的链接点是AOP的基本功能。
Spring提供了4种类型的AOP支持:
前三种都是Spirng AOP实现的变体,Spring AOP构建在动态代理基础上。所以,Spring对AOP的支持局限于方法拦截。
引入了简单的声明式AOP与基于注解的AOP以后,Spring经典的看起来就显得很是笨拙和过于复杂话,直接使用ProxyFactory bean 会让人感受厌烦。
借助于Spring的aop命名空间,咱们能够将纯POJO转为切面。
Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。本质上,它依然是Spring基于代理的AOP,可是编程模型几乎与编写成熟的AspectJ注解切面彻底一致。这种AOP风格的好处在于可以不使用XML来完成功能。
Spring所建立的通知都是用标准的Java类编写的,定义通知所应用的切点一般会使用注解或在Spring配置文件里采用XML来编写
通知带代理类中包裹切面,Spring在运行时把切面织入到Spring所管理的bean中。代理类封装了目标类,并拦截被通知方法的调用。再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法以前,会执行切面逻辑。直到应用须要被代理bean时,Spring才会建立代理对象。若是使用ApplicationContext的话,在ApplicationContext从BeanFactory中加载全部的bean的时候,Spring才会建立被代理的对象。由于Spirng运行时才建立代理对象,因此咱们不须要特殊的编译器来织入Spring AOP的切面。
Spring基于动态代理,因此Spring只支持方法链接点。方便拦截能够知足大部分的需求。
切点用于准肯定位应该在什么地方应用切面的通知。通知和切点是切面最基本的元素。
Spring仅支持AspectJ切点指示器的一个子集。Spring是基于代理的,而某些切点表达式是基于代理的AOP无关的。
Spring支持的指示器,只有execution指示器是实际执行匹配的,而其余的指示器都是用来限制匹配的。这说明execution指示器是咱们在编写切点定义时最主要的指示器。
为了阐述Spring中的切面, 咱们须要有个主题来定义切面的切点。
package com.guo.cocert; public interface Performance { public void perform(); }
execution(* concert.Performance.perform(..))
咱们使用execution()指示器选择Performance的perform()方法,方法表达式以"*"号开始,代表了咱们不关心方法返回值的类型。而后指明了全限定类名和方法名,对于方法参数列表,咱们使用了两个点号(..)代表切点要选择任意的perform()方法,不管该方法的入参是什么。
如今假设咱们须要配置的切点仅匹配concert包,可使用within()指示器
execution(* concert.Performance.perform(..)) && within(concert.*)
由于“&”在XMl中有特殊的含义,因此在Spring和XML配置中,描述切点时,可使用and代替“&&”。
Spring引入了一个新的bean()指示器,它容许咱们在切点表达式中使用bean的ID来标识bean。bean()使用bean ID 或 bean 名称做为参数来限制切点只匹配特定的bean。
execution(* concert.Performance.perform(..)) and bean("woodsotck")
也能够这样
execution(* concert.Performance.perform(..)) and !bean("woodsotck")
切面的通知会被编织到全部ID不为woodsotck的bean中。
使用注解来建立切面是AspectJ 5所引入的关键特性。
若是一场演出没有观众的话,那不能称之为演出。
@AspectJ public class Audience { }
Audience类使用@AspectJ注解进行了标注。该注解代表Audience不只仅是一个POJO,仍是一个切面。Audience类中的方法都是使用注解来定义切面的具体行为。
@AspectJ public class Audience { @Pointcut("execution(* * concern.Performance.perform(..))") public void performance() {}; }
在Autience中,performance()方法使用了@Pointcut注解。为@Pointcut注解设置的值是一个切点表达式,就像以前在通知注解上所设置的那样。
须要注意的是,除了注解和没有实际操做的performa()方法,Audience类依然是一个POJO,咱们可以像使用其余的Java类那样调用它的方法,它的方法也能独立的进行单元测试。与其余Java类没有什么区别。
像其余的Java类同样,它能够装配为Spring中的bean
@Bean public Audience audience() { return new Audience(); }
若是你就此止步的话,Audience只会是Spring容器中的一个bean。即使使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不会建立将其转化为切面的代理。
若是你使用JavaConfig的话,能够在配置类的级别上经过使用EnableAspectJ-AutoProxy
注解启用自动代理功能。
@Configuration @EnableAspectJAutoProxy //启用AspectJ自动代理 @ComponentScan public class ConcertConfig { @Bean public Audience autidence() { //声明Audience bean return new Audience(); } }
假如你在Spring中使用XMl来装配bean的话,那么须要使用Spring aop命名空间中的<aop:aspect-autoproxy>元素
<?xml version="1.0" encoding="UTF-8"?> 、、、、、、、、、、、、、、、、、、、、、、、、 <context:component-scan base-package="com.guo.concert"/> <aop:aspect-autoproxy/> <bean class="com.guo.concert.Audience"/>
无论你使用JavaConfig仍是XML,AspecJ自动代理都会使用@Aspect注解的bean建立一个代理。这个代理会围绕着全部该切面的切点所匹配的bean。
咱们须要记住的是,Spring的AspectJ自动代理仅仅使用@AspectJ做为建立切面的指导,切面依然是基于代理的。本质上它依然是Spring基于代理的切面。
环绕通知是最为强大的通知类型,它可以让你编写的逻辑将被通知的目标方法安全包装起来,实际上就像在一个通知方法中同时编写前置通知和后置通知。
@AspectJ public class Audience { @Pointcut("execution(* * concern.Performance.perform(..))") public void performance() {}; @Around public void xx(Xxx jp) { ....... jp.proced() } }
在这里,@Around注解,代表这个xx()方法会做为performance()切点的环绕通知。
这个通知所达到的效果与以前的前置通知和后置通知是同样的。
须要注意的是,别忘记调用proceed()方法,若是不调用这个方法,那么你的通知实际上会阻塞对被通知方法的调用,有意思的是,你能够不调用proceed方法,从而阻塞对被通知方法的反问,
一些编程语言,例如:Ruby和Groovy,有开放来的理念,它们能够不直接使用修改对象或类的定义就可以为对象或类增长新的方法。不过Java并非动态语言,一旦编译完成了,就很难在为该类添加新的功能了。
若是切面可以为现有的方法增长额外的功能,为何不恩那个为一个对象增长新的方法呢?利用引入AOP的概念,切面能够为Spring bean 添加新的方法。
在Spring中,注解和自动代理提供了一种便利的方式来建立切面,它很是简单,而且只设计最少的Spring配置,可是,面向注解的切面有一个明显的不足点:你必须可以为通知类添加注解,为了作到这一点,必需要有源码。
以前,有这样一条原则:那就是基于注解的配置要优于Java的配置,基于Java的配置要优于XMl的配置,可是,若是你须要声明切面,可是又不能为通知类添加注解的时候 ,那么就必须转向XML配置了。
在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面,
咱们会使用Spring aop命名空间中的一些元素,将没有注解的Aurience类转为切面
<aop:config> <aop:aspect ref="audience"> <!--引用audience Bean--> <aop:before pointcut="execution(* * concert.Performance.perform(..))" method="silenceCellIphones"/> <aop:before pointcut="execution(* * concert.Performance.perform(..))" method="takeSeats"/> <aop:after-returning pointcut="execution(* * concert.Performance.perform(..))" method="applause"/> <aop:after-throwing pointcut="execution(* * concert.Performance.perform(..))" method="demandRefund"/> </aop:aspect> </aop:config>
第一须要注意的就是大多数AOP配置元素必须在<aop:config>元素的上下文中使用。
在全部的通知元素中,pointcut属性定义了通知所应用的切点,它的值是使用AspectJ切点表达式语法所定义的切点。
在基于Aspectj注解的通知中,当发如今这些类型的重复时,使用@Pointcut注解来消除这些重复的内容。
以下的XMl配置展现了如何将通用的切点表达式抽取到一个切点声明中,这样,这个声明就能在全部的通知元素中使用了
<aop:config> <aop:aspect ref="audience"> <!--引用audience Bean--> <aop:pointcut id="performance" expression="execution(* * concert.Performance.perform(..))" /> <aop:before pointcut="" method="silenceCellIphones"/> <aop:before pointcut-ref="performance" method="takeSeats"/> <aop:after-returning pointcut-ref="performance" method="applause"/> <aop:after-throwing pointcut-ref="performance" method="demandRefund"/> </aop:aspect> </aop:config>
如今的切点是一个地方定义的,而且被多个通知元素所引用,<aop:pointcut>元素定义了一个id为performance的切点,同时修改全部的通知元素,用pointcut0ref来引用这个命名切点。
相比于前置通知和后置通知,环绕通知在这点上有明显的优点。使用环绕通知,咱们能够完成前置通知和后置通知所实现的相同功能,并且只须要在一个方法中实现。由于整个通知逻辑都是在一个方法中实现的。
<aop:config> <aop:aspect ref="audience"> <!--引用audience Bean--> <aop:pointcut id="performance" expression="execution(* * concert.Performance.perform(..))" /> <aop:around pointcut-ref="performance" method="watchPerformance"/> </aop:aspect> </aop:config>
像其余通知的XML元素同样,<aop:around>指定了一个切点和一个通知方法的名字。
区别在于切点表达式中包含了一个参数,这个参数传递到通知方法中。还有区别就是这里使用了and关键字
借助于AspectJ的@DeclareParents注解为被通知的方法引入新的方法。可是AOP引入并非Aspectj特有的。使用Spring aop命名空间中的<aop:declare-parents>元素,咱们能够实现相同的功能
<aop:config> <aop:aspect ref="audience"> <!--引用audience Bean--> <aop:declare-parents types-matching="concert.Performance" implement-interface="concert.Encoreable" default-impl="concert.DefaoultEncoreable" </aop:aspect> </aop:config>
虽然Spring AOP可以知足许多应用的切面需求,可是与AspectJ相比,Spring AOP是一个功能比较弱的AOP解决方案,ASpect提供了Spring AOP 所不能支持的许多类型的切点。
Spring不能像以前那样使用<bean>声明来建立一个实例----它已经在运行时由AspectJ建立完成了,Spring须要经过工厂方法获取切面的引用。而后像<bean>元素规定的那样在该对象上执行依赖注入
AOP是面向对象编程的一个强大补充,经过AspectJ,咱们如今能够把以前分散在应用各处的行为放入可重用的模块中。咱们显示地声明在何处如何应用该行为。这样有效减小了代码冗余,并让咱们的类关注自身的主要功能。
Spring提供了一个AOP框架,让咱们把切面插入到方法执行的周围。如今咱们已经学会了如何把通知织入前置,后置和环绕方法的调用中,以及为处理异常增长自定义行为。
关于在Spirng应用中如何使用切面 ,咱们能够有多种选择。经过使用@AspectJ注解和简化的配置命名空间,在Spring中装配通知和切点变得很是简单
最后,当Spring不能知足需求时,咱们必须转向更为强大的AspectJ。对于这些场景,咱们了解了如何使用Spring为AspectJ切面注入依赖。
此时此刻,咱们已经覆盖了Spring框架的基础知识,了解到如何配置Spring容器以及如何为Spring管理的对象应用切面,这些技术为建立高内聚,低耦合的应用奠基了坚实的基础。
从下一章开始,首先看到的是如何使用Spring构建Web应用。。
期待......