1.Spring对AOP的实现包括以下三种方式:1061.13种方式106
●第一种方法:Spring框架结合AspectJ框架实现AOP,基于注释方法。
●第二种方法:Spring框架结合AspectJ框架实现AOP,基于XML方法。
●第三种方法:基于XML配置的Spring框架本身实现的AOP。
在实际开发中,是Spring+AspectJ实现AOP。因此,我们专注于学习第一种和第二种方法。
1.2什么是AspectJ?106(Eclipse组织支持AOP的框架。AspectJ框架是独立于Spring框架的框架,Spring框架使用AspectJ)
AspectJ项目起源于帕洛阿尔托(PaloAlto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregorkiczales领导,自1997年以来一直致力于AspectJ的开发,1998年首次发布给外部用户,2001年发布1.0release。为了促进AspectJ技术和社区的发展,PARC于2003年3月正式将AspectJ项目移交给Eclipse组织,因为AspectJ的发展和关注度远远超出了PARC的预期,他们无法继续保持其发展。
2.准备工作107使用Spring+AspectJ的AOP需要引入以下依赖:
<!--spring context依赖--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0-M2</version></dependency><!--spring aop依赖--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>6.0.0-M2</version></dependency><!--spring 依赖aspects--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.0-M2</version></dependency>
在Spring配置文件中添加context命名空间和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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"></beans>
3.基于AspectJ的AOP注释开发108
实现步骤
3.1第一步:定义目标类别和方法
package com.powernode.spring6.service;import org.springframework.stereotype.Service;/** * AOP注释开发基于AspectJ 108 * 业务流程 **/@Service("userService")public class UserService { // 目标类 public void login(){ // 目标方法 System.out.println(”该系统正在进行身份认证..."); }// public void logout(){// System.out.println(退出系统...");// }}
3.2第二步:定义切面类别
package com.powernode.spring6.service;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;/** * AOP注释开发基于AspectJ 108 * 切面 **/@Component("logAspect") //这是纳入spring容器管理的@Aspect // 需要用@Aspect注释来标记切面类。public class LogAspect { // 切面}
3.3第三步:Springbean管理包括目标类和切面类
添加到目标类Orderservice上@Component注释。
添加切面类Myaspect@Component注释。
3.4第四步:在spring配置文件中添加组建扫描
<?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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 组件扫描 108--> <context:component-scan base-package="com.powernode.spring6.service"/></beans>
3.5第五步:在切面类中添加通知
package com.powernode.spring6.service;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;/** * AOP注释开发基于AspectJ 108 * 切面 **/@Component("logAspect") //这是纳入spring容器管理的@Aspect // 需要用@Aspect注释来标记切面类。public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // 这里通知Advice以方法的形式出现。public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // Advice以方法的形式出现在这里。(因为代码可以写在方法中) // @Before注释标记的方法是前置通知。 public void 增强(){ //通知 System.out.println(”我是一个通知,我是一个增强代码..."); }}
3.6第六步:在通知中添加切点表达式
package com.powernode.spring6.service;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;/** * AOP注释开发基于AspectJ 108 * 切面 **/@Component("logAspect") //这是纳入spring容器管理的@Aspect // 需要用@Aspect注释来标记切面类。public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // 这里通知Advice以方法的形式出现。public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // Advice以方法的形式出现在这里。(因为代码可以写在方法中) // @Before注释标记的方法是前置通知。 @Before("execution(* com.powernode.spring6.service.UserService.*(..))") //切点 public void 增强(){ //通知 System.out.println(”我是通知,我是一个增强代码..."); }}
注释@Before表示前置通知。
3.7第七步:在spring配置文件中使用自动代理
<?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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 组件扫描 108--> <context:component-scan base-package="com.powernode.spring6.service"/> <!-- 组件扫描 108--> <context:component-scan base-package="com.powernode.spring6.service"/> <!--自动代理打开aspectj 108 --> <!--扫描spring容器时,检查该类是否有@aspect注释,如果有,则为该类生成代理对象。--> <!-- proxy-target-class="true" 表示强制使用CGLIB动态代理 proxy-target-class="false" 这是默认值,表示界面使用JDK动态代理,而CGLIB动态代理。 --> <aop:aspectj-autoproxy proxy-target-class="true"/></beans>
打开自动代理后,任何带有@Aspect注释的bean都会生成代理对象。
proxy-target-class="true表示采用cglib动态代理。
proxy-target-class="false“表示使用jdk动态代理。默认值是false。即使写成false,cglib生成代理也会在没有接口的情况下自动选择。
测试程序:
//基于AspectJ的AOP注释开发 108 @Test public void testBefore(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = applicationContext.getBean("userService", UserService.class); userService.login(); }
4.切点表达式专题109
OrderService类也要增强
package com.powernode.spring6.service;import org.springframework.stereotype.Service;/** * AOP注释开发基于AspectJ 109 * 配合研究切点表达式 **/@Service("orderService")public class OrderService { // 目标类 // 目标方法 public void generate(){ System.out.println(”系统正在生成订单..."); /*if (1 == 1) { throw new RuntimeException(运行时异常); }*/ }}
修改切点的表达式如下
@Before("execution(* com.powernode.spring6.service..*(..))") //切点 public void 增强(){ //通知 System.out.println(”我是一个通知,我是一个增强代码..."); }
测试
//基于AspectJ的AOP注释开发 108 @Test public void testBefore(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = applicationContext.getBean("userService", UserService.class); userService.login(); userService.logout(); ///要OrderService也被加强 109 OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.generate(); }
5.通知类型110
通知类型包括:
●前置通知:@Before目标方法实施前的通知
●后置通知:@Afterrreturning目标方法实施后的通知
●环绕通知:@Around目标方法前添加通知,实施目标方法后添加通知。
●异常通知:@Afterthrowing发生异常后执行的通知
●最终通知:@After放在finally句块中的通知
接下来,编写程序来测试这些通知的执行顺序:
package com.powernode.spring6.service;import org.springframework.stereotype.Service;/** * AOP注释开发基于AspectJ 109 * 配合研究切点表达式 **/@Service("orderService")public class OrderService { // 目标类 // 目标方法 public void generate(){ System.out.println(”系统正在生成订单..."); /*if (1 == 1) { throw new RuntimeException(运行时异常); }*/ }}
package com.powernode.spring6.service;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;/** * AOP注释开发基于AspectJ 108 * 切面 **/@Component("logAspect") //这是纳入spring容器管理的@Aspect // 需要用@Aspect注释来标记切面类。public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // 这里通知Advice以方法的形式出现。public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // Advice以方法的形式出现在这里。(因为代码可以写在方法中) // @Before注释标记的方法是前置通知。 /*@Before("execution(* com.powernode.spring6.service..*(..))") //切点 public void 增强(){ //通知 System.out.println(”我是通知,我是一个增强代码..."); }*/ // 前置通知 110 @Before("execution(* com.powernode.spring6.service..*(..))") //切点 public void beforeAdvice(JoinPoint joinPoint){ System.out.println(“前置通知”); } // 后置通知 110 @AfterReturning("execution(* com.powernode.spring6.service..*(..))") //切点 public void afterReturningAdvice(JoinPoint joinPoint){ System.out.println(“后置通知”); } // 环绕通知(环绕是最大的通知,在前通知之前,在后通知之后。) 110 @Around("execution(* com.powernode.spring6.service..*(..))") public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { // 前面的代码 System.out.println(前环绕); // 执行目标 joinPoint.proceed(); // 执行目标 // 后面的代码 System.out.println(“后环绕”; } // 异常通知 @AfterThrowing("execution(* com.powernode.spring6.service..*(..))") public void afterThrowingAdvice(){ System.out.println(异常通知); } // 最终通知 (finally句块中的通知) @After("execution(* com.powernode.spring6.service..*(..))") public void afterAdvice(){ System.out.println(“最终通知”); }}
//测试 108public class SpringAOPTest { //基于AspectJ的AOP注释开发 108 @Test public void testBefore(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); /*UserService userService = applicationContext.getBean("userService", UserService.class); userService.login(); userService.logout();*/ ///要OrderService也被加强 109 OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.generate(); }}
6.截面顺序111
众所周知,业务流程中不一定只有一个截面,有的可以控制事务,有的可以记录日志,有的可以控制安全。如果有多个截面,如何控制顺序:用@order注释来识别切面类,为@order注释的value指定一个整数数字,数字越小,优先级越高。
再次定义一个切面类,如下:
package com.powernode.spring6.service;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;//基于AspectJ的AOP注释开发//安全截面 111@Aspect@Component@Order(0) ///截面通知的顺序是,数字越小,优先级越高 111public class SecurityAspect { //通知+切点 @Before("execution(* com.powernode.spring6.service..*(..))") //切点 public void beforeAdvice(){ System.out.println(前置通知:安全..."); }}
7.通用切点112
上述代码的缺点是:
第一:切点表达式重复写了很多次,没有重复使用。
第二:如果要修改切点表达式,需要修改很多地方,很难维护。
可以这样做:单独定义切点表达式,并将其引入所需位置。如下:
///定义通用切点表达式 112 @Pointcut("execution(* com.powernode.spring6.service..*(..))") public void 通用切点()()){ // 这种方法只是一个标记,方法名是随意的,方法体不需要写任何代码。 }
案例如下
package com.powernode.spring6.service;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;/** * AOP注释开发基于AspectJ 108 * 切面 **/@Component("logAspect") //这是纳入spring容器管理的@Aspect // 需要用@Aspect注释来标记切面类。@Order(2)public class LogAspect { // 切面 // 切面 = 通知 + 切点 // 通知是增强,是具体编写的增强代码 // 这里通知Advice以方法的形式出现。(因为代码可以在方法中写) // @Before注释标记的方法是前置通知。 /*@Before("execution(* com.powernode.spring6.service..*(..))") //切点 public void 增强(){ //通知 System.out.println(”我是通知,我是一个增强代码..."); }*/ ///定义通用切点表达式 112 @Pointcut("execution(* com.powernode.spring6.service..*(..))") public void 通用切点()()){ // 这种方法只是一个标记,方法名随意,方法体不需要写任何代码。 } // 前置通知 110 @Before(通用切点() //@Before("execution(* com.powernode.spring6.service..*(..))") //切点 public void beforeAdvice(JoinPoint joinPoint){ System.out.println(“前置通知”); // JoinPointt这个Join joinPoint,当Spring容器调用此方法时,它会自动传输. // 我们可以直接使用。使用这个JoinPointntt 为什么joinPoint?使用这个JoinPointntt 为什么joinPoint? // Signature signature = joinPoint.getSignature(); 获取目标方法的签名。 // 一种方法的具体信息可以通过方法签名获得。 // 获取目标方法的方法名称。 //System.out.println(目标方法的方法名称:" + joinPoint.getSignature().getName()); } // 后置通知 110 @AfterReturning(通用切点() //@AfterReturning("execution(* com.powernode.spring6.service..*(..))") //切点 public void afterReturningAdvice(JoinPoint joinPoint){ System.out.println(“后置通知”); } // 环绕通知(环绕是最大的通知,在前通知之前,在后通知之后。) 110 @Around(通用切点() //@Around("execution(* com.powernode.spring6.service..*(..))") public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { // 前面的代码 System.out.println(前环绕); // 执行目标 joinPoint.proceed(); // 执行目标 // 后面的代码 System.out.println(“后环绕”; } // 异常通知 110 @AfterThrowing(通用切点() //@AfterThrowing("execution(* com.powernode.spring6.service..*(..))") public void afterThrowingAdvice(){ System.out.println(异常通知); } // 最终通知 (finally句块中的通知) 110 @After(通用切点() //@After("execution(* com.powernode.spring6.service..*(..))") public void afterAdvice(){ System.out.println(“最终通知”); }}
package com.powernode.spring6.service;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;//基于AspectJ的AOP注释开发//安全截面 111@Aspect@Component@Order(0) ///截面通知的顺序是,数字越小,优先级越高 111public class SecurityAspect { //通知+切点 //@Before("execution(* com.powernode.spring6.service..*(..))") //切点 ///使用LogAspect切面类中定义的 通用切点()()) 表达式方法 112 ///需要引入注册 com.powernode.spring6.service.LogAspect @Before("com.powernode.spring6.service.LogAspect.通用切点()())") public void beforeAdvice(){ System.out.println(前置通知:安全..."); }}
//基于AspectJ的AOP注释开发 108 @Test public void testBefore(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); /*UserService userService = applicationContext.getBean("userService", UserService.class); userService.login(); userService.logout();*/ ///要OrderService也被加强 109 OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.generate(); }
8.小知识点113
JoinPointjoinPoint在Spring容器调用此方法时自动传输。113
我们可以直接使用它。用这个JoinPointjoinpoint做什么?
Signaturesignature=joinPoint.getSignature();//获取目标方法的签名。
一种方法的具体信息可以通过方法签名获得。
获取目标方法的方法名称。例如
System.out.println(目标方法的方法名称:"+joinPoint.getSignature().getName());
事实上,参数JoinPointjoinPoint不仅限于环绕通知
9.全注释开发1144写一个类,用大量的注释代替spring的配置文件,spring配置文件消失了,如下所示
package com.powernode.spring6.service;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;//全注解开发 114@Configuration //代表这一类,spring配置文件@ComponentScan({"com.powernode.spring6.service"})// 组件扫描@EnableAspectJAutoProxy(proxyTargetClass = true)///自动代理publictj打开aspectj class Spring6Config {}
测试
//全注解开发 114 @Test public void testNoXml(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class); OrderService orderService = applicationContext.getBean("orderService", OrderService.class); orderService.generate(); }