面试官:说说你对AOP的理解

您所在的位置:网站首页 aop一般用来干什么 面试官:说说你对AOP的理解

面试官:说说你对AOP的理解

2024-05-25 22:37| 来源: 网络整理| 查看: 265

文章目录 0. 前言1. 什么是AOP1.1 手写一个AOP1.2 AOP中的相关概念1.2.1 关注点代码和核心代码1.2.2 切入点(Pointcut)1.2.3 通知(Advice)1.2.4 切面(Aspect)1.2.5 织入(Weaving) 2. Spring AOP的使用2.1 基于注解的开发2.1.1 引入aop相关的jar包2.1.2 在配置文件中开启AOP注解方式2.1.3 定义切面类 2.2 通过XML配置文件的开发2.3 通知的类型

0. 前言

这篇文章主要讲解AOP的基本概念和在Spring中如何使用AOP,在下一篇文章我会记录我在项目中如何使用AOP进行日志管理的。

1. 什么是AOP

AOP即Aspect Oriented Program,面向切面编程,是面向对象编程(OOP)的一种增强模式,可以将项目中与业务无关的,却为业务模块所共同调用的非核心代码封装成(比如事务管理、日志管理、权限控制等等)一个个切面,然后在运行的时候通过动态代理或者CGLib代理的方式织入到核心业务功能中。

1.1 手写一个AOP

为了更好地理解AOP的概念,我们可以一步步来手写一个AOP。

用户接口UserService

package com.example.demo.Service; public interface UserService { /** * 插入一个用户 */ public void insertUser(); /** * 更新一个用户 */ public void updateUser(); /** * 删除一个用户 */ public void deleteUser(); /** * 选择一个用户 */ public void selectUser(); }

用户接口实现类UserServiceImpl

public class UserServiceImpl implements UserService { @Override public void insertUser() { System.out.println("开启事务"); System.out.println("插入一个用户"); System.out.println("关闭事务"); } @Override public void updateUser() { System.out.println("开启事务"); System.out.println("更新一个用户"); System.out.println("关闭事务"); } @Override public void deleteUser() { System.out.println("开启事务"); System.out.println("删除一个用户"); System.out.println("关闭事务"); } @Override public void selectUser() { System.out.println("开启事务"); System.out.println("选择一个用户"); System.out.println("关闭事务"); } }

UserServiceImpl类中每一个方法都需要开启事务和关闭事务,那么为了减少代码量,我们可以把重复的代码抽象成方法,如下

@Service public class UserServiceImpl implements UserService { @Override public void insertUser() { openTransition(); System.out.println("插入一个用户"); closeTransition(); } @Override public void updateUser() { openTransition(); System.out.println("更新一个用户"); closeTransition(); } @Override public void deleteUser() { openTransition(); System.out.println("删除一个用户"); closeTransition(); } @Override public void selectUser() { openTransition(); System.out.println("选择一个用户"); closeTransition(); } public void openTransition(){ System.out.println("开启事务"); } public void closeTransition(){ System.out.println("关闭事务"); } }

但是这个时候,我另一个接口HomeService的实现类HomeServiceImpl中的方法也需要开启事务和关闭事务,所以我会继续优化,把开启事务和关闭事务的功能代码抽象到一个类里面,然后Service层的实现类都可以复用同一份代码,如下

@Component public class TransitionAOP { public void openTransition(){ System.out.println("开启事务"); } public void closeTransition(){ System.out.println("关闭事务"); } } @Service public class UserServiceImpl implements UserService { @Autowired private TransitionAOP transitionAOP; @Override public void insertUser() { transitionAOP.openTransition(); System.out.println("插入一个用户"); transitionAOP.closeTransition(); } @Override public void updateUser() { transitionAOP.openTransition(); System.out.println("更新一个用户"); transitionAOP.closeTransition(); } @Override public void deleteUser() { transitionAOP.openTransition(); System.out.println("删除一个用户"); transitionAOP.closeTransition(); } @Override public void selectUser() { transitionAOP.openTransition(); System.out.println("选择一个用户"); transitionAOP.closeTransition(); } }

现在只要我其他Service层的实现类要实现开启和关闭事务功能,都需要手动引入TransitionAOP类,然后在方法前后分别调用openTransition()和closeTransition()方法。其实对于UserServiceImpl类来说,这两个方法属于对接口实现类方法的增强,但是这个增强是和接口实现类紧耦合的,那么为了降低增强功能和核心业务功能的耦合度,可不可以在执行方法的时候,动态执行开启事务和关闭事务呢?如果你看过我上一篇文章动态代理的话,那么答案就是使用动态代理的方式,为接口实现类(目标对象)动态生成一个代理对象,然后拦截实现类中的方法,做相应的事务增强。

代理工厂类TransitionProxyFactory

