Spring(4)AOP

1、AOP概述

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的

基础上,对我们的已有方法进行增强。

作用:

在程序运行期间,不修改源码对已有方法进行增强。

优势:

减少重复代码、提高开发效率、维护方便

2、动态代理

2.1、动态代理的特点

字节码随用随创建,随用随加载。

它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。

装饰者模式就是静态代理的一种体现。

2.2、动态代理的两种方式

基于接口的动态代理

提供者:JDK 官方的 Proxy 类。

要求:被代理类最少实现一个接口。

基于子类的动态代理

提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。

要求:被代理类不能用 final 修饰的类(最终类)。

2.2.1、基于接口的动态代理

被代理的类

public interface IProducer {
public void saleProduct(float money); public void afterService(float money);
} ============================================ public class Producer implements IProducer {
public void saleProduct(float money){
System.out.println("售出产品,价格:" + money);
} public void afterService(float money){
System.out.println("提供售后服务,价格:" + money);
}
}

使用 JDK 官方的 Proxy

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; public class client {
public static void main(String[] args) {
final Producer producer = new Producer();
//producer.saleProduct(10000f); /**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 资源:在不修改源码的情况下对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口:
* 涉及到类:proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用proxy中的newPoxyInstance方法
* 创建代理对象的要求:
* 被代理对象必须有接口,如果没有则不能使用
* newProxyInstance参数:
* classLoader:类加载器
* 用于加载代理对象的字节码,和被代理对象使用相同的类加载器。固定写法
* Class[]:字节码数组
* 让代理对象和被代理对象拥有相同方法。写法固定
* InvocationHandler:用于增强的代码
* 它让我们写如何代理,一般写该接口的实现类,通常情况是匿名内部类,但不是必须的
* 此接口谁用谁写
*/
IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader()
, producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行的被代理对象的方法都会经过此方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 被代理对象方法的参数
* @return 被代理对象方法的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
Float money = (Float)args[0];
if("saleProduct".equals(method.getName())){
rtValue = method.invoke(producer, money * 0.8f);
}
return rtValue;
}
});
proxyProduec.saleProduct(10000f);
} }

22.2、基于子类的动态代理

被代理的类


public class Producer {
public void saleProduct(float money){
System.out.println("售出产品,价格:" + money);
} public void afterService(float money){
System.out.println("提供售后服务,价格:" + money);
}
}

使用 CGLib 的 的 Enhancer

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method; public class client {
public static void main(String[] args) {
final Producer producer = new Producer();
//producer.saleProduct(10000f); /**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 资源:在不修改源码的情况下对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口:
* 涉及到类:Enhancer
* 提供者:第三方
* 如何创建代理对象:
* 使用Enhancer中的create方法
* 创建代理对象的要求:
* 代理类不是最终类
* create参数:
* Class:字节码
* 指定被代理对象的字节码
* Callback:提供增强代码
* 写如何代理,一般是写一个该接口实现类
* 此接口谁用谁写
* 一般写的是该接口的子接口实现类MethodInterceptor
*/
Producer p = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 作用:执行的被代理对象的方法都会经过此方法
* @param obj
* @param method
* @param args
* 以上三个参数与基于接口的代理作用相同
* @param proxy 当前方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object rtValue = null;
Float money = (Float)args[0];
if("saleProduct".equals(method.getName())){
rtValue = method.invoke(producer, money * 0.8f);
}
return rtValue;
}
});
p.saleProduct(10000f);
} }

3、spring中的AOP

3.1、AOP相关术语

Joinpoint( 连接点):

所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。

Pointcut( 切入点):

所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。

Advice( 通知/ 增强):

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

Introduction( 引介):

引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。

Target( 目标对象):

代理的目标对象。

Weaving( 织入):

是指把增强应用到目标对象来创建新的代理对象的过程。

spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

Proxy (代理):

一个类被 AOP 织入增强后,就产生一个结果代理类。

Aspect( 切面):

是切入点和通知(引介)的结合。

3.2、AOP的工作流程

开发阶段(我们做的)

