面试官:说说你对AOP的理解 |
您所在的位置:网站首页 › aop一般用来干什么 › 面试官:说说你对AOP的理解 |
文章目录
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. 什么是AOPAOP即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 |