文档章节

Spring AOP是什么?你都拿它做什么?

我叫刘半仙
 我叫刘半仙
发布于 2017/07/18 18:21
字数 2564
阅读 9786
收藏 464

         对于最近博主最近写博客的兴致大发,我也在思考:为什么而写博客?在互联网时代,无论你是牛人大咖,还是小白菜鸟,都有发表自己看法的权利。无论你是对的还是错的,都会在这个平台上找到答案。所以,我会尽可能去写自己感兴趣的内容,无论正面或者负面的消息,都尽可能回复我的每一位读者。即使自己只有一个读者,也会坚持写下去。有一个平台,去表达自己,记录自己的点滴,难道不是一种快乐吗?同样,了解技术是一个深入和拓展的过程,需要一个人清晰严谨的逻辑思维。有时候,写博客更像是给自己做笔记,巩固分散的知识!

         上一篇文章中,我对Spring源码进行了分析讨论,此处不再赘述,有兴趣的同学可以看看向Spring大佬低头--大量源码流出解析,本文是对上一篇文章的一个补充。回到正题,为什么会有面向切面编程(AOP)?我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志,权限验证,事务等功能时,只能在在每个对象里引用公共行为,这样做不便于维护,而且有大量重复代码。AOP的出现弥补了OOP的这点不足。

为了阐述清楚Spring AOP,我们从将以下方面进行讨论:

         1.代理模式。

         2.静态代理原理及实践。

         3.动态代理原理及实践。

         4.Spring AOP原理及实战。

1.代理模式。

         代理模式:为其他对象提供一种代理以控制对这个对象的访问。这段话比较官方,但我更倾向于用自己的语言理解:比如A对象要做一件事情,在没有代理前,自己来做,在对A代理后,由A的代理类B来做。代理其实是在原实例前后加了一层处理,这也是AOP的初级轮廓。

2.静态代理原理及实践。

         静态代理模式:静态代理说白了就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。废话不多说,我们看一下代码,为了方便阅读,博主把单独的class文件合并到接口中,读者可以直接复制代码运行:

package test.staticProxy;
// 接口
public interface IUserDao {
	void save();
	void find();
}
//目标对象
class UserDao implements IUserDao{
	@Override
	public void save() {
		System.out.println("模拟:保存用户!");
	}
	@Override
	public void find() {
		System.out.println("模拟:查询用户");
	}
}
/**
    静态代理
          特点:
	1. 目标对象必须要实现接口
	2. 代理对象,要实现与目标对象一样的接口
 */
class UserDaoProxy implements IUserDao{
	// 代理对象,需要维护一个目标对象
	private IUserDao target = new UserDao();
	@Override
	public void save() {
		System.out.println("代理操作: 开启事务...");
		target.save();   // 执行目标对象的方法
		System.out.println("代理操作:提交事务...");
	}
	@Override
	public void find() {
		target.find();
	}
}

测试结果:

                               

         静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理。再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那么要如何解决呢?答案是使用动态代理。

3.动态代理原理及实践。

         动态代理模式:动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。实例如下:

package test.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 接口
public interface IUserDao {
	void save();
	void find();
}
//目标对象
 class UserDao implements IUserDao{
	@Override
	public void save() {
		System.out.println("模拟: 保存用户!");
	}
	@Override
	public void find() {
		System.out.println("查询");
	}
}
/**
 * 动态代理:
 *    代理工厂,给多个目标对象生成代理对象!
 *
 */
class ProxyFactory {
	// 接收一个目标对象
	private Object target;
	public ProxyFactory(Object target) {
		this.target = target;
	}
	// 返回对目标对象(target)代理后的对象(proxy)
	public Object getProxyInstance() {
		Object proxy = Proxy.newProxyInstance(
			target.getClass().getClassLoader(),  // 目标对象使用的类加载器
			target.getClass().getInterfaces(),   // 目标对象实现的所有接口
			new InvocationHandler() {			// 执行代理对象方法时候触发
				@Override
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					
					// 获取当前执行的方法的方法名
					String methodName = method.getName();
					// 方法返回值
					Object result = null;
					if ("find".equals(methodName)) {
						// 直接调用目标对象方法
						result = method.invoke(target, args);
					} else {
						System.out.println("开启事务...");
						// 执行目标对象方法
						result = method.invoke(target, args);
						System.out.println("提交事务...");
					}
					return result;
				}
			}
		);
		return proxy;
	}
}

测试结果如下:

                       

在运行测试类中创建测试类对象代码中

IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();

其实是JDK动态生成了一个类去实现接口,隐藏了这个过程:

class $jdkProxy implements IUserDao{}

        使用jdk生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用JDK动态代理,所以Cglib代理就是解决这个问题的。

       Cglib是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类,如下:

