4-1-2 Spring基础-Spring AOP面向切面编程

什么是Spring AOP 

Aspect Oriented Programming 面向切面编程

AOP的做法是将通用,与业务无关的功能抽象封装为切面类

切面可配置在目标方法执行前,后运行,真正做到即插即用

在不修改源码的情况下对程序行为进行修改

一个AOP的Demo

pom文件

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>com.mingm.spring</groupId>
 8     <artifactId>aop</artifactId>
 9     <version>1.0-SNAPSHOT</version>
10     <repositories>
11         <repository>
12             <id>aliyun</id>
13             <name>aliyun</name>
14             <url>https://maven.aliyun.com/repository/public</url>
15         </repository>
16     </repositories>
17     <dependencies>
18         <dependency>
19             <groupId>org.springframework</groupId>
20             <artifactId>spring-context</artifactId>
21             <version>5.2.6.RELEASE</version>
22         </dependency>
23         <!--aspectjweaver是Spring AOP的底层依赖-->
24         <dependency>
25             <groupId>org.aspectj</groupId>
26             <artifactId>aspectjweaver</artifactId>
27             <version>1.9.5</version>
28         </dependency>
29     </dependencies>
30 </project>

applicationContext.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xmlns:context="http://www.springframework.org/schema/context"
 4        xmlns:aop="http://www.springframework.org/schema/aop"
 5        xmlns="http://www.springframework.org/schema/beans"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans
 7         http://www.springframework.org/schema/beans/spring-beans.xsd
 8         http://www.springframework.org/schema/context
 9         http://www.springframework.org/schema/context/spring-context.xsd
10         http://www.springframework.org/schema/aop
11         http://www.springframework.org/schema/aop/spring-aop.xsd">
12     <bean id="userDao" class="com.mingm.spring.aop.dao.UserDao"/>
13     <bean id="employeeDao" class="com.mingm.spring.aop.dao.EmployeeDao"/>
14     <bean id="userService" class="com.mingm.spring.aop.service.UserService">
15         <property name="userDao" ref="userDao"/>
16     </bean>
17     <bean id="employeeService" class="com.mingm.spring.aop.service.EmployeeService">
18         <property name="employeeDao" ref="employeeDao"/>
19     </bean>
20     <!-- AOP配置 -->
21     <bean id="methodAspect" class="com.mingm.spring.aop.aspect.MethodAspect"></bean>
22     <aop:config>
23         <!-- PointCut 切点,使用execution表达式描述切面的作用范围 -->
24         <!-- execution(public * com.mingm..*.*(..)) 说明切面作用在com.mingm包下的所有类的所有方法上 -->
25         <!--<aop:pointcut id="pointcut" expression="execution(public * com.mingm..*.*(..))"></aop:pointcut>-->
26         <!--只对所有Service类生效-->
27         <aop:pointcut id="pointcut" expression="execution(* com.mingm..*Service.*(..))"></aop:pointcut>
28         <!--只对所有返回值为String类型方法生效-->
29         <!--<aop:pointcut id="pointcut" expression="execution(String com.mingm..*Service.*(..))"></aop:pointcut>-->
30         <!--对方法名进行约束 -->
31         <!--<aop:pointcut id="pointcut" expression="execution(* com.mingm..*Service.create*(..))"></aop:pointcut>-->
32         <!-- 对参数进行约束 -->
33         <!--<aop:pointcut id="pointcut" expression="execution(* com.mingm..*Service.*(String,*))"></aop:pointcut>-->
34         <!-- 定义切面类 -->
35         <aop:aspect ref="methodAspect">
36             <!-- before通知(Advice),代表在目标方法运行前先执行methodAspect.printExecutionTime() -->
37             <aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
38             <aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
39             <aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
40             <aop:after method="doAfter" pointcut-ref="pointcut"></aop:after>
41         </aop:aspect>
42     </aop:config>
43 </beans>
SpringApplication.java
 1 package com.mingm.spring.aop;
 2 
 3 import com.mingm.spring.aop.service.UserService;
 4 import org.springframework.context.ApplicationContext;
 5 import org.springframework.context.support.ClassPathXmlApplicationContext;
 6 
 7 public class SpringApplication {
 8     public static void main(String[] args) {
 9         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
10         UserService userService = context.getBean("userService", UserService.class);
11         userService.createUser();
12         userService.generateRandomPassword("MD5", 16);
13     }
14 }
MethodAspect.java
 1 package com.mingm.spring.aop.aspect;
 2 
 3 import org.aspectj.lang.JoinPoint;
 4 
 5 import java.text.SimpleDateFormat;
 6 import java.util.Date;
 7 
 8 //切面类
 9 public class MethodAspect {
10     //切面方法,用于扩展额外功能
11     //JoinPoint 连接点,通过连接点可以获取目标类/方法的信息
12     public void printExecutionTime(JoinPoint joinPoint){
13         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
14         String now = sdf.format(new Date());
15         String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
16         String methodName = joinPoint.getSignature().getName();//获取目标方法名称
17         System.out.println("---->" + now + ":" + className + "." + methodName);
18         Object[] args = joinPoint.getArgs();
19         System.out.println("---->参数个数:" + args.length);
20         for(Object arg:args){
21             System.out.println("---->参数:" + arg);
22         }
23     }
24 
25     public void doAfterReturning(JoinPoint joinPoint,Object ret){
26         System.out.println("<----返回后通知:" + ret);
27     }
28     public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
29         System.out.println("<----异常通知:" + th.getMessage());
30     }
31     public void doAfter(JoinPoint joinPoint){
32         System.out.println("<----触发后置通知");
33     }
34 }

