初识AOP
(1)什么是AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
(2)为什么用AOP
就是为了方便,一个国外很有名的大师说,编程的人都是“懒人”,因为他把自己做的事情都让程序做了,用了AOP能让你少写很多代码。 为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等。
(3)AOP的引入:以代理模式为例
静态代理
a. 定义业务接口
1 2 3 4 | public interface IAccountService { // 转账业务接口 void transfer(); } |
b、定义目标类与目标方法
1 2 3 4 5 6 7 | public class AccountServiceImpl implements IAccountService { // 转账业务实现即目标方法 @Override public void transfer() { System.out.println( "进行转账操作" ); } } |
c. 定义代理类AccountServiceImplProxy,实现IAccountService接口。在有参构造方法中传入目标对象,将目标对象引入代理类,以便代理类调用目标方法,进行增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class AccountServiceImplProxy implements IAccountService { // 声明目标接口对象 private IAccountService target; public AccountServiceImplProxy() { } // 业务接口对象作为构造器,用于接收目标对象 public AccountServiceImplProxy(IAccountService target) { this .target = target; } @Override public void transfer() { // 此处对目标方法进行增强 System.out.println( "对转账人身份校验。。" ); target.transfer(); System.out.println( "进行日志记录。。" ); } } |
d. 编写测试类TransferServiceTest
1 2 3 4 5 6 7 8 9 10 | public class TransferServiceTest { public static void main(String[] args) { // 创建目标对象 IAccountService target = new AccountServiceImpl(); // 创建代理对象,传入目标对象进行初始化 IAccountService proxy = new AccountServiceImplProxy(target); // 执行代理对象的方法 proxy.transfer(); } } |
动态代理
动态代理,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理工具在程序运行时由JVM根据反射等机制动态生成。代理对象与目标对象的代理关系在程序运行时才确立。
(1)JDK动态代理
JDK动态代理是通过JDK提供的 java.lang.reflect.Proxy类实现动态大力,使用其静态方法newProxyInstance(),对目标对象、业务接口及业务增强逻辑,自动生成一个动态代理对象。
1. 定义业务接口与实现类
1 2 3 4 | public interface IAccountService { // 转账业务接口 void transfer(); } |
1 2 3 4 5 | public class AccountServiceImpl implements IAccountService{ public void transfer() { System.out.println( "进行转账操作" ); } } |
2. 定义JdkProxy类实现InvocationHandler接口,实现invoke()方法,并对业务逻辑进行增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxy implements InvocationHandler { private Object target; public JdkProxy() { } public JdkProxy(Object target) { this .target = target; } public Object createProxy(Object target) { this .target = target; // 1.类加载器 ClassLoader classLoader = JdkProxy. class .getClassLoader(); // 2.被代理对象实现的所有接口 Class[] classes = target.getClass().getInterfaces(); // 3.使用代理类,进行增强,返回的是代理对象 return Proxy.newProxyInstance(classLoader,classes, this ); } /* * 所有动态代理类的方法调用,都会交由invoke()方法去处理, proxy 被代理的对象 * method 将要被执行的方法信息(反射), args 执行方法时需要的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "对转账人身份校验。。" ); // 在目标类上调用方法,并传入参数 Object result = method.invoke(target, args); // 后增强 System.out.println( "进行日志记录。。" ); return result; } } |
3. 测试验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class JDKProxyTest { public static void main(String[] args) { IAccountService accountService = new AccountServiceImpl(); JdkProxy jdkProxy = new JdkProxy(); // 从代理对象中获取增强后的目标对象 IAccountService accountService2 = (IAccountService) jdkProxy.createProxy(accountService); accountService2.transfer(); } } |
(2)CGLIB动态代理
CGLIB是一个开源的第三方代码生成类库,对于无接口的类,要为其创建动态代理,就要使用CGLIB进行实现。CGLIB代理的生成原理是生成目标类的子类,子类是增强过的,就是目标类的代理类。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final修饰的类。
1.引入CGLIB依赖
1 2 3 4 5 | <dependency> <groupId>cglib</groupId> <artifactId>cglib-full</artifactId> <version> 2.0 . 2 </version> </dependency> |
2.创建目标类AccountService
1 2 3 4 5 6 7 8 9 10 11 12 | public class AccountService { // 转账业务 即目标方法 public void transfer() { System.out.println( "进行转账操作" ); } // 查询余额 即目标方法 public void getBalance() { System.out.println( "查询余额操作" ); } } |
3. 创建代理类AccountServiceCglibProxy实现MethodInterceptor接口,完善intercept()方法进行增强,创建生成代理对象createProxy()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | public class AccountServiceCglibProxy implements MethodInterceptor { // 声明目标类的成员变量,并创建以目标类为参数的构造器,用于接收目标对象 private AccountService target; public AccountServiceCglibProxy(AccountService accountService) { this .target = accountService; } /** * @param o 代理对象 * @param method 代理对象的方法,即增强后的方法 * @param objects 方法参数 * @param methodProxy 代理方法的对象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 此处对目标方法进行增强 Object result = new Object(); if ( "transfer" .equals(method.getName())){ System.out.println( "对转账人身份校验。。" ); result = method.invoke(target, objects); System.out.println( "进行日志记录。。" ); } else { // 直接执行目标对象的目标方法 result = methodProxy.invokeSuper(o, objects); } return result; } // 创建代理对象 public AccountService createProxy(){ // 创建增强器 Enhancer enhancer = new Enhancer(); // 初始化增强器:将目标类指定为父类 enhancer.setSuperclass(AccountService. class ); // 初始化增强器:设置回调至本类中的intercept()方法 enhancer.setCallback( this ); // 使用增强器创建代理对象 return (AccountService) enhancer.create(); } } |
4. 创建测试类
1 2 3 4 5 6 7 8 9 10 | public class CglibProxyTest { public static void main(String[] args) { // 目标对象 AccountService target = new AccountService(); // 创建代理对象,传入目标对象进行初始化 AccountService accountService = (AccountService) new AccountServiceCglibProxy(target).createProxy(); accountService.transfer(); accountService.getBalance(); } } |
Spring中的aop
Spring中的AOP,就是通过配置的方式(有基于XML配置的, 以及基于注解配置的),来实现相关的拦截切入功能。对原有的操作进行加强,但不影响原本的操作。
因为Spring AOP中的代理对象由IoC容器自动生成,所以开发者无须过多关注代理对象生成的过程,只需选择连接点、创建切面、定义切点并在XML文件中添加配置信息即可。 Spring提供了一系列配置Spring AOP的XML元素。
配置Spring AOP的XML元素
测试案例
1、引入pom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | <properties> <java.version> 8 </java.version> <project.build.sourceEncoding>UTF- 8 </project.build.sourceEncoding> <maven.compiler.source> 1.8 </maven.compiler.source> <maven.compiler.target> 1.8 </maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version> 4.0 . 0 .RELEASE</version> </dependency> <!-- https: //mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version> 4.0 . 0 .RELEASE</version> </dependency> <!-- https: //mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version> 4.0 . 0 .RELEASE</version> </dependency> <!-- https: //mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version> 1.1 . 1 </version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version> 4.12 </version> <scope>compile</scope> </dependency> <!-- https: //mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version> 4.0 . 0 .RELEASE</version> </dependency> <!-- https: //mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version> 2.2 . 2 </version> </dependency> <!-- https: //mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version> 1.8 . 13 </version> </dependency> <!-- https: //mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version> 1.0 </version> </dependency> </dependencies> |
2、创建目标类
1 2 3 4 5 6 7 8 | public class StudentService { public void eat(){ System.out.println( "我要开始吃午饭了" ); } } |
3、创建切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class StudentAspect { // 前置通知 public void before() { System.out.println( "前置通知===========" ); } // 后置通知: public void after() { System.out.println( "后置通知============" ); } // 环绕通知: public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println( "环绕前通知==========" ); Object obj = joinPoint.proceed(); System.out.println( "环绕后通知==========" ); return obj; } // 异常抛出通知: public void afterThrowing(Throwable e) { System.out.println( "异常抛出通知=========" + e.getMessage()); } //返回通知 public void afterReturning(Object result) { System.out.println( "返回通知===========" + result); } } |
4、创建配置文件
在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,因此,在使用<aop:aspect>元素之前,要在配置文件中先定义一个普通的Spring Bean。Spring Bean定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。
切点表达式类型execution匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。
表达式模式:
execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?)
表达式解释:
(1)modifier:匹配修饰符,public, private 等,省略时匹配任意修饰符
(2)ret-type:匹配返回类型,使用 * 匹配任意类型
(3)declaring-type:匹配目标类,省略时匹配任意类型
- .. 匹配包及其子包的所有类
(4)name-pattern:匹配方法名称,使用 * 表示通配符
- * 匹配任意方法
- set* 匹配名称以 set 开头的方法
(5)param-pattern:匹配参数类型和数量
- () 匹配没有参数的方法
- (..) 匹配有任意数量参数的方法
- (*) 匹配有一个任意类型参数的方法
- (*,String) 匹配有两个参数的方法,并且第一个为任意类型,第二个为 String 类型
(6)throws-pattern:匹配抛出异常类型,省略时匹配任意类型
使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" > <!-- 配置目标类 --> <bean id= "studentService" class = "com.gqx.StudentService" ></bean> <!-- 配置切面类 --> <bean id= "studentAspectXML" class = "com.gqx.StudentAspect" ></bean> <!-- 配置 AOP --> <aop:config> <!-- 配置切点表达式 --> <!-- 整个表达式可以分为五个部分: 1 、execution()::表达式主体。 2 、第一个*号:表示返回类型, *号表示所有的类型。 3 、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service包、子孙包下所有类的方法。 4 、第二个*号:表示类名,*号表示所有的类。 5 、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数 --> <!-- 配置切点 --> <aop:pointcut expression= "execution(* com.gqx.StudentService.eat(..))" id= "pointcut1" /> <!-- 配置切面及通知 --> <aop:aspect ref= "studentAspectXML" > <!-- 前置通知 --> <aop:before method= "before" pointcut-ref= "pointcut1" /> <!-- 后置通知 --> <aop:after method= "after" pointcut-ref= "pointcut1" /> <!-- 环绕通知--> <!-- <aop:around method= "around" pointcut-ref= "pointcut3" />--> <!-- 异常抛出通知 --> <!-- <aop:after-throwing method= "afterThrowing" pointcut-ref= "pointcut4" throwing= "e" />--> <!-- 返回通知 --> <!-- <aop:after-returning method= "afterReturning" pointcut-ref= "pointcut4" returning= "result" />--> </aop:aspect> </aop:config> </beans> |
5、创建运行类
1 2 3 4 5 6 7 8 9 10 11 | public class AppMain { public static void main(String[] args) { //1. 创建IoC容器对象,加载spring核心配置文件 //初始化Spring容器ApplicationContext,加载配置文件 ApplicationContext application = new ClassPathXmlApplicationContext( "applicationContext.xml" ); //2. 从IOC容器中获取Bean对象 StudentService studentService = (StudentService) application.getBean( "studentService" ); studentService.eat(); } } |
练习
补充下面接口测试代码,实现基于AOP的切面编程。
(1)创建接口UserDao,并在该接口中编写添加、删除、修改和查询的方法。
1 2 3 4 5 6 | public interface UserDao { public void insert(); public void delete(); public void update(); public void select(); } |
创建UserDao接口的实现类UserDaoImpl,实现UserDao接口中的方法。
1 2 3 4 5 6 7 8 9 10 | public class UserDaoImpl implements UserDao{ public void insert() { System.out.println( "添加用户信息" ); } public void delete() { System.out.println( "删除用户信息" ); } public void update() { System.out.println( "更新用户信息" ); } public void select() { System.out.println( "查询用户信息" ); } } |
(2)创建XmlAdvice类,用于定义切面通知。
1 2 3 4 5 6 7 8 9 10 | public class XmlAdvice { // 前置通知 public void before(JoinPoint joinPoint){ System.out.print( "这是前置通知!" ); System.out.print( "目标类是:" +joinPoint.getTarget()); System.out.println( ",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); } //需要补充:返回通知、环绕通知、异常通知、后置通知 } |
(3)创建applicationContext.xml文件,在该文件中引入AOP命名空间,使用<bean>元素添加Spring AOP的配置信息。
1 2 3 4 5 6 7 8 9 10 11 | <!-- 注册bean省略,下面内容为配置Spring AOP--> <aop:config> <aop:pointcut id= "pointcut" expression= "" /><!-- 指定切点 --> <aop:aspect ref = "xmlAdvice" ><!-- 指定切面 --> <aop:before method= "before" pointcut-ref= "pointcut" /><!-- 指定前置通知 --> <aop:after-returning method= "afterReturning" pointcut-ref= "pointcut" /> <aop:around method= "around" pointcut-ref= "pointcut" />-- 指定环绕方式 --> <aop:after-throwing method= "afterException" pointcut-ref= "pointcut" /> <aop:after method= "after" pointcut-ref= "pointcut" /><!-- 指定后置通知 --> </aop:aspect> </aop:config> |
(4)创建测试类TestXml,测试基于XML的AOP实现。
1 2 3 4 5 6 7 8 9 10 | public class TestXml{ public static void main(String[] args){ ApplicationContext context= new ClassPathXmlApplicationContext( "applicationContext.xml" ); UserDao userDao=context.getBean( "userDao" ,UserDao. class ); userDao.delete(); userDao.insert(); userDao.select(); userDao.update(); } } |
结果如图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!