在软件开发中,散布于应用中多处的功能被称为横切关注点。这些横切关注点从概念上是与应用的业务逻辑相分离的。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。AspectJ---另外一种流行的AOP实现。 若是是要重用通用功能的话,最多见的面向对象技术是继承或委托。可是,若是在整个应用中都使用相同的基类,继承每每会致使一个脆弱的对象体系;而使用委托可能须要对委托对象进行复杂的调用。正则表达式
通知(Advice):定义了切面是什么以及什么时候使用。除了描述切面要完成的工做,通知还解决了什么时候执行这个工做的问题。spring
Spring切面能够应用五种类型的通知:express
链接点(Join Point):在用于执行过程当中可以插入切面的一个点。切面代码能够利用这些点插入到应用的正常流程中,并添加新的行为。编程
切点(PointCut):切点的定义会匹配通知全部织入的一个或多个链接点。一般使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。maven
切面(Aspect):切面是通知和切点的结合,通知和切点共同定义了切面的所有内容--它是什么,在什么时候和何处完成其功能。ide
引入(Introduction):引入容许咱们向现有的类添加新方法或属性。post
织入(Weaving):织入是把切面应用到目标对象并建立新的代理对象的过程。切面在指定的链接点被织入到目标对象中。在目标对象的生命周期里有多个点能够进行织入:ui
2.类加载期:切面在目标类加载到JVM时被织入。这种方式须要特殊的类加载器(ClssLoader),他能够在目标类被引入应用以前加强该目标类的字节码。this
3.运行期:切面在应用运行的某个时刻被织入。通常状况下,在织入切面时,AOP容器会为目标对象动态地建立一个代理对象。spa
Spring提供了4中类型的AOP支持。 1.基于代理的经典SpringAOP。
2.纯POJO切面。
3.@AspectJ注解驱动的切面。
4.注入式AspectJ切面
首先:定义一个HelloWorld接口:
public interface HelloWorld { void printHelloWorld(); void doPrint(); }
切点的配置
execution(* com.xrq.aop.HelloWorld.(..)) 切点表达式。execution 在方法执行时触发, com.xrq.aop.HelloWorld.(..) 筛选制定的方法。 com.xrq.aop.HelloWorld.(..)) 是链接点。??
<aop:config> <aop:aspect id="time" ref="timeHandler"> <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" /> <aop:before method="printTime" pointcut-ref="addAllMethod" /> <aop:after method="printTime" pointcut-ref="addAllMethod" /> </aop:aspect> </aop:config>
使用@AspectJ注解进行了标注,该注解代表 Advices 不只是一个POJO类,也是一个切面。 使用@PoinCut定义命名的切点,能够简化重复的代码。
package com.zhangguo.Spring052.aop02; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * 通知类,横切逻辑 * */ @Component @Aspect public class Advices { @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))") public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))") public void after(JoinPoint jp){ System.out.println("----------最终通知----------"); } }
2.JavaConfig启用切面的代理 在配置类的级别上经过使用@EnableAspectJAutoProxy 注解启动代理功能。
package com.zhangguo.Spring052.aop05; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration //用于表示当前类为容器的配置类,相似<beans/> @ComponentScan(basePackages="com.zhangguo.Spring052.aop05") //扫描的范围,至关于xml配置的结点<context:component-scan/> @EnableAspectJAutoProxy(proxyTargetClass=true) //自动代理,至关于<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> public class ApplicationCfg { //在配置中声明一个bean,至关于<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/> @Bean public User getUser(){ return new User(); } }
3.xml装配Bean,启动注解: aspectj-autoproxy 元素声明aop的启动。
<?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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.zhangguo.Spring052.aop02"> </context:component-scan> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
环绕通知接受 ProceedingJoinPoint做为参数。这个对象是必需要有的,由于你要在通知中经过它来调用被通知的方法。通知方法中能够作任何的事情,当要将控制权交给被通知的方法时,它须要调用ProceedingJoinPoint的Proceed()方法。
若是忘记调用Proceed()方法,通知实际上会阻塞对被通知方法的调用。也能够在通知中对它进行多长调用,实现充实逻辑。
package com.zhangguo.Spring052.aop04; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 通知类,横切逻辑 */ @Component @Aspect public class Advices { //切点 @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))") public void pointcut(){ } //前置通知 @Before("pointcut()") public void before(JoinPoint jp){ System.out.println(jp.getSignature().getName()); System.out.println("----------前置通知----------"); } //最终通知 @After("pointcut()") public void after(JoinPoint jp){ System.out.println("----------最终通知----------"); } //环绕通知 @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println(pjp.getSignature().getName()); System.out.println("----------环绕前置----------"); Object result=pjp.proceed(); System.out.println("----------环绕后置----------"); return result; } //返回结果通知 @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result") public void afterReturning(JoinPoint jp,Object result){ System.out.println(jp.getSignature().getName()); System.out.println("结果是:"+result); System.out.println("----------返回结果----------"); } //异常后通知 @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp") public void afterThrowing(JoinPoint jp,Exception exp){ System.out.println(jp.getSignature().getName()); System.out.println("异常消息:"+exp.getMessage()); System.out.println("----------异常通知----------"); } }
@DeclareParents注解:
面向注解的切面声明有一个明显的劣势:你必须可以为通知类添加注解。若是你没有源码的话,或者不想将AspectJ注解放入到代码中,SpringXml能够实现。
优先的原则:基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XMl的配置。 基于xml的配置:
<?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:p="http://www.springframework.org/schema/p" 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-4.3.xsd"> <!-- 被代理对象 --> <bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean> <!-- 通知 --> <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean> <!-- aop配置 --> <aop:config proxy-target-class="true"> <!--切面 --> <aop:aspect ref="advices"> <!-- 切点 --> <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/> <!--链接通知方法与切点 --> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config> </beans>
当咱们须要建立更细粒度的通知或想监测bean的建立时,Spring所支持的AOP就比较弱了,这时,能够选择使用AspectJ提供的构造器切点,而且能够借助Spring的依赖注入把bean装配进AspectJ切面中。下面就来举个栗子:
//定义表演接口 package concert; public interface Performance { void perform(); void finishPerform(String performer, String title); }
//定义钢琴表演 package concert; public class PianoPerform implements Performance { public PianoPerform(){ System.out.println("有请钢琴表演"); } @Override public void perform() { System.out.println("钢琴表演开始"); } @Override public void finishPerform(String performer, String title) { System.out.println(performer + "演奏钢琴曲:" + title); } }
//定义小提琴表演 package concert; public class ViolinPerform implements Performance { public ViolinPerform(){ System.out.println("有请小提琴表演"); } @Override public void perform() { System.out.println("小提琴表演开始"); } @Override public void finishPerform(String performer, String title){ System.out.println(performer + "演奏了小提琴曲:" + title); } }
//定义工做人员,将做为切面的协做bean package concert; public class Worker { public void take(){ System.out.println("观众已所有交出手机"); } public void sendMsg(String name){ System.out.println(name + "表演即将开始,请各位观众交出手机"); } public void broadcast(String performer, String title){ System.out.println(performer + "演奏完毕,刚才演奏的曲子叫:" + title); } }
//定义切面 package concert; public aspect Audience { private Worker worker; public Audience(){} //经过setter方法注入 public void setWorker(Worker worker){ this.worker = worker; System.out.println("工做人员已入场"); } //定义piano构造器切点和后置通知 pointcut piano():execution(concert.PianoPerform.new()); after():piano(){ worker.sendMsg("钢琴"); } //定义violin构造器切点和后置通知 pointcut violin():execution(concert.ViolinPerform.new()); after():violin(){ worker.sendMsg("小提琴"); } //定义不带参数方法切点和前置通知 pointcut perform():execution(* concert.Performance.perform()); before():perform(){ worker.take(); } //定义带两个参数的切点和后置通知 pointcut finishPerform(String performer, String title):execution(* concert.Performance.finishPerform(String, String)) && args(performer, title); after(String performer, String title):finishPerform(performer, title){ worker.broadcast(performer, title); } }
XML配置文件:spring.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--<bean id="piano" class="concert.PianoPerform" lazy-init="true"/>--> <bean id="worker" class="concert.Worker"/> <!--Spring须要经过静态方法aspectOf得到audience实例,Audience切面编译后的class文件附在文末--> <bean class="concert.Audience" factory-method="aspectOf"> <property name="worker" ref="worker" /><!--经过Spring把协做的bean注入到切面中--> </bean> <!--这里注意一下bean的顺序,由于在构造器切点后置通知时调用了worker的sendMsg(String)方法,因此避免出现空指针异常,我们先把worker声明在前--> <!--若是要将piano或者violin声明在前,能够设置lazy-init="true"--> <!--因此spring是从上到下解析并实例化bean?仍是解析完整个文件再实例化呢?欢迎评论区留言交流--> <bean id="piano" class="concert.PianoPerform"/> <bean id="violin" class="concert.ViolinPerform"/> </beans>
//主程序 package concert; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainClass { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); Performance piano = context.getBean("piano", Performance.class); piano.perform(); piano.finishPerform("亚莎·海菲兹", "致爱丽斯"); Performance violin = context.getBean("violin", Performance.class); violin.perform(); violin.finishPerform("霍洛维茨", "爱之喜悦"); } }
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.8.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.7</version> </dependency> </dependencies> <build> <plugins> <!--编译aspect的插件,必需要将Audience.aj编译为class文件, 否则spring建立audience bean的时候找不到类--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.8</version> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> <configuration> <complianceLevel>1.8</complianceLevel> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
Audience.class文件
package concert; import concert.Worker; import org.aspectj.lang.NoAspectBoundException; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class Audience { private Worker worker; static { try { ajc$postClinit(); } catch (Throwable var1) { ajc$initFailureCause = var1; } } public void setWorker(Worker worker) { this.worker = worker; System.out.println("工做人员已入场"); } public Audience() { } @After( value = "piano()", argNames = "" ) public void ajc$after$concert_Audience$1$dd71540a() { this.worker.sendMsg("钢琴"); } @After( value = "violin()", argNames = "" ) public void ajc$after$concert_Audience$2$57c630b6() { this.worker.sendMsg("小提琴"); } @Before( value = "perform()", argNames = "" ) public void ajc$before$concert_Audience$3$1cad9822() { this.worker.take(); } @After( value = "finishPerform(performer, title)", argNames = "performer,title" ) public void ajc$after$concert_Audience$4$1840cdb9(String performer, String title) { this.worker.broadcast(performer, title); } //Aspect提供的静态方法,返回Audience切面的一个实例,Spring便可经过factory-method属性得到该实例 //<bean class="concert.Audience" factory-method="aspectOf"> public static Audience aspectOf() { if(ajc$perSingletonInstance == null) { throw new NoAspectBoundException("concert_Audience", ajc$initFailureCause); } else { return ajc$perSingletonInstance; } } public static boolean hasAspect() { return ajc$perSingletonInstance != null; } }
写在最后: Spring所支持的AOP已经能够知足不少需求,若是要求更高,可使用AspectJ提供的更丰富的切点类型,固然须要熟悉AspectJ语法。 本文只是简单的举出了部分切点类型和通知类型,更多的类型读者能够自行尝试。欢迎留言指正,感谢您的阅读!