Spring AOP面向切面编程
Spring AOP
- Spring AOP - Aspect Oriented Programming 面向切面编程
- AOP的做法是将通用、于业务无关的功能抽象封装为切面类
- 切面可配置在目标方法的执行前、后运行,真正做到即插即用
Spring AOP
用到的依赖dependency:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.2</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> </dependencies>
applicationContext.xml中schema依赖
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here --> </beans>
Spring AOP概念
Spring AOP与AspectJ的关系
- Eclipse AspectJ,一种基于Java平台的面向切面编程的语言
- Spring AOP使用AspectJWeaver实现类与方法的匹配
- Spring AOP利用代理模式实现对象运行时功能扩展
几个关键概念
注解 | 说明 |
---|---|
Aspect | 切面,具体的可插拔组件功能类,通常一个切面只实现一个通用功能 |
Target Class/Method | 目标类、目标方法,指真正要执行与业务相关的方法 |
PointCut | 切入点,使用execution表达式说明切面要作用在系统的哪些类上 |
JoinPoint | 连接点,切面运行过程中时包括了目标类/方法元数据的对象 |
Advice | 通知,说明具体的切面的执行时机,Spring包含了五种不同类型通知 |
AOP配置过程
- 依赖AspectJ
- 实现切面类/方法
- 配置Aspect Bean
- 定义PointCut
- 配置Advice
JoinPoint核心方法
注解 | 说明 |
---|---|
Object getTarget() | 获取IoC容器内目标对象 |
Signature getSignature() | 获取目标方法 |
Object[] getArgs() | 获取目标方法参数 |
PointCut切点表达式
通知
五种通知类型
注解 | 说明 |
---|---|
Before Advice | 前置通知,目标方法运行前执行 |
After Returning Advice | 返回后通知,目标方法返回数据后执行 |
After Throwing Advice | 异常通知,目标方法抛出异常后执行 |
After Advice | 后置通知,目标方法运行 |
Around Advice | 最强大通知,自定义通知执行时机,可决定目标方法是否运行 |
特殊的“通知”-引介增强
- 引介增强(Introduction Interceptor)是对类的增强,而非方法
- 引介增强允许在运行时为目标类增加新属性或方法
- 引介增强允许在运行时改变类的行为,让类随运行环境动态变更
Spring AOP实现原理
- Spring基于代理模式实现功能动态扩展,包含俩种形式
- 目标类拥有接口,通过JDK动态代理实现功能扩展
- 目标类没有接口,通过CGLib组件实现功能扩展
代理模式:
- 代理模式通过代理对象对原对象的实现功能扩展
静态代理示例:
UserService.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 11:47 */ public interface UserService { public void createUser(); }
UserService.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 11:47 */ public class UserServiceImpl implements UserService{ @Override public void createUser() { System.out.println("执行创建用户业务逻辑"); } }
UserServiceProxy.java
package com.imooc.spring.aop.service; import java.text.SimpleDateFormat; import java.util.Date; /** * @author Rex * @create 2021-01-05 11:49 */ //静态代理是指必须手动创建代理类的代理模式使用方式 public class UserServiceProxy implements UserService{ //持有委托类的对象 private UserService userService; public UserServiceProxy(UserService userService){ this.userService = userService; } @Override public void createUser() { System.out.println("======" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()) +"=========="); this.userService.createUser(); } }
UserServiceProxy1.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 11:54 */ public class UserServiceProxy1 implements UserService { private UserService userService; public UserServiceProxy1(UserService userService){ this.userService = userService; } @Override public void createUser() { userService.createUser(); System.out.println("=======后置扩展功能======"); } }
运行结果:
======2021-01-05 12:28:08 008========== 执行创建用户业务逻辑 =======后置扩展功能======
动态代理示例
UserSerivce.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 11:47 */ public interface UserService { public void createUser(); }
UserServiceImpl.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 11:47 */ public class UserServiceImpl implements UserService{ @Override public void createUser() { System.out.println("执行创建用户业务逻辑"); } }
EmployeeService.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 12:20 */ public interface EmployeeService { public void createEmployee(); }
EmployeeServiceImpl.java
package com.imooc.spring.aop.service; /** * @author Rex * @create 2021-01-05 12:20 */ public class EmployeeServiceImpl implements EmployeeService{ @Override public void createEmployee() { System.out.println("执行创建员工实现类"); } }
ProxyInvocationHandler.java
package com.imooc.spring.aop.service; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.text.SimpleDateFormat; import java.util.Date; /** * InvocationHandler是JDK提供的反射类,用于在JDK动态代理中对目标方法进行增强 * InvocationHandler实现类与切面类的的环绕通知类似 * @author Rex * @create 2021-01-05 12:06 */ public class ProxyInvocationHandler implements InvocationHandler { private Object target; //目标对象 private ProxyInvocationHandler(Object target){ this.target = target; } /** * 在invoke()方法对目标方法进行增强 * @param proxy 代理类对象 * @param method 目标方法对象 * @param args 目标方法实参 * @return 目标方法运行后返回值 * @throws Throwable 目标方法抛出的异常 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("======" +new SimpleDateFormat("yyyy-MM-dd HH:mm:ss sss").format(new Date()) + "======"); Object ret = method.invoke(target, args);//调用目标方法, ProceedingJoinPoint.proceed() return ret; } public static void main(String[] args) { UserService userService = new UserServiceImpl(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(userService); //动态创建代理类 UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), proxyInvocationHandler); userServiceProxy.createUser(); //动态代理,必须实现接口才可以运行 EmployeeService employeeService = new EmployeeServiceImpl(); EmployeeService employeeServiceProxy = (EmployeeService) Proxy.newProxyInstance(employeeService.getClass().getClassLoader(), employeeService.getClass().getInterfaces(), new ProxyInvocationHandler(employeeService)); employeeServiceProxy.createEmployee(); } }
运行结果:
======2021-01-05 12:23:20 020====== 执行创建用户业务逻辑 ======2021-01-05 12:23:20 020====== 执行创建员工实现类
CGLib实现代理类
- CGLib是运行时字节码增强技术
- Spring AOP扩展无接口类使用CGLib
- AOP会运行时生成目标继承类字节码的方式进行行为扩展
AOP代码实例:
XML形式
applicationContext.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"></bean> <bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"></bean> <bean id="userService" class="com.imooc.spring.aop.service.UserService"> <property name="userDao" ref="userDao"></property> </bean> <bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService"> <property name="employeeDao" ref="employeeDao"></property> </bean> <bean id="methodChecker" class="com.imooc.spring.aop.aspect.MethodChecker"></bean> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.imooc..*.*(..))"/> <aop:aspect ref="methodChecker"> <aop:around method="check" pointcut-ref="pointcut"></aop:around> </aop:aspect> </aop:config> </beans>
UserDao.java
package com.imooc.spring.aop.dao; /** * 用户表Dao */ public class UserDao { public void insert(){ System.out.println("新增用户数据"); } }
EmployeeDao.java
package com.imooc.spring.aop.dao; /** * 员工表Dao * @author Rex * @create 2021-01-04 23:20 */ public class EmployeeDao { public void insert(){ System.out.println("新增员工数据"); } }
Userervice.java
package com.imooc.spring.aop.service; import com.imooc.spring.aop.dao.UserDao; /** * 用户服务 * @author Rex * @create 2021-01-04 22:46 */ public class UserService { private UserDao userDao; public void createUser(){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行员工入职业务逻辑"); userDao.insert(); } public String generateRandomPassword(String type, Integer length){ System.out.println("按" + type + "方式生成" + length + "位随机密码"); return "Zxcquil"; } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
EmployeeService.java
package com.imooc.spring.aop.service; import com.imooc.spring.aop.dao.EmployeeDao; /** * 员工服务 * @author Rex * @create 2021-01-04 23:20 */ public class EmployeeService { private EmployeeDao employeeDao; public void entry(){ System.out.println("执行员工入职业务逻辑"); employeeDao.insert(); } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
MethodCheck.java
package com.imooc.spring.aop.aspect; import org.aspectj.lang.ProceedingJoinPoint; import java.text.SimpleDateFormat; import java.util.Date; /** * @author Rex * @create 2021-01-04 23:07 */ 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; } } }
SpringContext.java
package com.imooc.spring.aop; import com.imooc.spring.aop.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author Rex * @create 2021-01-04 23:20 */ public class SpringContext { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); UserService userService = context.getBean("userService", UserService.class); userService.createUser(); } }
执行结果:
执行员工入职业务逻辑 新增用户数据 ===========2021-01-05 14:15:17 017:com.imooc.spring.aop.service.UserService.createUser(3046ms)======
注解形式
applicationContext.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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.imooc"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
BookShop.java
package com.imooc.spring.aop.entity; import org.springframework.stereotype.Component; /** * @author Rex * @create 2021-01-05 10:53 */ @Component public class BookShop { public void sellingBooks(){ System.out.println("卖出一本java基础书籍"); } }
MethodPro.java
package com.imooc.spring.aop.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * @author Rex * @create 2021-01-05 10:54 */ @Component @Aspect public class MethodPro { // @Around("execution(public * com.imooc..*.*())") public void welcome(ProceedingJoinPoint pjd) throws Throwable { System.out.println("欢迎光临慕课小店"); pjd.proceed(); System.out.println("欢迎再次光临"); } @Before("execution(public * com.imooc..*.*(..))") public void preSales(){ System.out.println("=====售前服务====="); } @After("execution(public * com.imooc..*.*(..))") public void afterSale(){ System.out.println("=====售后服务======"); } }
SpringApplication.java
package com.imooc.spring.aop; import com.imooc.spring.aop.entity.BookShop; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author Rex * @create 2021-01-05 11:03 */ @ComponentScan public class SpringApplication { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); BookShop bookShop = context.getBean("bookShop", BookShop.class); bookShop.sellingBooks(); } }
执行结果:
=====售前服务===== 卖出一本java基础书籍 =====售后服务======