编写核心业务代码,把公用代码抽取出来,制作成通知。在配置文件中,声明切入点与通知间的关系,即切面。

运行阶段(Spring 框架完成的)

Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

关于代理的选择

在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

3.3、基于 XML 的 的 AOP

基于案例描述

案例:在service类的方法执行时加入日志

service类

public interface IAccountService {
/**
* 保存账户
*/
void saveAccount(); /**
* 更新账户
* @param i
* @return
*/
void updateAccount(int i); /**
* 删除账户
* @return
*/
int deleteAccount();
} =================================== public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
} public void updateAccount(int i) {
System.out.println("执行了更新" + i);
} public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}

Logger日志

/**
* 用于记录日志的工具类
*/
public class Logger { public void printLog(){
System.out.println("Logger类中的printLog执行日志记录。。。");
}
}

配置文件

<?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: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">
<!--配置spring的IOC把service对象配置-->
<bean id="accountService" class="wf.service.impl.AccountServiceImpl"></bean> <!--spring开启基于xml的aop配置 1.把通知bean配置
2.使用aop:config配置表明开始配置aop
3.使用aop:aspect标签表明配置切面
id属性:给切面一个唯一标识
ref属性:指定通知类的bean
4.在aop:aspect标签内部使用对应的标签来配置通知类型
示例是让pringLog方法在切入点方法前执行所以使用前置通知
aop:before:表明前置通知
method属性:用于指定使用logger类中哪一个方法作为前置通知
pointcut属性:用于指定切入点表达式,该表达式含义指对业务层哪一个方法增强 切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
标准表达式的写法:
public void wf.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void wf.service.impl.AccountServiceImpl.saveAccount()
返回值可以用通配符,表示任意返回值
* wf.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符表示任意包,但有几级包就用几个*.
* *.*.*.AccountServiceImpl.saveAccount()
包名可以再升级,使用..表示当前包和其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*进行通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
* *..*.*(int)
可以使用通配符表示任意类型,但方法必须有参数
* *..*.*(*)
可以使用..表示任意类型,且有无参数均可
* *..*.*(..)
全通配写法:
* *..*.*(..)
实际开发切入点的通常写法
切入到业务层实现类下的所有方法
* wf.service.impl.*.*(..)
-->
<bean id="logger" class="wf.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut="execution(* wf.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config> </beans>

3.3.1、AOP中通知的配置

import org.aspectj.lang.ProceedingJoinPoint;

/**
* 用于记录日志的工具类
*/
public class Logger { /**
* 前置通知
*/
public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog执行日志记录。。。");
} /**
* 后置通知
*/
public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog执行日志记录。。。");
} /**
* 异常通知
*/
public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog执行日志记录。。。");
} /**
* 最终通知
*/
public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog执行日志记录。。。");
} /**
* 环绕通知
* 当配置了环绕通知,切入点方法没有执行,而只通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知,发现动态代理的环绕通知有明确的切入点方法调用,而现在写的方法没有
* 解决:
* spring框架提供了一个接口:ProceedingJoinPoint 接口,该接口有一个procced()方法,改方法明确了切入点方法调用
* 该接口可作为切入点方法的参数,在实际执行中spring框架会通过该接口的实现类供我们使用
* spring框架中的环绕通知:
* 它是spring框架提供的一种可以在代码中手动控制增强方法何时执行的方式
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();//获取要执行方法的参数
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。前置");
rtValue = pjp.proceed(args);//执行切入点方法
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。最终");
} }
}

配置文件

<?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: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">
<!--配置spring的IOC把service对象配置-->
<bean id="accountService" class="wf.service.impl.AccountServiceImpl"></bean> <bean id="logger" class="wf.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切入点表达式,id属性指定表达式的唯一标识。expression属性用于指定表达式内容
该标签写在aop:aspect标签内部时只能用在当前切面使用
它还可以写在aop:aspect标签的外部,就变成了所有切面都可以用
注意:改标签写在aop:aspect标签内部时按照约束只能写在最前面
-->
<aop:pointcut id="pt" expression="execution(* wf.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger"> <!--前置通知:在切入点方法前执行
<aop:before method="beforePrintLog" pointcut-ref="pt"></aop:before>--> <!--后置通知:在切入点方法正常执行后执行。它和异常通知只能执行一个
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt"></aop:after-returning>--> <!--异常通知:在切入点方法方式异常后执行
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt"></aop:after-throwing>--> <!--最终通知:无论切入点方法是否正常执行,它都会在最后执行
<aop:after method="afterPrintLog" pointcut-ref="pt"></aop:after>--> <!--配置环绕通知 详解在Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt"></aop:around>
</aop:aspect>
</aop:config> </bean

3.3、基于注解的配置

被代理类

package wf.service.impl;

import org.springframework.stereotype.Service;
import wf.service.IAccountService; /**
* 账户的服务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
public void saveAccount() { System.out.println("执行了保存");
// int i = 1/0;
} public void updateAccount(int i) {
System.out.println("执行了更新" + i);
} public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}

切面类

package wf.utils;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component; /**
* 用于记录日志的工具类
*/
@ComponentScan("wf")
@EnableAspectJAutoProxy
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger { @Pointcut("execution(* wf.service.impl.*.*(..))")
public void pt(){} /**
* 前置通知
*/
@Before("pt()")
public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog执行日志记录。。。");
} /**
* 后置通知
*/
@AfterReturning("pt()")
public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog执行日志记录。。。");
} /**
* 异常通知
*/
@AfterThrowing("pt()")
public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog执行日志记录。。。");
} /**
* 最终通知
*/
@After("pt()")
public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog执行日志记录。。。");
} /**
* 环绕通知
* 当配置了环绕通知,切入点方法没有执行,而只通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知,发现动态代理的环绕通知有明确的切入点方法调用,而现在写的方法没有
* 解决:
* spring框架提供了一个接口:ProceedingJoinPoint 接口,该接口有一个procced()方法,改方法明确了切入点方法调用
* 该接口可作为切入点方法的参数,在实际执行中spring框架会通过该接口的实现类供我们使用
* spring框架中的环绕通知:
* 它是spring框架提供的一种可以在代码中手动控制增强方法何时执行的方式
*/
@Around("pt()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
Object[] args = pjp.getArgs();//获取要执行方法的参数
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。前置");
rtValue = pjp.proceed(args);//执行切入点方法
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog执行日志记录。。。最终");
} }
}