AOP配置过程

1.依赖AspectJ

2.实现切面类/方法

3.胚子Aspect Bean

4.定义PointCut

5.配置Advice通知

Spring AOP 与AspectJ的关系

Eclipse AspectJ,一种基于Java平台的面向切面编程的语言

Spring AOP使用AspectJWeavear实现类与方法匹配

Spring AOP利用代理模式实现对象运行时功能扩展

Spring AOP相关概念

注解 说明
Aspect 切面,具体的可插拔组件功能类,通常一个切面只实现一个通用功能
Target Class/Method 目标类,目标方法,指真正要执行与业务相关的方法
PointCut 切入点,使用execution表达式说明切面要作用在系统的哪些类上
JoinPoint 连接点,切面运行过程中包含了目标类/方法元数据的对象
Adivice 通知,说明具体的切面的执行时机,Spring包含五种不同类型通知

JoinPoint核心方法

 

注解 说明
Object getTarget() 获取IoC容器目标对象
Signature getSignature() 获取目标方法
Object[] getArgs() 获取目标方法参数

PointCut切点表达式

 

 * - 通配符

.. - 包通配符

(..) - 参数通配符

示例:

只对所有Service类生效: execution(* com.mingm..*Service.*(..))

只对所有返回值为String类型方法生效: execution(String com.mingm..*Service.*(..))

对方法名进行约束: execution(* com.mingm..*Service.create*(..))

对参数进行约束:  execution(* com.mingm..*Service.*(String,*))

AOP通知

五种通知类型

注解 说明
Before 前置通知,目标方法运行前执行
After Returning Advice 返回后通知,目标方法返回数据后执行
After Throwing Advice 异常通知,目标方法抛出异常后执行
After Advice 后置通知,目标方法运行后执行
Around Advice 最强大通知,自定义通知执行时机,可决定目标方法是否执行

特殊的"通知"-引介增强

引介增强是对类的增强,而非方法

本质是拦截器

引介增强允许在运行时为目标类增加新属性或方法

引介增强允许在运行时改变类的行为,让类随运行环境动态变更

后置通知和返回后通知执行顺序

由applicationContext.xml配置顺序决定

Demo:利用AOP方法进行性能筛选

pom.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>com.mingm.spring</groupId>
 8     <artifactId>aop</artifactId>
 9     <version>1.0-SNAPSHOT</version>
