,AOP 什么?
AOP(Aspect Oriented Programming), 通常称为面向切面编程. 它利用一种称为 "横切" 的技术, 剖解开封装的对象内部, 并将那些影响了多个类的公共行为封装到一个可重用模块, 并将其命名为 "Aspect", 即切面. 所谓 "切面", 简单说就是那些与业务无关, 却为业务模块所共同调用的逻辑或责任封装起来, 便于减少系统的重复代码, 降低模块之间的耦合度, 并有利于未来的可操作性和可维护性.
什么是切面, 什么是公共模块, 那么我们概念少说, 直接通过一个实例来看看 AOP 到底是什么.
2, 需求
现在有一张表 User, 然后我们要在程序中实现对 User 表的增加和删除操作.
要求: 增加和删除操作都必须要开启事务, 操作完成之后要提交事务.
- package com.ys.aop.one;
- public class User {
- private int uid;
- private String uname;
- public int getUid() {
- return uid;
- }
- public void setUid(int uid) {
- this.uid = uid;
- }
- public String getUname() {
- return uname;
- }
- public void setUname(String uname) {
- this.uname = uname;
- }
- }
3, 解决办法 1: 使用静态代理
第一步: 创建 UserService 接口
- package com.ys.aop.one;
- public interface UserService {
- // 添加 user
- public void addUser(User user);
- // 删除 user
- public void deleteUser(int uid);
第二步: 创建 UserService 的实现类
- package com.ys.aop.one;
- public class UserServiceImpl implements UserService{
- @Override
- public void addUser(User user) {
- System.out.println("增加 User");
- }
- @Override
- public void deleteUser(int uid) {
- System.out.println("删除 User");
- }
- }
第三步: 创建事务类 MyTransaction
- package com.ys.aop.one;
- public class MyTransaction {
- // 开启事务
- public void before(){
- System.out.println("开启事务");
- }
- // 提交事务
- public void after(){
- System.out.println("提交事务");
- }
- }
第四步: 创建代理类 ProxyUser.java
- package com.ys.aop.one;
- public class ProxyUser implements UserService{
- // 真实类
- private UserService userService;
- // 事务类
- private MyTransaction transaction;
- // 使用构造函数实例化
- public ProxyUser(UserService userService,MyTransaction transaction){
- this.userService = userService;
- this.transaction = transaction;
- }
- @Override
- public void addUser(User user) {
- transaction.before();
- userService.addUser(user);
- transaction.after();
- }
- @Override
- public void deleteUser(int uid) {
- transaction.before();
- userService.deleteUser(uid);
- transaction.after();
- }
- }
这是一个很基础的静态代理, 业务类 UserServiceImpl 只需要关注业务逻辑本身, 保证了业务的重用性, 这也是代理类的优点, 没什么好说的. 我们主要说说这样写的缺点:
1, 代理对象的一个接口只服务于一种类型的对象, 如果要代理的方法很多, 势必要为每一种方法都进行代理, 静态代理在程序规模稍大时就无法胜任了.
2, 如果接口增加一个方法, 比如 UserService 增加修改 updateUser() 方法, 则除了所有实现类需要实现这个方法外, 所有代理类也需要实现此方法. 增加了代码维护的复杂度.
4, 解决办法 2: 使用 JDK 动态代理
动态代理就不要自己手动生成代理类了, 我们去掉 ProxyUser.java 类, 增加一个 ObjectInterceptor.java 类
- package com.ys.aop.two;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import com.ys.aop.one.MyTransaction;
- public class ObjectInterceptor implements InvocationHandler{
- // 目标类
- private Object target;
- // 切面类 (这里指事务类)
- private MyTransaction transaction;
- // 通过构造器赋值
- public ObjectInterceptor(Object target,MyTransaction transaction){
- this.target = target;
- this.transaction = transaction;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- // 开启事务
- this.transaction.before();
- // 调用目标类方法
- method.invoke(this.target, args);
- // 提交事务
- this.transaction.after();
- return null;
- }
- }
那么使用动态代理来完成这个需求就很好了, 后期在 UserService 中增加业务方法, 都不用更改代码就能自动给我们生成代理对象. 而且将 UserService 换成别的类也是可以的.
也就是做到了代理对象能够代理多个目标类, 多个目标方法.
注意: 我们这里使用的是 JDK 动态代理, 要求是必须要实现接口. 与之对应的另外一种动态代理实现模式 Cglib, 则不需要, 我们这里就不讲解 cglib 的实现方式了.
不管是哪种方式实现动态代理. 本章的主角: AOP 实现原理也是动态代理
5,AOP 关键术语
1.target: 目标类, 需要被代理的类. 例如: UserService
2.Joinpoint(连接点): 所谓连接点是指那些可能被拦截到的方法. 例如: 所有的方法
3.PointCut 切入点: 已经被增强的连接点. 例如: addUser()
4.advice 通知 / 增强, 增强代码. 例如: after,before
5. Weaving(织入): 是指把增强 advice 应用到目标对象 target 来创建新的代理对象 proxy 的过程.
6.proxy 代理类: 通知 + 切入点
7. Aspect(切面): 是切入点 pointcut 和通知 advice 的结合
具体可以根据下面这张图来理解:
6,AOP 的通知类型
Spring 按照通知 Advice 在目标类方法的连接点位置, 可以分为 5 类
前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强, 比如上面例子的 before() 方法
后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强, 比如上面例子的 after() 方法
环绕通知 org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强
异常抛出通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常后实施增强
引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性
7, 使用 Spring AOP 解决上面的需求
我们只需要在 Spring 的配置文件 applicationContext.xml 进行如下配置:
- <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">
- <!--1, 创建目标类 -->
- <bean id="userService" class="com.ys.aop.UserServiceImpl"></bean>
- <!--2, 创建切面类 (通知) -->
- <bean id="transaction" class="com.ys.aop.one.MyTransaction"></bean>
- <!--3,aop 编程
- 3.1 导入命名空间
- 3.2 使用 <aop:config > 进行配置
- proxy-target-class="true" 声明时使用 cglib 代理
- 如果不声明, Spring 会自动选择 cglib 代理还是 JDK 动态代理
- <aop:pointcut> 切入点 , 从目标对象获得具体方法
- <aop:advisor> 特殊的切面, 只有一个通知 和 一个切入点
- advice-ref 通知引用
- pointcut-ref 切入点引用
- 3.3 切入点表达式
- execution(* com.ys.aop.*.*(..))
- 选择方法 返回值任意 包 类名任意 方法名任意 参数任意
- -->
- <aop:config>
- <!-- 切入点表达式 -->
- <aop:pointcut expression="execution(* com.ys.aop.*.*(..))" id="myPointCut"/>
- <aop:aspect ref="transaction">
- <!-- 配置前置通知, 注意 method 的值要和 对应切面的类方法名称相同 -->
- <aop:before method="before" pointcut-ref="myPointCut"></aop:before>
- <aop:after-returning method="after" pointcut-ref="myPointCut"/>
- </aop:aspect>
- </aop:config>
- </beans>
上面的配置我们在注释中写的很清楚了. 这里我们重点讲解一下:
1, 切入点表达式, 一个完整的方法表示如下:
execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
类修饰符 返回值 方法所在的包 方法名 方法抛出的异常
2,springAOP 的具体加载步骤:
1, 当 spring 容器启动的时候, 加载了 spring 的配置文件
2, 为配置文件中的所有 bean 创建对象
3,spring 容器会解析 aop:config 的配置
1, 解析切入点表达式, 用切入点表达式和纳入 spring 容器中的 bean 做匹配
如果匹配成功, 则会为该 bean 创建代理对象, 代理对象的方法 = 目标方法 + 通知
如果匹配不成功, 不会创建代理对象
4, 在客户端利用 context.getBean() 获取对象时, 如果该对象有代理对象, 则返回代理对象; 如果没有, 则返回目标对象
说明: 如果目标类没有实现接口, 则 spring 容器会采用 cglib 的方式产生代理对象, 如果实现了接口, 则会采用 jdk 的方式
来源: http://www.bubuko.com/infodetail-3099917.html