测试类

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wf.service.IAccountService;
import wf.utils.Logger; public class AopTest {
public static void main(String[] args) {
//1.获取容器
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(Logger.class);
//2.获取容器对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
//3.执行方法
as.saveAccount();
// as.updateAccount(3);
// as.deleteAccount();
}
}

SSM框架之Spring(4)AOP的更多相关文章

  1. 【SSM框架】Spring + Springmvc + Mybatis 基本框架搭建集成教程

    本文将讲解SSM框架的基本搭建集成,并有一个简单demo案例 说明:1.本文暂未使用maven集成,jar包需要手动导入. 2.本文为基础教程,大神切勿见笑. 3.如果对您学习有帮助,欢迎各种转载,注 ...

  2. java web后台开发SSM框架(Spring+SpringMVC+MyBaitis)搭建与优化

    一.ssm框架搭建 1.1创建项目 新建项目后规划好各层的包. 1.2导入包 搭建SSM框架所需包百度云链接:http://pan.baidu.com/s/1cvKjL0 1.3整合spring与my ...

  3. SSM框架-----------SpringMVC+Spring+Mybatis框架整合详细教程

    1.基本概念 1.1.Spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One  ...

  4. 2018用IDEA搭建SSM框架(Spring+SpringMVC+Mybatis)

    使用IDEA搭建ssm框架 环境 工具:IDEA 2018.1 jdk版本:jdk1.8.0_171 Maven版本:apache-maven-3.5.3 Tomcat版本:apache-tomcat ...

  5. 浅谈IDEA集成SSM框架(SpringMVC+Spring+MyBatis)

    前言 学习完MyBatis,Spring,SpringMVC之后,我们需要做的就是将这三者联系起来,Spring实现业务对象管理,Spring MVC负责请求的转发和视图管理, MyBatis作为数据 ...

  6. SSM框架之spring(1)

    spring(1) 1.spring概述 Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP( ...

  7. 一步步教你整合SSM框架(Spring MVC+Spring+MyBatis)详细教程重要

    前言 SSM(Spring+SpringMVC+Mybatis)是目前较为主流的企业级架构方案,不知道大家有没有留意,在我们看招聘信息的时候,经常会看到这一点,需要具备SSH框架的技能:而且在大部分教 ...

  8. ssm框架(Spring Springmvc Mybatis框架)整合及案例增删改查

    三大框架介绍 ssm框架是由Spring springmvc和Mybatis共同组成的框架.Spring和Springmvc都是spring公司开发的,因此他们之间不需要整合.也可以说是无缝整合.my ...

  9. SSM框架整合(Spring+SpringMVC+Mybatis)

    第一步:创建maven项目并完善项目结构  第二步:相关配置 pom.xml 引入相关jar包 1 <properties> 2 <project.build.sourceEncod ...

  10. Maven+SSM框架(Spring+SpringMVC+MyBatis)(二)

    1.基本概念 2.开发环境搭建 3.Maven Web项目创建 4.SSM整合 此次整合我分两个配置文件: 1)分别是spring-mybatis.xml,包含spring和mybatis的配置文件, ...

