标签:
4.1)暂时了Spring对切面的支持,包括如何把普通类声明为一个切面和如何使用注解创建切面;4.2)除此之外,还可以看到 AspectJ——另一种流行的AOP实现——如何补充Spring AOP的功能;(干货——引入AspectJ框架)
1.1)problem:如果要重用通用功能的话,最常见的技术是继承或委托。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;1.2)solution:切面提供了取代继承和委托的另一种可选方案;
m1)现在每个关注点都集中于一个地方,而不是分散到多处;m2)服务模块更加简洁了,因为它们只包含主要关注点的代码,而次要关注点的代码被转移到切面中了;
1.1)通知(Advice):切面的工作被称为通知;通知定义了切面是什么以及何时使用;(5中类型的通知:前置通知,后置通知,返回通知,异常通知,环绕通知)
1.2)连接点(Join point):是在应用执行过程中能够插入切面的一个点;1.3)切点(Pointcut):如果说通知定义了切面是什么和何时执行的话,那么切点就定义了 何处(在何处执行),切点的定义会匹配通知所要织入的一个或多个连接点;1.4)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么?1.5)引入(Introduction):引入允许我们向现有的类添加新方法或属性;1.6)织入(Weaving):织入是吧切面应用到目标对象并创建新的代理对象的过程;切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
peroid1)编译期:切面在目标类编译时被织入;AspectJ的织入编译器就是以这种方式织入切面的;peroid2)类加载期:切面在目标类加载到 jvm 时 被织入;这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码;AspectJ 5 的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面;period3)运行期:切面在应用运行的某个时刻被织入;一般case下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。Spring AOP 就是以这种方式织入切面的;
type1)基于代理的经典 spring AOP;type2)纯 POJO 切面;type3)@AspectJ 注解驱动的切面;type4)注入式 AspectJ 切面(适用于spring 各种version);
A1)前三种都是 spring aop 实现的变体,spring aop 构建在动态代理基础之上:因此,spring 对 aop的支持局限于方法拦截;(干货——spring 对 aop的支持局限于方法拦截)A2)spring 借鉴了AspectJ 的切面,以提供注解驱动的AOP。本质上,它依然是 spring 基于代理的aop,但是编程模型几乎与编写成熟的 AspectJ 注解切面完全一致。这种aop 风格的好处在于能够不使用XML 来完成功能;
4.1)因为spring基于动态代理,所以spring 只支持方法连接点。这与一些其他的AOP 框架是不同的,例如 AspectJ 和 JBoss,除了方法切点外,它们还提供了字段和构造器接入点;(干货——因为spring基于动态代理,所以spring 只支持方法连接点)4.2)方法拦截可以满足绝大部分的需求;如果需要方法拦截之外的连接点拦截功能,可以利用 Aspect 来补充 spring aop 的功能;
public interface Performance { public void perform(); }2)表达式execution(* concert.Performance.perform(..)):这个表达式能够设置当 perform()方法执行时触发通知的调用;
execution(* concert.Performance.perform()) and bean(‘jaychou‘) :我们希望在执行Performance.perform方法时应用通知,但限定bean的ID为 jaychou;execution(* concert.Performance.perform()) and !bean(‘jaychou‘): 我们希望在执行Performance.perform方法时应用通知,但限定bean的ID不为 jaychou;
@Aspect public class Examinee { // 考生 @Before("execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))") public void checkIdentity() { // 身份审查 System.out.println("please show us your admission ticket."); } @Before("execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))") public void checkSecurity() { // 安检 System.out.println("please accept electronic equipment inspection."); } @AfterReturning("execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))") public void handinPaper() { // 交卷 System.out.println("please hand in an examination paper."); } @AfterThrowing("execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))") public void cancelQualification() { // 取消考试资格 System.out.println("cancel the exam qualification"); } }
A1)spring使用了 AspectJ 注解来声明通知方法, 如下表所示:
A2)上面的注解都给定了一个切点表达式作为它的值,且这4个切点表达式都是相同的,其实它们可以设置成为不同的切点表达式;
3.1)problem:相同的切点表达式出现了四遍,这可是不光彩的事情;3.2)solution:引入了 @PointCut注解, 该注解能够在一个 @AspectJ 切面内定义可重用个切点;@Aspect public class ExamineeVersion2 { @Pointcut("execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))") public void chineseExamination(){} @Before("chineseExamination()") public void checkIdentity() { // 身份审查 System.out.println("please show us your admission ticket."); } @Before("chineseExamination()") public void checkSecurity() { // 安检 System.out.println("please accept electronic equipment inspection."); } @AfterReturning("chineseExamination()") public void handinPaper() { // 交卷 System.out.println("please hand in an examination paper."); } @AfterThrowing("chineseExamination()") public void cancelQualification() { // 取消考试资格 System.out.println("cancel the exam qualification"); } }
A1)在 Examinee中,chineseExamination()方法使用了 @Pointcut 注解,为@Pointcut注解设置的值是一个切点表达式;A2)实际上扩展了切点表达式语言,这就可以在 任何的切点表达式中使用 chineseExamination()方法了;
4.1)problem:如果仅仅是以上代码的话,即便使用了AspectJ 注解,但它也不会被视为切面,这些注解不会解析,也不会创建将其转换为切面的代理;4.2)solution:如果使用JavaConfig的话,可以在配置类的类级别上通过使用 @EnableAspectJAutoProxy 注解启用自动代理功能;(干货——@EnableAspectJAutoProxy 注解的作用)@Configuration @EnableAspectJAutoProxy @ComponentScan public class ExaminationConfig { @Bean public Examinee getExaminee() { return new Examinee("xiaotang"); } }
4.3)如果使用XML配置类装配bean的话,使用 spring aop命名空间中的 <aop:aspectj-autoproxy> 元素:
A1)不管使用JavaConfig 还是 XML配置来装配:AspectJ 自动代理都会为使用 @AspectJ 注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean;A2)在这种case下,将会为 Examination bean创建一个代理,Examinee类中的通知方法将会在 chineseExamination() 方法调用前后执行;A3)spring的 AspectJ 自动代理仅仅是使用 @AspectJ 作为创建切面的指导,切面依然是基于代理的;本质上,它依然是 spring基于代理的切面;
// 考生(就是一个切面) @Aspect public class ExamineeVersion3 { @Pointcut("execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))") public void chineseExamination() { } @Around("chineseExamination()") public void doChineseExamination(ProceedingJoinPoint jp) { try { System.out.println("please show us your admission ticket."); System.out.println("please accept electronic equipment inspection."); jp.proceed(); System.out.println("please hand in an examination paper."); } catch (Throwable e) { System.out.println("cancel the exam qualification"); } } }
A1)@Around注解表明 doChineseExamination() 方法会作为 chineseExamination() 切点的环绕通知;(干货——@Around注解的作用)A2)别忘记调用 ProceedingJoinPoint.proceed()方法,如果不调用这个方法的话,那么你的通知实际上会阻塞对被通知方法的调用;A3)当然,你可以不调用ProceedingJoinPoint.proceed()方法,你也可以在通知中对它进行多次调用;
A1)这里的不同点在于切点还声明了要提供给通知方法的参数;
A2)关注 args(trackNumber))限定符:它表明传递给 playTrack()方法的int类型参数也会传递到通知里面去,参数的名称 trackNumber 也与切点方法签名中的参数相匹配;A3)这个参数会传递到通知方法中:该方法是通过 @Before 注解和命名切点 trackPlayed(trackNumber)定义的。切点定义中的参数与切点方法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数转移;
@Aspect public class ExamineeArgs { private String name; public ExamineeArgs(String name) { this.name = name; } @Pointcut("execution(* com.spring.chapter4.CollegeEntraceExamination.getExamineeNumber(int)) " + "&& args(examSiteCode)") public void myGetExamineeNumber(int examSiteCode) { } @Before("myGetExamineeNumber(examSiteCode)") public void checkIdentity(int examSiteCode) { // 身份审查 System.out.println("@Before: your exam site code is " + examSiteCode); } }
@Aspect public class ExamineeArgs { private String name; public ExamineeArgs(String name) { this.name = name; } @Pointcut("execution(* com.spring.chapter4.CollegeEntraceExamination.getExamineeNumber(int)) " + "&& args(examSiteCode)") public void myGetExamineeNumber(int examSiteCode) { } @Before("myGetExamineeNumber(examSiteCode)") public void checkIdentity(int examSiteCode) { // 身份审查 System.out.println("@Before: your exam site code is " + examSiteCode); } } // 高考 public class CollegeEntraceExamination { public CollegeEntraceExamination() { } public void chineseExamination() { System.out.println("chinese examination is underway."); } public int getExamineeNumber(int examSiteCode) { return 1000; } } //---------------------------------------我是分割线.
@Aspect public class AdditionExaminationIntroducer{ @DeclareParents(value="com.spring.chapter4.CollegeEntraceExamination.chineseExamination+", defaultImpl=AdditionExaminationImpl.class) public static AddtionExamination addtionExamination; }
public interface AddtionExamination { void doAddtionalExamination(); } public class AdditionExaminationImpl implements AddtionExamination{ @Override public void doAddtionalExamination() { System.out.println("do additional questions as possible as you can."); } }
A1)这个切面与以往切面不同的是:它并没有前置,后置等通知,而是通过 @DeclareParents注解将 AddtionExamination 接口引入到 CollegeEntraceExamination bean中;A2)@DeclareParents注解由3个部分组成:(干货——@DeclareParents注解的作用)
part1)value属性:指定了哪种类型的bean 要引入该接口;part2)defaultImpl:指定了为引入功能提供实现的类;part3)@DeclareParents注解所标注的静态属性:指明了要引入的接口;
A3)和其它切面一样,需要在spring 应用中将 AdditionExaminationIntroducer 声明为 一个 bean;<bean class="com.spring.chapter4.AdditionExaminationIntroducer" />spring 的自动代理机制将会获取到它的声明,但 spring 发现一个bean使用 @Aspect注解时,spring就会创建一个代理,然后将调用委托给被代理bean 或被引入的实现,这取决于调用的方法属于被代理的bean 还是属于被引入的接口;(干货——这解释了为什么一个spring bean的实现被拆分到了多个类中的问题)
对上图的分析(Analysis):我们看到,即使CollegeEntraceExamination 并没有实现 AddtionExamination接口,但是通过创建新切面为前者添加了新方法,CollegeEntraceExamination 就可以调用了doAddtionalExamination()方法了;
<aop:config> <aop:aspect ref="examinee"> <aop:before pointcut="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" method="checkIdentity"/> <aop:before pointcut="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" method="checkIdentity"/> <aop:after-returning pointcut="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" method="handinPaper"/> <aop:after-throwing pointcut="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" method="cancelQualification"/> </aop:aspect> </aop:config>
<aop:config> <aop:aspect ref="examinee"> <aop:pointcut id="chineseExamination" expression="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" /> <aop:before pointcut-ref="chineseExamination" method="checkIdentity"/> <aop:before pointcut-ref="chineseExamination" method="checkIdentity"/> <aop:after-returning pointcut-ref="chineseExamination" method="handinPaper"/> <aop:after-throwing pointcut-ref="chineseExamination" method="cancelQualification"/> </aop:aspect> </aop:config>
<?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-3.0.xsd "> <aop:aspectj-autoproxy /> <aop:config> <aop:aspect ref="examineeXML"> <aop:pointcut id="chineseExamination" expression="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" /> <aop:before pointcut-ref="chineseExamination" method="checkIdentity" /> <aop:before pointcut-ref="chineseExamination" method="checkSecurity" /> <aop:after-returning pointcut-ref="chineseExamination" method="handinPaper" /> <aop:after-throwing pointcut-ref="chineseExamination" method="cancelQualification" /> </aop:aspect> </aop:config> </beans>
// 考生(就是一个切面) public class ExamineeAroundXML { private String name; public ExamineeAroundXML(String name) { this.name = name; } public void attendChineseExamination(ProceedingJoinPoint jp) { try { System.out.println("AroundXML@Before: please show us your admission ticket."); System.out.println("AroundXML@Before: please accept electronic equipment inspection."); jp.proceed(); System.out.println("AroundXML@AfterReturn: please hand in an examination paper."); } catch (Throwable e) { System.out.println("AroundXML@AfterThrowing: cancel the exam qualification"); } } }
<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="chineseExamination" expression="execution(** com.spring.chapter4.CollegeEntraceExamination.chineseExamination(..))" /> <aop:around pointcut-ref="chineseExamination" method="attendChineseExamination"/> </aop:aspect> </aop:config>
@Configuration @ImportResource("classpath:com/spring/chapter4/spring4-args-config.xml") public class ExaminationConfig { @Bean public CollegeEntraceExamination getExamination() { return new CollegeEntraceExamination(); } @Bean(name="examineeXMLArgs") public ExamineeXMLArgs getExamineeXMLArgs() { return new ExamineeXMLArgs("xiaotang"); } }
<?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-3.0.xsd "> <aop:aspectj-autoproxy /> <aop:config> <aop:aspect ref="examineeXMLArgs"> <aop:pointcut id="getExamineeNumber" expression="execution(* com.spring.chapter4.CollegeEntraceExamination.getExamineeNumber(int)) and args(examSiteCode)" /> <aop:before pointcut-ref="getExamineeNumber" method="checkIdentity"/> </aop:aspect> </aop:config> </beans>
@Configuration @ImportResource("classpath:com/spring/chapter4/spring4-args-intro-config.xml") public class ExaminationConfig { @Bean public CollegeEntraceExamination getExamination() { return new CollegeEntraceExamination(); } @Bean(name="examineeXMLArgs") public ExamineeXMLArgs getExamineeXMLArgs() { return new ExamineeXMLArgs("xiaotang"); } }
<?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-3.0.xsd "> <aop:aspectj-autoproxy /> <aop:config> <aop:aspect ref="examineeXMLArgs"> <aop:pointcut id="getExamineeNumber" expression="execution(* com.spring.chapter4.CollegeEntraceExamination.getExamineeNumber(int)) and args(examSiteCode)" /> <aop:before pointcut-ref="getExamineeNumber" method="checkIdentity"/> </aop:aspect> <aop:aspect> <aop:declare-parents types-matching="com.spring.chapter4.CollegeEntraceExamination+" implement-interface="com.spring.chapter4.AddtionExamination" default-impl="com.spring.chapter4.AdditionExaminationImpl" <!-- delegate-ref="additionExaminationImpl" 前提是spring容器中有 additionExaminationImpl bean--> /> </aop:aspect> </aop:config> </beans>
标签:
原文地址:http://blog.csdn.net/pacosonswjtu/article/details/51564181