咱们有一个Pay(接口) 而后两个实现类DollarPay和RmbPay,都须要重写pay()方法, 这时咱们须要对pay方法进行性能监控,日志的添加等等怎么作?java
对每一个字符方法均作日志代码的编写处理,以下面方式spring
缺点: 代码重复太多, 添加的日志代码耦合度过高(若是须要更改日志记录代码功能需求,类中方法须要所有改动,工程量浩大)express
装饰器模式:动态地给一个对象添加一些额外的职责。编程
代理模式:以上刚讲过。因而得出如下结构:安全
仔细考虑事后发现虽然对原有内部代码没有进行改动,对于每一个类作日志处理,并引用目标类,可是若是待添加日志的业务类的数量不少,此时手动为每一个业务类实现一个装饰器或建立对应的代理类,同时代码的耦合度也加大,需求一旦改变,改动的工程量也是可想而知的。ide
有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方可以实现代码的复用,达到松耦合的同时,又可以完美完成功能?oop
答案是确定的,存在这样的技术,aop已经对其提供了完美的实现!性能
若有疑问,可加入群:10803-55292,输入暗号13,便可有大佬帮助代理
Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来讲,Aop关注的再也不是程序代码中某个类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入,因此称之为切面。联想你们吃的汉堡(中间夹肉)。那么aop是怎么作到拦截整个面的功能呢?考虑前面学到的servlet filter /* 的配置 ,实际上也是aop 的实现。日志
AOP主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。
1. 下降模块与模块之间的耦合度,提升业务代码的聚合度。(高内聚低耦合)
2. 提升了代码的复用性。
3. 提升系统的扩展性。(高版本兼容低版本)
4. 能够在不影响原有的功能基础上添加新的功能
动态代理(JDK + CGLIB)
被拦截到的每一个点,spring中指被拦截到的每个方法,spring aop一个链接点即表明一个方法的执行。
对链接点进行拦截的定义(匹配规则定义 规定拦截哪些方法,对哪些方法进行处理),spring 有专门的表达式语言定义。
拦截到每个链接点即(每个方法)后所要作的操做
切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要作什么,切面则是横切关注点的抽象,与类类似,类是对物体特征的抽象,切面则是横切关注点抽象。
被代理的目标对象
将切面应用到目标对象并生成代理对象的这个过程即为织入
在不修改原有应用程序代码的状况下,在程序运行期为类动态添加方法或者字段的过程称为引入
<!--Spring AOP--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
添加命名空间
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; } }
<!--配置AOP代理--> <aop:aspectj-autoproxy/>
** * 切面 * 切入点和通知的抽象 (与面向对象中的 类 类似) * 定义 切入点和通知 (切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要作什么) */ @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; } }
<!--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>
静态代理:手动为目标对象制做代理对象,即在程序编译阶段完成代理对象的建立
动态代理:在程序运行期动态建立目标对象对应代理对象。
jdk动态代理:被代理目标对象必须实现某一或某一组接口 实现方式 经过回调建立代理对象。
cglib 动态代理:被代理目标对象能够没必要实现接口,继承的方式实现。
动态代理相比较静态代理,提升开发效率,能够批量化建立代理,提升代码复用率。