随机推荐

  1. angular8 导出excel文件

    angular package 1.xlsx npm install xlsx --save 2.file-saver npm install file-saver --save npm instal ...

  2. Android 避免内存泄漏

    什么是内存泄露? 就是该回收的内存由于种种原因没有被回收,还驻留在内存中. 内存泄露有什么影响? 可能一处小小的内存泄露就会导致整个应用卡顿,甚至崩溃. 例子说明: Toast.makeText(Ma ...

  3. MTDDL 美团分布式数据访问中间件(转)

    MTDDL 美团分布式数据访问中间件(转) 原文地址:MTDDL--美团点评分布式数据访问层中间件 因原文文字和图显示有问题,故整理于此,仅供参考. 业界方案 组件 简介 Atlas Qihoo 36 ...

  4. Linux - CentOS 7 安装 .Net Core 运行环境

    阿里云的CentOS 7.7 64位,所需要的环境:MySql 5.7,.Net Core 2.2 ,Nginx 我这里用的 Xshell 工具,首先用root进入系统 版本信息 打开终端输入命令: ...

  5. python中基本类型的连接组合和互相转换13种方式

    本篇总结了一下字符串,列表,字典,元组的连接组合使用和类型的互相转换小例子,尤其列表中的extend()方法和字典中的 update方法非常的常用. 1.连接两个字符串 a = "hello ...

  6. jQuery随笔记录

            DOM遍历 parent()方法返回所选元素的直接父元素.(parent() 只能遍历单个级别的 DOM树) parents()方法获取所选元素的所有祖先 children()所选元素 ...

  7. Mac Electron App 签名后打开闪退

    背景 昨天在测试 Mac Electron App 打包,发现不签名的应用能够正常打开,签了名的打开反而会崩溃. 寻因 首先我怀疑是不是自己代码导致闪退,但是在一番查找后,发现还根本没到执行我的代码就 ...

  8. 如何实现用户的历史记录功能(最多n条)

    使用容量为n的队列存储历史记录 使用标准库collections中的deque,它是一个双端循环队列 from collections import deque q = deque([], 5) #参 ...

  9. Dotnetcore或owin程序启用SSL的方法

    https端口需要绑定SSL证书 操作方法与步骤如下: 在IIS中创建证书 查看证书的指纹 使用命令行绑定端口与证书 上述第三步也可以更换为创建一个新的空网站,绑定https端口为相同端口并绑定证书, ...

  10. Pinpoint-agent监控springboot编译的jar启动方式

    由于springboot在打包发版时已经将tomcat容器内嵌到jar文件中,可以通过以下命令来使pinpoint-agent监控生成的jar服务 java -javaagent:D:\Softwar ...