10     <repositories>
11         <repository>
12             <id>aliyun</id>
13             <name>aliyun</name>
14             <url>https://maven.aliyun.com/repository/public</url>
15         </repository>
16     </repositories>
17     <dependencies>
18         <dependency>
19             <groupId>org.springframework</groupId>
20             <artifactId>spring-context</artifactId>
21             <version>5.2.6.RELEASE</version>
22         </dependency>
23         <dependency>
24             <groupId>org.aspectj</groupId>
25             <artifactId>aspectjweaver</artifactId>
26             <version>1.9.5</version>
27         </dependency>
28     </dependencies>
29 </project>
MethodChecker.java
package com.mingm.spring.aop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MethodChecker {
    ////ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        try {
            long startTime = new Date().getTime();
            Object ret = pjp.proceed();//执行目标方法
            long endTime = new Date().getTime();
            long duration = endTime - startTime; //执行时长
            if(duration >= 1000){
                String className = pjp.getTarget().getClass().getName();
                String methodName = pjp.getSignature().getName();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
                String now = sdf.format(new Date());
                System.out.println("=======" + now + ":" + className + "." + methodName + "(" + duration + "ms)======");
            }
            return ret;
        } catch (Throwable throwable) {
            System.out.println("Exception message:" + throwable.getMessage());
            throw throwable;
        }
    }
}

applicationContext.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xmlns:context="http://www.springframework.org/schema/context"
 4        xmlns:aop="http://www.springframework.org/schema/aop"
 5        xmlns="http://www.springframework.org/schema/beans"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans
 7         http://www.springframework.org/schema/beans/spring-beans.xsd
 8         http://www.springframework.org/schema/context
 9         http://www.springframework.org/schema/context/spring-context.xsd
10         http://www.springframework.org/schema/aop
11         http://www.springframework.org/schema/aop/spring-aop.xsd">
12     <bean id="userDao" class="com.mingm.spring.aop.dao.UserDao"/>
13     <bean id="employeeDao" class="com.mingm.spring.aop.dao.EmployeeDao"/>
14     <bean id="userService" class="com.mingm.spring.aop.service.UserService">
15         <property name="userDao" ref="userDao"/>
16     </bean>
17     <bean id="employeeService" class="com.mingm.spring.aop.service.EmployeeService">
18         <property name="employeeDao" ref="employeeDao"/>
19     </bean>
20 
21     <bean id="methodChecker" class="com.mingm.spring.aop.aspect.MethodChecker"></bean>
22     <aop:config>
23         <aop:pointcut id="pointcut" expression="execution(* com.mingm..*.*(..))"></aop:pointcut>
24         <aop:aspect ref="methodChecker">
25             <!--环绕通知-->
26             <aop:around method="check" pointcut-ref="pointcut"/>
27         </aop:aspect>
28     </aop:config>
29 </beans>
SpringApplication.java
 1 package com.mingm.spring.aop;
 2 
 3 import com.mingm.spring.aop.service.UserService;
 4 import org.springframework.context.ApplicationContext;
 5 import org.springframework.context.support.ClassPathXmlApplicationContext;
 6 
 7 public class SpringApplication {
 8     public static void main(String[] args) {
 9         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
10         UserService userService = context.getBean("userService", UserService.class);
11         userService.createUser();
12     }
13 }

利用注解开发Spring AOP

将利用AOP方法进行性能筛选demo改造为注解开发

applicationContext.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xmlns:context="http://www.springframework.org/schema/context"
 4        xmlns:aop="http://www.springframework.org/schema/aop"
 5        xmlns="http://www.springframework.org/schema/beans"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans
 7         http://www.springframework.org/schema/beans/spring-beans.xsd
 8         http://www.springframework.org/schema/context
 9         http://www.springframework.org/schema/context/spring-context.xsd
10         http://www.springframework.org/schema/aop
11         http://www.springframework.org/schema/aop/spring-aop.xsd">
12     <!--初始化IoC容器-->
13     <context:component-scan base-package="com.imooc"/>
14     <!--启用Spring AOP注解模式-->
15     <aop:aspectj-autoproxy/>
16 </beans>
MethodChecker.java
 1 package com.imooc.spring.aop.aspect;
 2 
 3 import org.aspectj.lang.ProceedingJoinPoint;
 4 import org.aspectj.lang.annotation.*;
 5 import org.springframework.stereotype.Component;
 6 
 7 import java.text.SimpleDateFormat;
 8 import java.util.Date;
 9 @Component //标记当前类为组件
