AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
下面举个生活上的例子来帮助理解AOP是什么样的思想,先看一下传统程序的流程,比如银行系统会有一个取款流程:
我们可以把方框里的流出合为一个,另外系统还会有一个查询余额的流出,我们先把这两个流程放在一起:
接口
这有个问题就是,有多少接口,就要copy多少次代码,这显然不符合我们的要求。好,我们提取一个公共的方法,让每个接口都来调用这个方法,这里有点切面的味道了:
同样有个问题,虽然不用每次都copy代码了,但是每个接口总要调用这个方法吧。有没有想过把这个验证用户的代码提取出去,不放到主流程里去呢?这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另我一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程。举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。
现在大概明白了AOP了吗,我们来理一下头绪,上面那个方框像不像个平面,你可以把它当块板子,这块板子插入一些控制流程,这块板子就可以当成是AOP中的一个切面。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面,这句话应该好理解吧,我们把纵向流程画成一条直线,然把相同的部分以绿色突出,如下图左,而AOP相当于把相同的地方连一条横线,如下图右,这个图没画好,大家明白意思就行。
使用AOP之前,我们需要理解几个概念。
连接点(Join Point):所有可能的需要注入切面的地方。如方法前后、类初始化、属性初始化前后等等。
切入点(Poincut):需要做某些处理(如打印日志、处理缓存等等)的连接点。
通知(Advice):定义在什么时候做什么事情。通知分为前置、后置、异常、最终、环绕通知五类
切面(Aspect):通知+切点的集合,定义在什么地方什么时间做什么事情。
目标对象(Target):被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
织入(Weaving):把切面应用到目标对象来创建新的代理对象的过程。
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
public class MyJDKProxy implements InvocationHandler { private UserDao userDao; public MyJDKProxy(UserDao userDao) { this.userDao = userDao; } // 编写工具方法:生成代理 public UserDao createProxy(){ UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this); return userDaoProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("save".equals(method.getName())){ System.out.println("权限校验================"); } return method.invoke(userDao, args); } }
注意:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类
public class MyCglibProxy implements MethodInterceptor { private CustomerDao customerDao; public MyCglibProxy(CustomerDao customerDao) { this.customerDao = customerDao; } // 生成代理的方法 public CustomerDao createProxy(){ // 创建Cglib的核心类 Enhancer enhancer = new Enhancer(); // 设置父类 enhancer.setSuperclass(CustomerDao.class); // 设置回调 enhancer.setCallback(this); // 生成代理 CustomerDao customerDaoProxy = (CustomerDao) enhancer.create(); return customerDaoProxy; } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if("delete".equals(method.getName())){ Object obj = methodProxy.invokeSuper(proxy, args); System.out.println("日志记录=============="); return obj; } return methodProxy.invokeSuper(proxy, args); } }
Cglib代理可以对任何类生成代理,代理的原理是对目标对象进行继承代理.,如果目标对象被final修饰.那么该类无法被cglib代理。
第一步:引入相应的jar包
第二步:准备目标对象
public class UserServiceImpl implements UserService { public void save() { System.out.println("保存用户"); } public void delete() { System.out.println("删除用户"); } public void update() { System.out.println("更新用户"); } public void find() { System.out.println("查找用户"); } }
第三步:准备通知
public class MyAdvice { // 前置通知:目标方法运行之前调用 public void before(){ System.out.println("这是前置通知"); } // 后置通知:在目标方法运行之后调用(如果出现异常不会调用) public void afterReturning(){ System.out.println("这是后置通知(如果出现异常不会调用)"); } // 环绕通知:在目标方法之前和之后都调用 public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("这是环绕通知之前的部分"); Object proceed = pjp.proceed();// 调用目标方法 System.out.println("这是环绕通知之后的部分"); return proceed; } // 异常拦截通知(如果出现异常,就会调用) public void afterException(){ System.out.println("出现异常了"); } // 后置通知:在目标方法运行之后调用(无论是否出现异常,都会调用) public void after(){ System.out.println("这是后置通知(出现异常也会调用)"); } }
第四步:准备applicationContext.xml,并导入aop约束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd "> </beans>
第五步:将通知织入目标对象中
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd "> <!-- 1.准备目标对象 --> <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"></bean> <!-- 2.准备通知对象 --> <bean id="myAdvice" class="cn.itcast.service.MyAdvice"></bean> <!-- 3.将通知织入目标对象 --> <aop:config> <!-- 配置切入点 public void cn.itcast.service.UserServiceImpl.save() void cn.itcast.service.UserServiceImpl.save() * cn.itcast.service.UserServiceImpl.save() * cn.itcast.service.UserServiceImpl.*() * cn.itcast.service.*ServiceImpl.*(..) * cn.itcast.service..*ServiceImpl.*(..) --> <aop:pointcut expression="execution(* cn.itcast.service..*ServiceImpl.*(..))" id="pointcut"/> <aop:aspect ref="myAdvice"> <!-- 指定名为before的方法作为前置通知 --> <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> </beans>
第六步:编写测试方法
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo { @Resource(name="userService") private UserService userService; @Test public void fun1() throws Exception { userService.save(); } }
结果输出:
第一步:导包
第二步:准备目标对象
public class UserServiceImpl implements UserService { public void save() { System.out.println("保存用户"); } public void delete() { System.out.println("删除用户"); } public void update() { System.out.println("更新用户"); } public void find() { System.out.println("查找用户"); } }
第三步:准备通知
// 表示该类是一个通知类 @Aspect public class MyAdvice { @Pointcut("execution(* cn.itcast.service..*ServiceImpl.*(..))") public void pointcut(){ } // 前置通知:目标方法运行之前调用 // 指定该方法是前置通知,并指定切入点 @Before("MyAdvice.pointcut()") public void before(){ System.out.println("这是前置通知"); } // 后置通知:在目标方法运行之后调用(如果出现异常不会调用) // 这里的execution(* cn.itcast.service..*ServiceImpl.*(..))等价于MyAdvice.pointcut() @AfterReturning("execution(* cn.itcast.service..*ServiceImpl.*(..))") public void afterReturning(){ System.out.println("这是后置通知(如果出现异常不会调用)"); } // 环绕通知:在目标方法之前和之后都调用 @Around("execution(* cn.itcast.service..*ServiceImpl.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("这是环绕通知之前的部分"); Object proceed = pjp.proceed();// 调用目标方法 System.out.println("这是环绕通知之后的部分"); return proceed; } // 异常拦截通知(如果出现异常,就会调用) @AfterThrowing("execution(* cn.itcast.service..*ServiceImpl.*(..))") public void afterException(){ System.out.println("出现异常了"); } // 后置通知:在目标方法运行之后调用(无论是否出现异常,都会调用) @After("execution(* cn.itcast.service..*ServiceImpl.*(..))") public void after(){ System.out.println("这是后置通知(出现异常也会调用)"); } }
第四步:开启使用注解完成织入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd "> <!-- 1.准备目标对象 --> <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"></bean> <!-- 2.准备通知对象 --> <bean id="myAdvice" class="cn.itcast.service.MyAdvice"></bean> <!-- 3.开启使用注解完成织入 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
第五步:测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo { @Resource(name="userService") private UserService userService; @Test public void fun1() throws Exception { userService.save(); } }
参考资料:https://www.cnblogs.com/Wolfmanlq/p/6036019.html
https://www.jianshu.com/p/570c5283b1fc
原文:https://www.cnblogs.com/yft-javaNotes/p/10295292.html