public class UserDao{}
//Cglib是以动态生成的子类继承目标的方式实现,程序执行时,隐藏了下面的过程
public class $Cglib_Proxy_class  extends UserDao{}

       Cglib使用的前提是目标类不能为final修饰。因为final修饰的类不能被继承。

       现在,我们可以看看AOP的定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。

       通过定义和前面代码我们可以发现3点:

         1.AOP是基于动态代理模式。

         2.AOP是方法级别的(要测试的方法不能为static修饰,因为接口中不能存在静态方法,编译就会报错)。

         3.AOP可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。

4.spring AOP原理及实战。

         前文提到JDK代理和Cglib代理两种动态代理,优秀的Spring框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?:

         1.创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。

         2.如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib代理。然后从容器获取代理后的对象,在运行期植入"切面"类的方法。通过查看Spring源码,我们在DefaultAopProxyFactory类中,找到这样一段话。

         简单的从字面意思看出,如果有接口,则使用Jdk代理,反之使用Cglib,这刚好印证了前文所阐述的内容。Spring AOP综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且class为final修饰的,则不能进行Spring AOP编程!

         知道了原理,现在我们将自己手动实现Spring的AOP:

package test.spring_aop_anno;

import org.aspectj.lang.ProceedingJoinPoint;

public interface IUserDao {
	void save();
}
//用于测试Cglib动态代理
class OrderDao {
	public void save() {
		//int i =1/0;用于测试异常通知
		System.out.println("保存订单...");
	}
}
//用于测试jdk动态代理
class UserDao implements IUserDao {
	public void save() {
		//int i =1/0;用于测试异常通知
		System.out.println("保存用户...");
	}
}
//切面类
class TransactionAop {
	public void beginTransaction() {
		System.out.println("[前置通知]  开启事务..");
	}
	public void commit() {
		System.out.println("[后置通知] 提交事务..");
	}
	public void afterReturing(){
		System.out.println("[返回后通知]");
	}
	public void afterThrowing(){
		System.out.println("[异常通知]");
	}
	public void arroud(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("[环绕前:]");
		pjp.proceed();    			   // 执行目标方法
		System.out.println("[环绕后:]");
	}
}

Spring的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:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!-- dao实例加入容器 -->
	<bean id="userDao" class="test.spring_aop_anno.UserDao"></bean>
	
	<!-- dao实例加入容器 -->
	<bean id="orderDao" class="test.spring_aop_anno.OrderDao"></bean>
	
	<!-- 实例化切面类 -->
	<bean id="transactionAop" class="test.spring_aop_anno.TransactionAop"></bean>
	
	<!-- Aop相关配置 -->
	<aop:config>
		<!-- 切入点表达式定义 -->
		<aop:pointcut expression="execution(* test.spring_aop_anno.*Dao.*(..))" id="transactionPointcut"/>
		<!-- 切面配置 -->
		<aop:aspect ref="transactionAop">
			<!-- 【环绕通知】 -->
			<aop:around method="arroud" pointcut-ref="transactionPointcut"/>
			<!-- 【前置通知】 在目标方法之前执行 -->
			<aop:before method="beginTransaction" pointcut-ref="transactionPointcut" />
			<!-- 【后置通知】 -->
			<aop:after method="commit" pointcut-ref="transactionPointcut"/>
			<!-- 【返回后通知】 -->
			<aop:after-returning method="afterReturing" pointcut-ref="transactionPointcut"/>
			<!-- 异常通知 -->
			<aop:after-throwing method="afterThrowing" pointcut-ref="transactionPointcut"/>
		</aop:aspect>
	</aop:config>
</beans>      

切入点表达式不在这里介绍。ref:Spring AOP 切入点表达式

代码的测试结果如下:

                

到这里,我们已经全部介绍完Spring AOP,回到开篇的问题,我们拿它做什么?

         1.Spring声明式事务管理配置,博主的另一篇文章:分布式系统架构实战demo:SSM+Dubbo

         2.Controller层的参数校验。ref:Spring AOP拦截Controller做参数校验

         3.使用Spring AOP实现MySQL数据库读写分离案例分析

         4.在执行方法前,判断是否具有权限。

         5.,对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。

         6.信息过滤,页面转发等等功能,博主一个人的力量有限,只能列举这么多,欢迎评论区对文章做补充。

     Spring AOP还能做什么,实现什么魔幻功能,就在于我们每一个平凡而又睿智的程序猿!

© 著作权归作者所有

共有 人打赏支持
我叫刘半仙
粉丝 216
博文 24
码字总数 51600
作品 0
西安
程序员
加载中

评论(33)

飘渺青衣
飘渺青衣
写的好
我叫刘半仙
我叫刘半仙

引用来自“异想天开12138”的评论

依赖包怎么办?
最简单的方法就是放在一个带有spring的项目里测试,如果没有建议看下我的另外一篇文章:分布式系统架构实战demo:ssm+dubbo,顺便练习一下搭建项目,不想练习的话,可以直接把源码下载下来,导入到你的eclipse里,让maven自己下载依赖包就可以测试了
异想天开12138
依赖包怎么办?
我叫刘半仙
我叫刘半仙

