Spring之旅第五篇-AOP详解
一、什么是AOP?
Aspect oritention programming(面向切面编程),AOP是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如果每个页面都去实现方法显然是不合适的,这个时候我们就可以利用切面编程。
每个页面都去实现这个方法就是横向的重复,我们直接从中切入,封装一个与主业务无关的权限验证的公共方法,这样可以减少系统的重复代码,降低模块之间的耦合度,简单的示意图如下:
二、应用场景
AOP用来封装横切关注点,具体可以在下面的场景中使用:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试 l
ogging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
三、相关概念
1.连接点(Joinpoint) 所谓连接点是指那些可能被拦截到的方法。例如:所有可以增加的方法
2.切点(Pointcut) 已经被增强的连接点
3.增强(Advice) 增强的代码
4.目标对象(Target) 目标类,需要被代理的类
5.织入(Weaving) 是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程
6.代理(Proxy) 一个类被AOP织入增强后,就产生出了一个结果类,它是融合了原类和增强逻辑的代理类。
7.切面(Aspect)切入点+通知
通知类型:Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
前置通知 (在目标方法执行前实施增强)
后置通知(在目标方法执行后实施增强)
环绕通知(在目标方法执行前后实施增加)
异常抛出通知(在方法跑出异常时通知)
引介通知(在目标类中添加一些新的方法和属性)
四、实现原理
AOP的实现关键在于AOP框架自动创建的AOP代理。AOP代理主要分为两大类:
静态代理:使用AOP框架提供的命令进行编译,从而在编译阶段就可以生成AOP代理类,因此也称为编译时增强;静态代理一Aspectj为代表。
动态代理:在运行时借助于JDK动态代理,CGLIB等在内存中临时生成AOP动态代理类,因此也被称为运行时增强,Spring AOP用的就是动态代理。
4.1 静态代理
例子:在增加员工和删除员工时增加事务处理
//员工类
public class Employee {
private Integer uid; public void setUid(Integer uid) {
this.uid = uid;
} public Integer getUid() { return uid;
} private Integer age; private String name; public Integer getAge() {
return age;
} public String getName() {
return name;
} public void setAge(Integer age) {
this.age = age;
} public void setName(String name) {
this.name = name;
}
}
员工接口:
//员工接口
public interface EmployeeService {
//新增方法
void addEmployee(Employee employee);
//删除方法
void deleteEmployee(Integer uid);
}
员工实现:
//员工方法实现
public class EmployeeServiceImpl implements EmployeeService {
@Override
public void addEmployee(Employee employee) {
System.out.println("新增员工");
} @Override
public void deleteEmployee(Integer uid) {
System.out.println("删除员工"); }
}
事务类:
//事务类
public class MyTransaction {
//开启事务
public void before(){
System.out.println("开启事务");
}
//提交事务
public void after(){
System.out.println("提交事务");
}
}
代理类:
//代理类
public class ProxyEmployee implements EmployeeService {
//
private EmployeeService employeeService; private MyTransaction myTransaction; public ProxyEmployee(EmployeeService employeeService,MyTransaction myTransaction)
{
this.employeeService=employeeService;
this.myTransaction=myTransaction;
}
@Override
public void addEmployee(Employee employee) {
myTransaction.before();
employeeService.addEmployee(employee);
myTransaction.after();
} @Override
public void deleteEmployee(Integer uid) {
myTransaction.before();
employeeService.deleteEmployee(uid);
myTransaction.after();
}
}
测试:
@Test
public void fun1(){
MyTransaction transaction = new MyTransaction();
EmployeeService EmployeeService = new EmployeeServiceImpl();
//产生静态代理对象
ProxyEmployee proxy = new ProxyEmployee(EmployeeService, transaction);
proxy.addEmployee(null);
proxy.deleteEmployee(0);
}
结果:
这是静态代理的实现方式,静态代理有明显的缺点:
1、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2、如果接口增加一个方法,比如 EmployeeService增加修改 updateEmployee()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
4.2 动态代理
动态代理就不要自己手动生成代理类了,我们去掉 ProxyEmployee.java 类,增加一个 ObjectInterceptor.java 类
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(target,args);
this.transaction.after();
return null;
}
}
测试:
@Test
public void fun2(){
//目标类
Object target = new EmployeeServiceImpl ();
//事务类
MyTransaction transaction = new MyTransaction();
ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
/**
* 三个参数的含义:
* 1、目标类的类加载器
* 2、目标类所有实现的接口
* 3、拦截器
*/
EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), proxyObject);
employeeService.addEmployee(null);
employeeService.deleteEmployee(0);
}
结果:
五、spring的处理AOP的方式
spring 有两种方式实现AOP的:一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)
5.1 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"> <!--目标类-->
<bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
<bean name="transaction" class="com.yuanqinnan.aop.MyTransaction"></bean>
<aop:config>
<aop:aspect ref="transaction">
<aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"/>
<!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
<!--配置后置通知,注意 method 的值要和 对应切面的类方法名称相同-->
<aop:after-returning method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config> </beans>
测试:
@Test
public void fun3(){
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
EmployeeService employeeService = (EmployeeService) context.getBean("employeeService");
employeeService.addEmployee(null);
}
结果:
补充:
1.aop:pointcut如果位于aop:aspect元素中,则命名切点只能被当前aop:aspect内定义的元素访问到,为了能被整个aop:config元素中定义的所有增强访问,则必须在aop:config下定义切点。
2.如果在aop:config元素下直接定义aop:pointcut,必须保证aop:pointcut在aop:aspect之前定义。aop:config下还可以定义aop:advisor,三者在aop:config中的配置有先后顺序的要求:首先必须是aop:pointcut,然后是aop:advisor,最后是aop:aspect。而在aop:aspect中定义的aop:pointcut则没有先后顺序的要求,可以在任何位置定义。
aop:pointcut:用来定义切入点,该切入点可以重用;
aop:advisor:用来定义只有一个通知和一个切入点的切面;
aop:aspect:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
3.在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式 execution(* com.sample.service.impl...(..)) execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分: (1)、execution(): 表达式主体。
(2)、第一个号:表示返回类型,号表示所有的类型。
(3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
(4)、第二个号:表示类名,号表示所有的类。
(5)、(..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
5.2 注解配置
新建注解类:
@Component
@Aspect
public class AopAspectJ {
/**
* 必须为final String类型的,注解里要使用的变量只能是静态常量类型的
*/
public static final String EDP="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"; /**
* 切面的前置方法 即方法执行前拦截到的方法
* 在目标方法执行之前的通知
* @param jp
*/
@Before(EDP)
public void doBefore(JoinPoint jp){ System.out.println("=========执行前置通知==========");
} /**
* 在方法正常执行通过之后执行的通知叫做返回通知
* 可以返回到方法的返回值 在注解后加入returning
* @param jp
* @param result
*/
@AfterReturning(value=EDP,returning="result")
public void doAfterReturning(JoinPoint jp,String result){
System.out.println("===========执行后置通知============");
} /**
* 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
* @param jp
*/
@After(value=EDP)
public void doAfter(JoinPoint jp){
System.out.println("===========执行最终通知============");
} /**
* 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。
* @param pjp
* @return
* @throws Throwable
*/
@Around(EDP)
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行环绕通知开始=========");
// 调用方法的参数
Object[] args = pjp.getArgs();
// 调用的方法名
String method = pjp.getSignature().getName();
// 获取目标对象
Object target = pjp.getTarget();
// 执行完方法的返回值
// 调用proceed()方法,就会触发切入点方法执行
Object result=pjp.proceed();
System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
System.out.println("======执行环绕通知结束=========");
return result;
} /**
* 在目标方法非正常执行完成, 抛出异常的时候会走此方法
* @param jp
* @param ex
*/
@AfterThrowing(value=EDP,throwing="ex")
public void doAfterThrowing(JoinPoint jp,Exception ex) {
System.out.println("===========执行异常通知============");
}
}
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">
<context:component-scan base-package="com.yuanqinnan.aop" ></context:component-scan>
<!-- 声明spring对@AspectJ的支持 -->
<aop:aspectj-autoproxy/>
<!--目标类-->
<bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
</beans>
测试:
@Test
public void fun4(){
ApplicationContext act = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
EmployeeService employeeService = (EmployeeService) act.getBean("employeeService");
employeeService.addEmployee(null);
}
结果:
pringAOP的知识就总结到这里
Spring之旅第五篇-AOP详解的更多相关文章
- Spring框架 之IOC容器 和AOP详解
主要分析点: 一.Spring开源框架的简介 二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置 一.S ...
- Spring Boot2 系列教程 (五) | yaml 配置文件详解
自定义属性加载 首先构建 SpringBoot 项目,不会的看这篇旧文 使用 IDEA 构建 Spring Boot 工程. 首先在项目根目录 src >> resource >&g ...
- 深入浅出学习Spring框架(三):AOP 详解
AOP的英文解释——AOPAspect Oriented Programming面向切面编程.主要目的是通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. 在反 ...
- Spring全家桶系列–SpringBoot之AOP详解
//本文作者:cuifuan //本文将收录到菜单栏:<Spring全家桶>专栏中 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关 ...
- Spring AOP详解及简单应用
Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
- 转:Spring AOP详解
转:Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
- Spring Aop 详解二
这是Spring Aop的第二篇,案例代码很详解,可以查看https://gitee.com/haimama/java-study/tree/master/spring-aop-demo. 阅读前,建 ...
- 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理
Spring AOP详解 . JDK动态代理.CGLib动态代理 原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...
- [Spring学习笔记 5 ] Spring AOP 详解1
知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...
随机推荐
- 有趣的toggleClass实现交替样式
addClass和removeClass进行样式类型的修改相信比较容易学习和接受 但是用这两个方法去实现交替样式,像一些<li>列表的样式,还有同类型数据的呈现, 当然很多框架都给出了封装 ...
- java编程思想-第13章-某些练习题
. 匹配任意一个字符 * 表示匹配0个或多个前面这个字符 + 表示1个或多个前面这个字符 ? 表示0个或1个前面这个字符 ^ 表示一行的开始 ^[a-zA-Z] :表示开头是a-z或者A-Z [^0- ...
- 电脑开机出现“致命错误C0000034。。。”--该怎么办?
win7或win8系统的电脑在开机时出现了 "致命错误C0000034 正在更新操作236,共156764个0000000000000000.cdf-ms "的提示并不能正常启动系 ...
- Django rest_framework快速入门
一.什么是REST 面向资源是REST最明显的特征,资源是一种看待服务器的方式,将服务器看作是由很多离散的资源组成.每个资源是服务器上一个可命名的抽象概念.因为资源是一个抽象的概念,所以它不仅仅能代表 ...
- BZOJ_1015_[JSOI2008]星球大战_并查集
BZOJ_1015_[JSOI2008]星球大战_并查集 题意:很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的 机遇,一支反抗军摧毁了帝国的超级武器, ...
- linux yum命令 使用
yum -y install 包名(支持*) :自动选择y,全自动 yum install 包名(支持*) :手动选择y or n yum remove 包名(不支持*) rpm -ivh 包名(支持 ...
- 【毕业原版】-《伦敦艺术大学毕业证书》UAL一模一样原件
☞伦敦艺术大学毕业证书[微/Q:865121257◆WeChat:CC6669834]UC毕业证书/联系人Alice[查看点击百度快照查看][留信网学历认证&博士&硕士&海归& ...
- linux系统光盘开机自动挂载-配置本地yum源
一.光盘开机自动挂载 1.修改配置文件 执行命令 :vi /etc/fstab 添加/dev/cdrom /mnt iso9660 ...
- java基础 —— properties 使用
目的:分别读取myPro*.properties文件内容,复习一下项目中读取资源配置文件的方法. 项目下载地址:http://pan.baidu.com/s/1jHuzPxs 项目结构如图,ReadP ...
- HTML 基本语法速查
HTML 基本文档 <!DOCTYPE html> <html> <head> <title>文档标题</title> </head& ...