10 @Aspect //说明当前类是切面类
11 public class MethodChecker {
12     //环绕通知,参数为PointCut切点表达式
13     @Around("execution(* com.imooc..*Service.*(..))")
14     //ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
15     public Object check(ProceedingJoinPoint pjp) throws Throwable {
16         try {
17             long startTime = new Date().getTime();
18             Object ret = pjp.proceed();//执行目标方法
19             long endTime = new Date().getTime();
20             long duration = endTime - startTime; //执行时长
21             if(duration >= 1000){
22                 String className = pjp.getTarget().getClass().getName();
23                 String methodName = pjp.getSignature().getName();
24                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
25                 String now = sdf.format(new Date());
26                 System.out.println("=======" + now + ":" + className + "." + methodName + "(" + duration + "ms)======");
27             }
28             return ret;
29         } catch (Throwable throwable) {
30             System.out.println("Exception message:" + throwable.getMessage());
31             throw throwable;
32         }
33     }
34 }

Spring AOP实现原理

Spring基于代理模式实现功能动态扩展,包含两种形式:

目标类拥有接口,通过JDK动态代理实现功能扩展

目标类没有接口,通过CGlib组件实现功能扩展

代理模式

代理模式通过代理对象对原对象实现功能扩展

 

 静态代理

代理类和委托类都要实现相同的接口

接口

1 public interface UserService {
2     public void createUser();
3 }

委托类

1 public class UserServiceImpl implements UserService{
2     public void createUser() {
3         System.out.println("执行创建用户业务逻辑");
4     }
5 }

代理类

 1 import java.text.SimpleDateFormat;
 2 import java.util.Date;
 3 //静态代理是指必须手动创建代理类的代理模式使用方式
 4 public class UserServiceProxy implements UserService{
 5     //持有委托类的对象
 6     private UserService userService ;
 7     public UserServiceProxy(UserService userService){
 8         this.userService = userService;
 9     }
10 
11     public void createUser() {
12         System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=========");
13         userService.createUser();
14     }
15 }

JDK动态代理

 1 /**
 2  * InvocationHandler是JDK提供的反射类,用于在JDK动态代理中对目标方法进行增强
 3  * InvocationHandler实现类与切面类的环绕通知类似
 4  */
 5 public class ProxyInvocationHandler implements InvocationHandler {
 6     private Object target;//目标对象
 7     private ProxyInvocationHandler(Object target){
 8         this.target = target;
 9     }
10     /**
11      * 在invoke()方法对目标方法进行增强
12      * @param proxy 代理类对象
13      * @param method 目标方法对象
14      * @param args 目标方法实参
15      * @return 目标方法运行后返回值
16      * @throws Throwable 目标方法抛出的异常
17      */
18     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
19         System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=========");
20         Object ret = method.invoke(target, args);//调用目标方法,ProceedingJoinPoint.proceed()
21         return ret;
22     }
23 
24     public static void main(String[] args) {
25         UserService userService = new UserServiceImpl();
26         ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
27         //动态创建代理类
28         UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
29                 userService.getClass().getInterfaces(),
30                 invocationHandler);
31         userServiceProxy.createUser();
32 
33         //动态代理,必须实现接口才可以运行
34         EmployeeService employeeService = new EmployeeServiceImpl();
35         EmployeeService employeeServiceProxy = (EmployeeService)Proxy.newProxyInstance(employeeService.getClass().getClassLoader(),
36                 employeeService.getClass().getInterfaces(),
37                 new ProxyInvocationHandler(employeeService));
38         employeeServiceProxy.createEmployee();
39     }
40 }

CGlib实现代理类

CGlib是运行时字节码增强技术

Spring AOP扩展无接口类使用CGlib

AOP会运行时生成目标继承类字节码的方式进行扩展

CGlib执行过程

1.生成代理类的二进制字节码文件

2.加载二进制字节码,生成Class对象(Class.forName())

3.通过反射机制获取实例构造,并创建代理类对象

posted @ 2020-11-13 16:29  mingmingn  阅读(151)  评论(0)    收藏  举报