public class TransitionProxyFactory { //目标对象 private static Object target; //关注点业务类 private static TransitionAOP transitionAOP; //新建一个代理对象方法 public static Object getProxyInstance(Object target_,TransitionAOP transitionAOP_) { target = target_; transitionAOP = transitionAOP_; return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { transitionAOP.openTransition(); Object ret = method.invoke(target,args); transitionAOP.closeTransition(); return ret; } } ); } }

在测试类里面测试一下

@SpringBootTest class DemoApplicationTests { @Autowired private TransitionAOP transitionAOP; @Autowired private UserService userService; @Test void contextLoads() { //新建userService的代理类 UserService userServiceProxy = (UserService) TransitionProxyFactory.getProxyInstance(userService,transitionAOP); userServiceProxy.deleteUser(); userServiceProxy.insertUser(); userServiceProxy.updateUser(); userServiceProxy.selectUser(); } }

打印的结果如下: 在这里插入图片描述

1.2 AOP中的相关概念 1.2.1 关注点代码和核心代码

【关注点代码】指的就是与业务无关,但是又被业务模块所共同调用的非核心代码,如下图红色框中的代码就属于关注点代码。 【核心代码】指的就是与业务相关的代码。

在这里插入图片描述 AOP的目的就是要实现"关注点代码"与"核心代码"分离,然后通过面向切面的方式,将关注点代码织入到核心代码中。

1.2.2 切入点(Pointcut)

切入点指的就是在哪些类的哪些方法上织入切面(Where)。

在上面的例子中,我们的切入点就是UserServiceImpl类中的方法。

1.2.3 通知(Advice)

通知指的是在方法执行的什么时候(When:方法前/方法后/方法前后/方法异常/方法返回)做什么增强(What)。

在上面的例子中,我们的通知就是在方法的前面开启事务,在方法的后面关闭事务。

1.2.4 切面(Aspect)

切面 = 切入点+通知,指的就是在什么地方,什么时候,做了什么增强。

1.2.5 织入(Weaving)

织入指的就是把切面加入到目标对象中,并创建出目标对象对应的代理对象(由Spring完成)

2. Spring AOP的使用

在Spring中,提供了两种AOP的开发方式,一种是基于注解的方式,一种是基于XML配置文件的方式,注解的方式在开发过程中会更加方便。在Spring中AOP的实现主要是基于JDK动态代理和CGLib代理方式。

2.1 基于注解的开发

在上面手写AOP的例子中,动态代理的代理工厂类是需要我们自己手写的,但是使用Spring的话,内部会自动为我们创建代理工厂,因此我们只需要关心切面类、切入点、通知的设计就可以。

2.1.1 引入aop相关的jar包

因为我使用的是Maven项目,所以直接在pom.xml文件中引入aop相关的jar包,主要有四个核心jar包。

org.springframework spring-aop 5.0.7.RELEASE aopalliance aopalliance 1.0 org.aspectj aspectjweaver 1.9.1 org.springframework spring-aspects 5.0.7.RELEASE 2.1.2 在配置文件中开启AOP注解方式

applicationContext.xml

2.1.3 定义切面类 @Component @Aspect public class TransitionAOP { @Pointcut("execution(* com.example.demo.Service.*.*(..))") public void pt(){ } @Before() public void openTransition("pt()"){ System.out.println("开启事务"); } @Before("pt()") public void closeTransition(){ System.out.println("关闭事务"); } }

上面代码中,execution()是指切入点表达式,可以通过使用@Poincut()注解来指定切入点表达式,然后后面使用到的地方直接引用就可以,避免重复写切入点表达式的代码。

测试代码:

public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //这里得到的是代理对象 UserService userService = (UserService) applicationContext.getBean("UserServiceImpl"); System.out.println(userService.getClass()); userService.deleteUser(); } }

打印的结果如下 在这里插入图片描述 从运行信息可以看出,Spring已经通过动态代理自动为UserServiceImpl类创建了一个代理对象$Proxy15。

通过动态代理的方式为目标对象创建一个代理对象的前提是,目标对象要实现接口,而如果对于没有实现接口的目标对象,会使用CGLib代理模式去动态得生成目标对象的子类来进行功能增强。

定义一个没有实现接口的类

@Component public class NonInterfaceUser { public void insertUser(){ System.out.println("插入一个用户"); } }

测试代码:

public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); NonInterfaceUser userService = (NonInterfaceUser) applicationContext.getBean("NonInterfaceUser"); System.out.println(userService.getClass()); userService.insertUser(); } }

打印的结果如下: 在这里插入图片描述

2.2 通过XML配置文件的开发

配置文件applicationContext.xml

测试代码

public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) applicationContext.getBean("UserServiceImpl"); NonInterfaceUser userService1 = (NonInterfaceUser) applicationContext.getBean("NonInterfaceUser"); System.out.println(userService.getClass()); userService.insertUser(); System.out.println(userService1.getClass()); userService1.insertUser(); } }

打印的结果如下: 在这里插入图片描述

2.3 通知的类型

通知指的是执行方法的什么时候,做什么增强,这里什么时候指的就是通知的类型,分为5大类,前置通知、后置通知、环绕通知、方法返回后通知和方法异常通知。在Spring 注解开发中分别对应以下五种注解

@Before():前置通知,目标方法执行之前通知。@After():后置通知,目标方法执行之后通知。@Around():环绕通知,目标方法执行前后通知。@AfterReturning():方法返回的时候通知。@AfterThrowing():方法异常的时候通知。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3