引用来自“飞吧12321”的评论

博主,我是新手,我这样理解对吗?静态代理是为一个类做定制,而动态代理是批量的。������
静态代理也可以代理一批对象,只要实现每个类所对应的接口,然后添加他们的未实现的方法进行代理。但代码重复高,接口要添加一个方法除了实现类要实现外,代理类也要添加未实现的方法,可维护性不好。
class UserDaoProxy implements IUserDao,OrderDao{
  // 代理对象,需要维护一个目标对象
  private IUserDao target = new UserDao();
  
  private OrderDao target2 = new OrderDaoImple();
  @Override
  public void save() {
    System.out.println("代理操作: 开启事务...");
    target.save(); // 执行目标对象的方法
    target2.save();
    System.out.println("代理操作:提交事务...");
  }
  @Override
  public void find() {
    target.find();
    target2.find();
  }
}
飞吧12321
飞吧12321
博主,我是新手,我这样理解对吗?静态代理是为一个类做定制,而动态代理是批量的。������
l
leojj
言简意赅
tzm529
tzm529
谢谢分享,写的不错
我叫刘半仙
我叫刘半仙

引用来自“zz腿”的评论

666
感谢老铁支持:wink:
我叫刘半仙
我叫刘半仙

引用来自“springsky”的评论

写的好
感谢支持:bowtie:
我叫刘半仙
我叫刘半仙

引用来自“谷粑糖”的评论

3.使用Spring AOP实现MySQL数据库读写分离案例分析
5.,对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。

这两点很新颖!:+1:
更新颖的方式等待着你来创造+1
轻松理解AOP(面向切面编程)

本文主要介绍AOP思想,而不是Spring,Spring在本文只做为理解AOP的工具和例子,所以也不打算介绍Spring的Aspect、Join point、Advice、AOP proxy等概念,那样初学者会很难理解,如果你懂了A...

爱捣鼓
2014/02/26
0
0
1000行代码读懂Spring(二)- 在Spring中实现AOP

关于AOP AOP是Spring核心功能之一。今天就用tiny-spring来实现一个AOP。具体功能会包括: 读取AspectJ格式的Pointcut描述。 使用JDK动态代理以及CGLib两种方式进行AOP织入。 AOP分为配置(Po...

黄亿华
2014/01/20
0
3
Spring - AOP简单理解及术语解释

工作中使用Spring Boot将近一年了,由于某些需要也接触过Spring Cloud。Spring到Spring Boot再到Spring Cloud,逐渐地变成了一套完整的企业分布式系统的解决方案,已经不能单纯的认为是基于S...

陶源0111
06/17
0
0
BeanPostProcessor —— 连接Spring IOC和AOP的桥梁

之前都是从大Boss的视角,来介绍Spring,比如IOC、AOP。 今天换个视角,从一个小喽啰出发,来加深对Spring的理解。 这个小喽啰就是,BeanPostProcessor(下面简称BBP)。 讲解思路: BBP怎么...

SexyCode
06/19
0
0
[转载]69道Spring面试题和答案

原文地址 http://ifeve.com/spring-interview-questions-and-answers/ 目录 Spring 概述 依赖注入 Spring beans Spring注解 Spring数据访问 Spring面向切面编程(AOP) Spring MVC Spring 概......

王木东
2017/03/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Go语言_通神之路(2)

1、包 每个Go程序都是由包构成,从main包开始运行,就是我上一篇讲到的,都是从main函数开始执行,但是必须在main包下面! package mainimport ( "fmt" "math/rand")func ...

木九天
昨天
5
0
51.php-fpm的pool 慢日志 open_basedir 进程管理

12.21 php-fpm的pool 12.22 php-fpm慢执行日志(测试时报错) 12.23 open_basedir 12.24 php-fpm进程管理 12.21 php-fpm的pool: php-fpm里的pool也叫池子,咱们之前加入过www的配置,这个w...

王鑫linux
昨天
0
0
java内存模型概述

1、Java虚拟机运行时数据分区图 程序计数器:线程私有,是一块较小的内存空间,它是当前线程所执行的字节码文件的行号指示器 java虚拟机栈:线程私有,其生命周期与线程相同,这也就是我们平...

京一
昨天
2
0
shell学习之test语法

因为if-then语句不能测试退出状态码之外的条件,所以提供了test, 如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0;如果条件不成立,test命令就会退出并返回非零的退出状态...

woshixin
昨天
0
0
openJDK之如何下载各个版本的openJDK源码

如果我们需要阅读openJDK的源码,那么需要下载,那么该去哪下载呢? 现在JDK已经发展到版本10了,11已经处于计划中,如果需要特定版本的openJDK,它们的下载链接在哪呢? 1.openJDK的项目 链接...

汉斯-冯-拉特
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部