前言
因为假期起因,有一段时间没给大家更新了!和大家说个事吧,放假的时候一位粉丝和我说了下本人的被虐经验,在假期前他去某互联网公司面试,后果间接被人家面试官 Spring AOP 三连问给问的一脸懵逼!其实我觉着吧,这玩意不是挺简略的吗?
大家在学习 AOP 之前,如果分明代理模式的话,则学习起来十分轻松,接下来就由我为大家介绍 AOP 这个重要的知识点!
代理模式
代理模式在 Java 开发中是一种比拟常见的设计模式。设计目的旨在为服务类与客户类之间插入其余性能,插入的性能对于调用者是通明的,起到假装管制的作用。如租房的例子:房客、中介、房东。对应于代理模式中即:客户类、代理类、委托类(被代理类)。
为某一个对象(委托类)提供一个代理(代理类),用来管制对这个对象的拜访。委托类和代理类有一个独特的父类或父接口。代理类会对申请做预处理、过滤,将申请调配给指定对象。
生存中常见的代理状况:租房中介、婚庆公司等
代理模式的两个设计准则:
- 代理类与委托类具备类似的行为(独特)
- 代理类加强委托类的行为
罕用的代理模式:
- 动态代理
- 动静代理
动态代理
某个对象提供一个代理,代理角色固定,以管制对这个对象的拜访。代理类和委托类有独特的父类或父接口,这样在任何应用委托类对象的中央都能够用代理对象代替。代理类负责申请的预处理、过滤、将申请分派给委托类解决、以及委托类执行完申请后的后续解决。
代理的三要素
- 有独特的行为(结婚)– 接口
- 指标角色(新人)– 实现行为
- 代理角色(婚庆公司)– 实现行为 加强指标对象行为
动态代理的特点
- 指标角色固定
- 在应用程序执行前就失去指标角色
- 代理对象会加强指标对象的行为
- 有可能存在多个代理,引起 ” 类爆炸 ”(毛病)
动态代理的实现
定义行为(独特)定义接口
/**
* 定义⾏为
*/
public interface Marry {public void toMarry();
}
指标对象(实现行为)
/**
* 动态代理 ——> ⽬标对象
*/
public class You implements Marry {
// 实现⾏为
@Override
public void toMarry() {System.out.println("我要结婚了...");
}
}
代理对象(实现行为、加强指标对象的行为)
/**
* 动态代理 ——> 代理对象
*/
public class MarryCompanyProxy implements Marry {
// ⽬标对象
private Marry marry;
// 通过结构器将⽬标对象传⼊
public MarryCompanyProxy(Marry marry) {this.marry = marry;}
// 实现⾏为
@Override
public void toMarry() {
// 加强⾏为
before();
// 执⾏⽬标对象中的⽅法
marry.toMarry();
// 加强⾏为
after();}
/**
* 加强⾏为
*/
private void after() {System.out.println("新婚高兴,早⽣贵⼦!");
}
/**
* 加强⾏为
*/
private void before() {System.out.println("场地正在安排中...");
}
}
通过代理对象实现目标对象的性能
// ⽬标对象
You you = new You();
// 结构代理⻆⾊同时传⼊实在⻆⾊
MarryCompanyProxy marryCompanyProxy = new MarryCompanyProxy(you);
// 通过代理对象调⽤⽬标对象中的⽅法
marryCompanyProxy.toMarry();
动态代理对于代理的角色是固定的,如 dao 层有 20 个 dao 类,如果要对办法的拜访权限进行代理,此时须要创立 20 个动态代理角色,引起类爆炸,无奈满足生产上的须要,于是就催生了动静代理的思维。
动静代理
相比于动态代理,动静代理在创立代理对象上更加的灵便,动静代理类的字节码在程序运行时,由 Java 反射机制动静产生。它会依据须要,通过反射机制在程序运行期,动静的为指标对象创立代理对象,无需程序员手动编写它的源代码。动静代理不仅简化了编程工作,二且进步了软件系统的可扩展性,因为反射机制能够生成任意类型的动静代理类。代理的行为能够代理多个办法,即满足生产须要的同时又达到代码通用的目标。
动静代理的两种实现形式:
- JDK 动静代理
- CGLIB 动静代理
动静代理的特点
- 指标对象不固定
- 在应用程序执行时动态创建指标对象
- 代理对象会加强指标对象的行为
JDK 动静代理
注:JDK 动静代理的指标对象必须有接口实现
newProxyInstance
Proxy 类:
Proxy 类是专门实现代理的操作类,能够通过此类为一个或多个接口动静地生成实现类,此类提供了如下操作方法:
/*
返回⼀个指定接⼝的代理类的实例⽅法调⽤分派到指定的调⽤处理程序。(返回代理对象)
loader:⼀个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对⽣成的代理对象进⾏加载
interfaces:⼀个 Interface 对象的数组,示意的是我将要给我须要代理的对象提供⼀组什么接⼝,如果
我提供了⼀组接⼝给它,那么这个代理对象就声称实现了该接⼝ (多态),这样我就能调⽤这组接⼝中的⽅法了
h:⼀个 InvocationHandler 接⼝,示意代理实例的调⽤处理程序实现的接⼝。每个代理实例都具备⼀个关联
的调⽤处理程序。对代理实例调⽤⽅法时,将对⽅法调⽤进⾏编码并将其指派到它的调⽤处理程序的 invoke ⽅法(传⼊ InvocationHandler 接⼝的⼦类)*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
获取代理对象
public class JdkHandler implements InvocationHandler {
// ⽬标对象
private Object target; // ⽬标对象的类型不固定,创立时动静⽣成
// 通过结构器将⽬标对象赋值
public JdkHandler(Object target) {this.target = target;}
/**
* 1、调⽤⽬标对象的⽅法(返回 Object)* 2、加强⽬标对象的⾏为
* @param proxy 调⽤该⽅法的代理实例
* @param method ⽬标对象的⽅法
* @param args ⽬标对象的⽅法形参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// 加强⾏为
System.out.println("============== ⽅法前执⾏");
// 调⽤⽬标对象的⽅法(返回 Object)Object result = method.invoke(target,args);
// 加强⾏为
System.out.println("⽅法后执⾏ ==============");
return result;
}
/**
* 失去代理对象
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* loader:类加载器
* interfaces:接⼝数组
* h:InvocationHandler 接⼝ (传⼊ InvocationHandler 接⼝的实现类)
*
*
* @return
*/
public Object getProxy() {
return
Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterface
s(),this);
}
}
通过代理对象实现目标对象的性能
// ⽬标对象
You you = new You();
// 获取代理对象
JdkHandler jdkHandler = new JdkHandler(you);
Marry marry = (Marry) jdkHandler.getProxy();
// 通过代理对象调⽤⽬标对象中的⽅法
marry.toMarry();
问:Java 动静代理类中的 invoke 是怎么调用的?
答:在生成的动静代理类 $Proxy0.class 中,构造方法调用了父类 Proxy.class 的构造方法,给成员变量 invocationHandler 赋值,$Proxy0.class 的 static 模块中创立了被代理类的办法,调用相应办法时办法体中调用了父类中的成员变量 InvocationHandler 的 invoke () 办法。
注:JDK 的动静代理依附接口实现,如果有些类并没有接口实现,则不能应用 JDK 代理。
CGLIB 动静代理
JDK 的动静代理机制只能代理实现了接口的类,而不能实现接口的类就不能应用 JDK 的动静代理,cglib 是针对类来实现代理的,它的原理是对指定的指标类生成一个子类,并笼罩其中办法实现加强,但因为采纳的是继承,所以不能对 final 润饰的类进行代理。
增加依赖
在 pom.xml 文件中引入 cglib 的相干依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
定义类
实现 MethodInterceptor 接口
public class CglibInterceptor implements MethodInterceptor {
// ⽬标对象
private Object target;
// 通过结构器传⼊⽬标对象
public CglibInterceptor(Object target) {this.target = target;}
/**
* 获取代理对象
* @return
*/
public Object getProxy() {// 通过 Enhancer 对象的 create() ⽅法能够⽣成⼀个类,⽤于⽣成代理对象
Enhancer enhancer = new Enhancer();
// 设置⽗类 (将⽬标类作为其⽗类)
enhancer.setSuperclass(target.getClass());
// 设置拦截器 回调对象为自身对象
enhancer.setCallback(this);
// ⽣成⼀个代理类对象,并返回
return enhancer.create();}
/**
* 拦截器
* 1、⽬标对象的⽅法调⽤
* 2、加强⾏为
* @param object 由 CGLib 动静⽣成的代理类实例
* @param method 实体类所调⽤的被代理的⽅法引⽤
* @param objects 参数值列表
* @param methodProxy ⽣成的代理类对⽅法的代理引⽤
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 加强⾏为
System.out.println("============== ⽅法前执⾏");
// 调⽤⽬标对象的⽅法(返回 Object)Object result = methodProxy.invoke(target,objects);
// 加强⾏为
System.out.println("⽅法后执⾏ ==============");
return result;
}
}
调用办法
// ⽬标对象
You you = new You();
CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
Marry marry = (Marry) cglibInterceptor.getProxy();
marry.toMarry();
User user = new User();
CglibInterceptor cglibInterceptor = new CglibInterceptor(user);
User u = (User) cglibInterceptor.getProxy();
u.test();
JDK 代理与 CGLIB 代理的区别
- JDK 动静代理实现接口,Cglib 动静代理继承思维
- JDK 动静代理(指标对象存在接口时)执行效率高于 Ciglib
- 如果指标对象有接口实现,抉择 JDK 代理,如果没有接口实现抉择 Cglib 代理
Spring AOP
日志解决带来的问题
咱们有一个 Pay (接口) 而后两个实现类 DollarPay 和 RmbPay,都须要重写 pay () 办法, 这时咱们须要对 pay 办法进行性能监控,日志的增加等等怎么做?
最容易想到的办法
对每个字符办法均做日志代码的编写解决,如上面形式
毛病: 代码反复太多, 增加的日志代码耦合度太高(如果须要更改日志记录代码性能需要,类中办法须要全副改变,工程量盛大)
应用装璜器模式 / 代理模式改良解决方案
装璜器模式:动静地给一个对象增加一些额定的职责。
代理模式:以上刚讲过。于是得出以下构造:
认真思考过后发现尽管对原有外部代码没有进行改变, 对于每个类做日志解决,并援用指标类,然而如果待增加日志的业务类的数量很多,此时手动为每个业务类实现一个装璜器或创立对应的代理类,同时代码的耦合度也加大,需要一旦扭转,改变的工程量也是可想而知的。
有没有更好的解决方案,只有写一次代码,对想要增加日志记录的中央可能实现代码的复用,达到松耦合的同时,又可能完满实现性能?
答案是必定的,存在这样的技术,aop 曾经对其提供了完满的实现!
什么是 AOP?
Aspect Oriented Programing 面向切面编程,相比拟 oop 面向对象编程来说,Aop 关注的不再是程序代码中某个类,某些办法,而 aop 思考的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(两头夹肉)。那么 aop 是怎么做到拦挡整个面的性能呢?思考后面学到的 servlet filter /* 的配置,实际上也是 aop 的实现。
AOP 能做什么?
AOP 次要利用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。
AOP 的特点
- 升高模块与模块之间的耦合度,进步业务代码的聚合度。(高内聚低耦合)
- 进步了代码的复用性
- 进步了代码的复用性
- 能够在不影响原有的性能根底上增加新的性能
AOP 的底层实现
动静代理(JDK + CGLIB)
AOP 基本概念
被拦挡到的每个点,spring 中指被拦挡到的每一个办法,spring aop 一个连接点即代表一个办法的执行。
Pointcut(切入点)
对连接点进行拦挡的定义(匹配规定定义 规定拦挡哪些办法,对哪些办法进行解决),spring 有专门的表达式语言定义。
Advice(告诉)
拦挡到每一个连接点即(每一个办法)后所要做的操作
- 前置告诉(前置加强)— before() 执行办法前告诉
- 返回告诉(返回加强)— afterReturn 办法失常完结返回后的告诉
- 异样抛出告诉(异样抛出加强)— afetrThrow()
- 最终告诉 — after 无论办法是否产生异样,均会执行该告诉。
- 盘绕告诉 — around 突围一个连接点(join point)的告诉,如办法调用。这是最弱小的一种告诉类型。盘绕告诉能够在办法调用前后实现自定义的行为。它也会抉择是否继续执行连接点或间接返回它们本人的返回值或抛出异样来完结执行。
Aspect(切面)
切入点与告诉的联合,决定了切面的定义,切入点定义了要拦挡哪些类的哪些办法,告诉则定义了拦挡过办法后要做什么,切面则是横切关注点的形象,与类类似,类是对物体特色的形象,切面则是横切关注点形象。
Target(指标对象)
被代理的指标对象
Weave(织入)
将切面利用到指标对象并生成代理对象的这个过程即为织入
Introduction(引入)
在不批改原有利用程序代码的状况下,在程序运行期为类动静增加办法或者字段的过程称为引入
Spring AOP 的实现
Spring AOP 环境搭建
坐标依赖引入
<!--Spring AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
增加 spring.xml 的配置
增加命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
注解实现
定义切面
/**
* 切⾯
* 切⼊点和告诉的形象(与⾯向对象中的 类 类似)* 定义 切⼊点和告诉(切⼊点定义了要拦挡哪些类的哪些⽅法,告诉则定义了拦挡过⽅法后要做什么)*/
@Component // 将对象交给 IOC 容器去实例化
@Aspect // 申明以后类是⼀个切⾯
public class LogCut {
/**
* 切⼊点:* 匹配规定。规定什么⽅法被拦挡、须要解决什么⽅法
* 定义切⼊点
* @Pointcut("匹配规定")
*
* Aop 切⼊点表达式简介
* 1. 执⾏任意公共⽅法:* execution(public *(..))
* 2. 执⾏任意的 set ⽅法
* execution(* set*(..))
* 3. 执⾏ com.xxxx.service 包下任意类的任意⽅法
* execution(* com.xxxx.service.*.*(..))
* 4. 执⾏ com.xxxx.service 包 以及⼦包下任意类的任意⽅法
* execution(* com.xxxx.service..*.*(..))
*
* 注:表达式中的第⼀个 * 代表的是⽅法的润饰范畴
* 可选值:private、protected、public(* 示意所有范畴)*/
@Pointcut("execution (* com.xxxx.service..*.*(..) )")
public void cut(){}
/**
* 申明前置告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法执⾏前 执⾏该告诉
*
*/
@Before(value = "cut()")
public void before() {System.out.println("前置告诉.....");
}
/**
* 申明返回告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异样)执⾏后 执⾏该告诉
*
*/
@AfterReturning(value = "cut()")
public void afterReturn() {System.out.println("返回告诉.....");
}
/**
* 申明最终告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异样或有异样)执⾏后 执⾏该告诉
*
*/
@After(value = "cut()")
public void after() {System.out.println("最终告诉.....");
}
/**
* 申明异样告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法出现异常时 执⾏该告诉
*/
@AfterThrowing(value="cut()",throwing = "e")
public void afterThrow(Exception e) {System.out.println("异样告诉....." + "异样起因:" + e.getCause());
}
/**
* 申明盘绕告诉 并将告诉应⽤到切⼊点上
* ⽅法执⾏前后 通过盘绕告诉定义相应解决
* 须要通过显式调⽤对应的⽅法,否则⽆法拜访指定⽅法 (pjp.proceed();)
* @param pjp
* @return
*/
@Around(value = "cut()")
public Object around(ProceedingJoinPoint pjp) {System.out.println("前置告诉...");
Object object = null;
try {object = pjp.proceed();
System.out.println(pjp.getTarget() + "======" + pjp.getSignature());
// System.out.println("返回告诉...");
} catch (Throwable throwable) {throwable.printStackTrace();
System.out.println("异样告诉...");
}
System.out.println("最终告诉...");
return object;
}
}
配置文件(spring.xml)
<!-- 配置 AOP 代理 -->
<aop:aspectj-autoproxy/>
XML 实现
定义切面
**
* 切⾯
* 切⼊点和告诉的形象(与⾯向对象中的 类 类似)* 定义 切⼊点和告诉(切⼊点定义了要拦挡哪些类的哪些⽅法,告诉则定义了拦挡过⽅法后要做什么)*/
@Component // 将对象交给 IOC 容器去实例化
public class LogCut02 {public void cut(){}
/**
* 申明前置告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法执⾏前 执⾏该告诉
*/
public void before() {System.out.println("前置告诉.....");
}
/**
* 申明返回告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异样)执⾏后 执⾏该告诉
*
*/
public void afterReturn() {System.out.println("返回告诉.....");
}
/**
* 申明最终告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法(⽆异样或有异样)执⾏后 执⾏该告诉
*
*/
public void after() {System.out.println("最终告诉.....");
}
/**
* 申明异样告诉 并将告诉应⽤到定义的切⼊点上
* ⽬标类⽅法出现异常时 执⾏该告诉
*/
public void afterThrow(Exception e) {System.out.println("异样告诉....." + "异样起因:" + e.getCause());
}
/**
* 申明盘绕告诉 并将告诉应⽤到切⼊点上
* ⽅法执⾏前后 通过盘绕告诉定义相应解决
* 须要通过显式调⽤对应的⽅法,否则⽆法拜访指定⽅法 (pjp.proceed();)
* @param pjp
* @return
*/
public Object around(ProceedingJoinPoint pjp) {System.out.println("前置告诉...");
Object object = null;
try {object = pjp.proceed();
System.out.println(pjp.getTarget() + "======" + pjp.getSignature());
// System.out.println("返回告诉...");
} catch (Throwable throwable) {throwable.printStackTrace();
System.out.println("异样告诉...");
}
System.out.println("最终告诉...");
return object;
}
}
配置文件(spring.xml)
<!--aop 相干配置 -->
<aop:config>
<!--aop 切⾯ -->
<aop:aspect ref="logCut02">
<!-- 定义 aop 切⼊点 -->
<aop:pointcut id="cut" expression="execution(* com.xxxx.service..*.*(..))"/>
<!-- 配置前置告诉 指定前置告诉⽅法名 并引⽤切⼊点定义 -->
<aop:before method="before" pointcut-ref="cut"/>
<!-- 配置返回告诉 指定返回告诉⽅法名 并引⽤切⼊点定义 -->
<aop:after-returning method="afterReturn" pointcut-ref="cut"/>
<!-- 配置异样告诉 指定异样告诉⽅法名 并引⽤切⼊点定义 -->
<aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="cut"/>
<!-- 配置最终告诉 指定最终告诉⽅法名 并引⽤切⼊点定义 -->
<aop:after method="after" pointcut-ref="cut"/>
<!-- 配置盘绕告诉 指定盘绕告诉⽅法名 并引⽤切⼊点定义 -->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
Spring AOP 总结
代理模式实现三要素
- 接口定义
- 指标对象与代理对象必须实现对立接口
- 代理对象持有指标对象的援用,加强指标对象行为
代理模式实现分类以及对应区别
- 动态代理:手动为指标对象制作代理对象,即在程序编译阶段实现代理对象的创立
- 动静代理:在程序运行期动态创建指标对象对应代理对象。
- jdk 动静代理:被代理指标对象必须实现某一或某一组接口实现形式通过回调创立代理对象。
- cglib 动静代理:被代理指标对象能够不用实现接口,继承的形式实现
动静代理相比拟动态代理,进步开发效率,能够批量化创立代理,进步代码复用率。
Aop 了解
- 面向切面,相比 oop 关注的是代码中的层或面
- 解耦,进步零碎扩展性
- 进步代码复用
Aop 关键词
- 连接点:每一个办法
- 切入点:匹配的办法汇合
- 切面:连接点与切入点的汇合决定了切面,横切关注点的形象
- 告诉:几种告诉
- 指标对象:被代理对象
- 织入:程序运行期将切面利用到指标对象并生成代理对象的过程
- 引入:在不批改原始代码状况下,在程序运行期为程序动静引入办法或字段的过程
最初
感激你看到这里,文章有什么有余还请斧正,感觉文章对你有帮忙的话记得给我点个赞!