再学习之Spring(面向切面编程).
一、概念1、理论? ? 把横切关注点和业务逻辑相分离是面向切面编程所要解决的问题。如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或 组成(delegation)。但是,如果在整个应用中都使用相同的基类,继承往往会导致一个脆弱的对象体系;而使用组成可能需要对委托对象进行复杂的调用。切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。Spring AOP 基于动态代理,所以Spring只支持方法连接点,这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。? 2、AOP术语横切关注点(cross-cutuing concern):散布在应用中多处的功能。 切面(aspect) : 横切关注点模块化为特殊的类。切面是通知和切点的结合。 通知(advice):定义了切面是什么以及何时使用。 Spring切面可以应用5种类型的通知: 前置通知(Before):在目标方法被调用之前调用通知功能; 切点(pointcut):定义了切面在何处调用,会匹配通知所要织入的一个或多个连接点。 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。 织入有三种方式可以实现,Spring采用的是第三种,在运行期织入的: 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。 3、AspectJ的切点表达式语言? ? 注意:只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器 。同时需要注意的是, 表达式之间允许用 &&(and)、||(or)、!(not) 来匹配复杂的被通知类。除了上面罗列的表达式外,Spring 还提供了一个Bean 表达式来匹配 Bean 的id,例如??execution(* com.service.Performance.perform(..)) && bean(performance) @args的正确用法:自定义一个ElementType.TYPE的注解,这个注解用来修饰自定义类型(比如自己写的一个类),一个方法以这个自定义的类的实例为参数且只能有这唯一一参数,那这个方法在调用时会被匹配@args(自定义注解)的切面拦截。 @target (cn.javass.spring.chapter6.Secure)??任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用。 二、使用注解创建切面1、添加pom.xml依赖<dependency> groupId>org.aspectj</artifactId>aspectjrtversion>1.6.11> >aspectjweaver>View Code 2、定义切面@Aspect //表示这是一个切面类 public class Audience { 使用简明的PointCut @Pointcut("execution(* com.service.Performance.perform(..))") void performance(){} 前置通知 即 @Before("execution(* com.service.Performance.perform(..))") @Before("performance()" silenceCellPhones(){ System.out.println("Silencing cell phones"); } takeSeats(){ System.out.println("Taking seats"); } 方法调用结束通知(并不是指返回值通知,即使是void的返回值,仍然会触发通知) 即 @AfterReturning("execution(* com.service.Performance.perform(..))") @AfterReturning("performance()" applause(){ System.out.println("CLAP CLAP CLAP!!!"有异常抛出的时候通知,即 @AfterThrowing("execution(* com.service.Performance.perform(..))") @AfterThrowing("performance()" demandRefund(){ System.out.println("Demanding a refund"); } }View Code 3、启用AspectJ注解的自动代理?有两种方式可以启用AspectJ 注解的自动代理: (1)在 Java 配置文件中显示配置 @Configuration @EnableAspectJAutoProxy 启用Aop自动代理 JavaConfig { @Bean public Audience getAudience(){ return new Audience(); } }View Code (2)在XML文件中配置 <!--启用AspectJ自动代理--> aop:aspectj-autoproxy/> bean id="audience" class="com.aspect.Audience"/>View Code 不管你是使用JavaConfig还是XML,AspectJ自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。当程序执行到连接点的时候,就会由代理转到切面触发相应的通知。 4、创建环绕通知@Aspect Audience3 { @Pointcut("execution(* com.service.Performance.perform(..))" performance(){} @Around("performance()" watchPerformance(ProceedingJoinPoint joinPoint) { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); try { joinPoint.proceed(); System.out.println("CLAP CLAP CLAP!!!"); } catch (Throwable throwable) { System.out.println("Demanding a refund"); throwable.printStackTrace(); } } }View Code ?注意?ProceedingJoinPoint?作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。? 5、切面匹配输入参数TrackCounter { private Map<Integer,Integer> trackCounts=new HashMap<Integer,Integer>(); @Pointcut("execution(* com.service.CompactDisc.playTrack(int)) && args(trackNumber)") 带入输入参数 @Pointcut("target(com.service.CompactDisc) && args(trackNumber)") target 匹配目标对象(非AOP对象)为指定类型 @Pointcut("within(com.service..*) && args(trackNumber)") com.service 包以及子包下的所有方法都执行 @Pointcut("within(com.service..CompactDisc+) && args(trackNumber)") com.service 包的CompactDisc类型以及子类型 @Pointcut("this(com.service.CompactDisc) && args(trackNumber)") 匹配当前AOP代理对象类型,必须是类型全称,不支持通配符 void trackPlayed(int trackNumber){} @Before("trackPlayed(trackNumber)"void countTrack( trackNumber){ int playCount = getPlayCount(trackNumber); trackCounts.put(trackNumber,playCount+1); System.out.println(trackCounts.toString()); } int getPlayCount(return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }View Code ?参数的配置可以用占位符 * 和 ..? ?* 的意思是任意类型任意名称的一个参数 ?.. 的意思是任意类型,任意多个参数,并且只能放到args的后面。? 6、利用切面注入新功能? ??Java并不是动态语言。一旦类编译完成了,我们就很难为该类添加新的功能了。但是,我们的切面编程却可以做到动态的添加方法...话虽如此,其实也不过是障眼法罢了。实际上,面向切面编程,不过是把方法添加到切面代理中,当要对添加的方法调用的时候,可以把被通知的 Bean 转换成相应的接口。也就是代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中。(说实话,想了半天,实在想不到这个功能有什么作用......) (1) 重新定义一个接口和实现类 interface Encoreable { performEncode(); }View Code class DefaultEncoreable implements Encoreable { performEncode() { System.out.println("this is DefaultEncoreable"); } }View Code (2) 把接口实现类嵌入到目标类代理中 EncoreableIntroducer { @DeclareParents(value = "com.service.CompactDisc+",defaultImpl = DefaultEncoreable.class) value 表示要嵌入哪些目标类的代理 。 defaultImpl:表示要嵌入的接口的默认实现方法 static Encoreable encoreable; }View Code (3) JUnit 测试 @RunWith(SpringJUnit4ClassRunner.) @ContextConfiguration(locations = "classpath:applicationContext.xml") Test02 { @Autowired private CompactDisc compactDisc; @Test test02(){ compactDisc.playTrack(123); Encoreable compactDisc = (Encoreable) this.compactDisc; 当要调用添加的新功能的时候,这个用法相当于由代理转换到对应类实现,不会报类型转换错误 compactDisc.performEncode(); } }View Code 三、使用XML声明切面? 1、定义切面AudienceXML { ); } }View Code 2、XML配置切面aop:configaop:aspect ref="audienceXML"aop:pointcut ="performance" expression="execution(* com.service.Performance.perform(..))"/> aop:before method="silenceCellPhones" pointcut-ref="performance"="takeSeats"aop:after-returning ="applause"aop:after-throwing ="demandRefund"/> aop:aspect> >View Code 3、创建环绕通知Audience3XML { ); throwable.printStackTrace(); } } }View Code ="audience3XML"="performance3"aop:around ="watchPerformance"="performance3">View Code 4、匹配输入参数="trackCounter"="trackPlayed"="execution(* com.service.CompactDisc.playTrack(int)) and args(trackNumber)"="countTrack"="trackPlayed">View Code 5、注入新功能aop:declare-parents types-matching="com.service.CompactDisc+" implement-interface="com.service.Encoreable" default-impl="com.service.impl.DefaultEncoreable" delegate-ref="encoreableDelegate"/> >View Code (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |