1. Spring对AOP的实现包括以下3种方式: 106
1.1 三种方式 106
● 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
● 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
● 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。
实际开发中,都是Spring+AspectJ来实现AOP。所以我们重点学习第一种和第二种方式。
1.2 什么是AspectJ? 106
(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)
AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动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;
/**
* 基于AspectJ的AOP注解式开发 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;
/**
* 基于AspectJ的AOP注解式开发 108
* 切面
**/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
public class LogAspect { // 切面
}
3.3 第三步:目标类和切面类都纳入spring bean管理
在目标类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;
/**
* 基于AspectJ的AOP注解式开发 108
* 切面
**/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
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;
/**
* 基于AspectJ的AOP注解式开发 108
* 切面
**/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
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"/>
<!--开启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;
/**
* 基于AspectJ的AOP注解式开发 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 目标方法执行之前的通知
● 后置通知:@AfterReturning 目标方法执行之后的通知
● 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
● 异常通知:@AfterThrowing 发生异常之后执行的通知
● 最终通知:@After 放在finally语句块中的通知
接下来,编写程序来测试这几个通知的执行顺序:
package com.powernode.spring6.service;
import org.springframework.stereotype.Service;
/**
* 基于AspectJ的AOP注解式开发 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;
/**
* 基于AspectJ的AOP注解式开发 108
* 切面
**/
@Component("logAspect") //这是纳入spring容器管理
@Aspect // 切面类是需要使用@Aspect注解进行标注的。
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("最终通知");
}
}
//测试 108
public 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) //切面通知的顺序是,数字越小优先级越高 111
public 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;
/**
* 基于AspectJ的AOP注解式开发 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("前置通知");
// 这个JoinPoint joinPoint,在Spring容器调用这个方法的时候自动传过来.
// 我们可以直接用。用这个JoinPoint 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) //切面通知的顺序是,数字越小优先级越高 111
public 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
这个JoinPoint joinPoint,在Spring容器调用这个方法的时候自动传过来. 113
我们可以直接用。用这个JoinPoint joinPoint干啥?
Signature signature = joinPoint.getSignature(); //获取目标方法的签名。
通过方法的签名可以获取到一个方法的具体信息。
获取目标方法的方法名。例如
System.out.println("目标方法的方法名:" + joinPoint.getSignature().getName());
其实我们的每个通知类型中都可以加入参数JoinPoint joinPoint 它不只局限于环绕通知
9. 全注解开发 114
就是编写一个类,在这个类上面使用大量注解来代替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)//开启aspectj的自动代理
public class Spring6Config {
}
测试
//全注解开发 114
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
标签:service,..,通知,org,切点,AspectJ,AOP,注解,public
From: https://blog.51cto.com/u_15784725/6458354