Spring 实践 -AOP
Spring 实践
标签: Java与设计模式
AOP引介
AOP(Aspect Oriented Programing)面向切面编程采用横向抽取机制,以取代传统的纵向继承体系的重复性代码(如性能监控/事务管理/安全检查/缓存实现等).
横向抽取代码复用: 基于代理技术,在不修改原来代码的前提下,对原有方法进行增强.
Spring AOP 历史
- 1.2开始, Spring开始支持AOP技术(Spring AOP)
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码. - 2.0之后, 为了简化AOP开发, Spring开始支持AspectJ(一个基于Java的AOP框架)框架.
AOP相关术语
| 术语 | 中文 | 描述 |
|---|---|---|
| Joinpoint | 连接点 | 指那些被拦截到的点.在Spring中,这些点指方法(因为Spring只支持方法类型的连接点). |
| Pointcut | 切入点 | 指需要(配置)被增强的Joinpoint. |
| Advice | 通知/增强 | 指拦截到Joinpoint后要做的操作.通知分为前置通知/后置通知/异常通知/最终通知/环绕通知等. |
| Aspect | 切面 | 切入点和通知的结合. |
| Target | 目标对象 | 需要被代理(增强)的对象. |
| Proxy | 代理对象 | 目标对象被AOP 织入 增强/通知后,产生的对象. |
| Weaving | 织入 | 指把增强/通知应用到目标对象来创建代理对象的过程(Spring采用动态代理织入,AspectJ采用编译期织入和类装载期织入). |
| Introduction | 引介 | 一种特殊通知,在不修改类代码的前提下,可以在运行期为类动态地添加一些Method/Field(不常用). |
其他关于AOP理论知识可参考AOP技术研究.
AOP实现
Spring AOP代理实现有两种:JDK动态代理和Cglib框架动态代理, JDK动态代理可以参考博客代理模式的动态代理部分, 在这里仅介绍CGLib框架实现.
cglib 动态代理
cglib(Code Generation Library)是一个开源/高性能/高质量的Code生成类库,可以在运行期动态扩展Java类与实现Java接口.
cglib比java.lang.reflect.Proxy更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法(cglib项目).从3.2开始, spring-core包中内置cglib类,因此可以不用添加额外依赖.
- UserDAO(并没有实现接口)
/**
* @author jifang
* @since 16/3/3 上午11:16.
*/
public class UserDAO {
public void add(Object o) {
System.out.println("UserDAO -> Add: " + o.toString());
}
public void get(Object o) {
System.out.println("UserDAO -> Get: " + o.toString());
}
}
- CGLibProxyFactory
public class CGLibProxyFactory {
private Object target;
public CGLibProxyFactory(Object target) {
this.target = target;
}
private Callback callback = new MethodInterceptor() {
/**
*
* @param obj 代理对象
* @param method 当期调用方法
* @param args 方法参数
* @param proxy 被调用方法的代理对象(用于执行父类的方法)
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("+ Before Advice ...");
// 执行目标方法
//Object result = method.invoke(target, args);
Object result = proxy.invoke(target, args);
// 后置增强
System.out.println("+ After Advice ...");
return result;
}
};
public Object createProxy() {
// 1. 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 2. cglib创建代理, 对目标对象创建子对象
enhancer.setSuperclass(target.getClass());
// 3. 传入回调接口, 对目标增强
enhancer.setCallback(callback);
return enhancer.create();
}
public static void main(String[] args) {
UserDAO proxy = (UserDAO) new CGLibProxyFactory(new UserDAO()).createProxy();
proxy.get("hello");
proxy.add("world");
}
}
AOP小结
- Spring AOP的底层通过JDK/cglib动态代理为目标对象进行横向织入:
1) 若目标对象实现了接口,则Spring使用JDK的java.lang.reflect.Proxy代理.
2) 若目标对象没有实现接口,则Spring使用cglib库生成目标对象的子类. - Spring只支持方法连接点,不提供属性连接.
- 标记为
final的方法不能被代理,因为无法进行覆盖. - 程序应优先对针对接口代理,这样便于程序解耦/维护.
Spring AOP
AOP联盟为通知Advice定义了org.aopalliance.aop.Advice接口, Spring在Advice的基础上,根据通知在目标方法的连接点位置,扩充为以下五类:
| 通知 | 接口 | 描述 |
|---|---|---|
| 前置通知 | MethodBeforeAdvice |
在目标方法执行前实施增强 |
| 后置通知 | AfterReturningAdvice |
…执行后实施增强 |
| 环绕通知 | MethodInterceptor |
..执行前后实施增强 |
| 异常抛出通知 | ThrowsAdvice |
…抛出异常后实施增强 |
| 引介通知 | IntroductionInterceptor |
在目标类中添加新的方法和属性(少用) |
- 添加Spring的AOP依赖
使用Spring的AOP和AspectJ需要在pom.xml中添加如下依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
- 定义Target
/**
* @author jifang
* @since 16/3/3 下午2:50.
*/
public interface OrderService {
void save();
Integer delete(Integer param);
}
public class OrderServiceImpl implements OrderService {
@Override
public void save() {
System.out.println("添加...");
}
@Override
public Integer delete(Integer param) {
System.out.println("删除...");
return param;
}
}
- 定义Advice
/**
* 实现MethodInterceptor接口定义环绕通知
*
* @author jifang
* @since 16/3/6 下午2:54.
*/
public class ConcreteInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前置通知 -> ");
Object result = invocation.proceed();
System.out.println("<- 后置通知");
return result;
}
}
Spring手动代理
- 配置代理
Spring最原始的AOP支持, 手动指定目标对象与通知(没有使用AOP名称空间).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- target -->
<bean id="service" class="com.fq.service.impl.OrderServiceImpl"/>
<!-- advice -->
<bean id="advice" class="com.fq.advice.ConcreteInterceptor"/>
<bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames" value="advice"/>
<property name="proxyTargetClass" value="false"/>
</bean>
</beans>
- Client
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")
public class AOPClient {
@Autowired
// 必须指定使用代理对象名称, 否则不予代理
@Qualifier("serviceProxy")
private OrderService service;
@Test
public void client() {
service.save();
service.delete(88);
}
}
这种方式的缺陷在于每个
Target都必须手动指定ProxyFactoryBean对其代理(不能批量指定),而且这种方式会在Spring容器中存在两份Target对象(代理前/代理后),浪费资源,且容易出错(比如没有指定@Qualifier).
Spring自动代理 - 引入AspectJ
通过AspectJ引入Pointcut切点定义
- Target/Advice同前
- 定义切面表达式
通过execution函数定义切点表达式(定义切点的方法切入)
execution(<访问修饰符> <返回类型><方法名>(<参数>)<异常>)
如:
1)execution(public * *(..))# 匹配所有public方法.
2)execution(* com.fq.dao.*(..))# 匹配指定包下所有类方法(不包含子包)
3)execution(* com.fq.dao..*(..))# 匹配指定包下所有类方法(包含子包)
4)execution(* com.fq.service.impl.OrderServiceImple.*(..))# 匹配指定类所有方法
5)execution(* com.fq.service.OrderService+.*(..))# 匹配实现特定接口所有类方法
6)execution(* save*(..))# 匹配所有save开头的方法
<?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">
<!-- target -->
<bean id="service" class="com.fq.service.impl.OrderServiceImpl"/>
<!-- advice -->
<bean id="advice" class="com.fq.advice.ConcreteInterceptor"/>
<!-- 配置切面 : proxy-target-class确定是否使用CGLIB -->
<aop:config proxy-target-class="true">
<!--
aop:pointcut : 切点定义
aop:advisor: 定义Spring传统AOP的切面,只支持一个pointcut/一个advice
aop:aspect : 定义AspectJ切面的,可以包含多个pointcut/多个advice
-->
<aop:pointcut id="pointcut" expression="execution(* com.fq.service.impl.OrderServiceImpl.*(..))"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- Client同前
AspectJ AOP
AspectJ是一个基于Java的AOP框架,提供了强大的AOP功能,其他很多AOP框架都借鉴或采纳了AspectJ的一些思想,Spring2.0以后增加了对AspectJ切点表达式支持(如上),并在Spring3.0之后与AspectJ进行了很好的集成.
在Java领域,AspectJ中的很多语法结构基本上已成为AOP领域的标准, 他定义了如下几类通知类型:
| 通知 | 接口 | 描述 |
|---|---|---|
| 前置通知 | @Before |
相当于BeforeAdvice |
| 后置通知 | @AfterReturning |
相当于AfterReturningAdvice |
| 环绕通知 | @Around |
相当于MethodInterceptor |
| 抛出通知 | @AfterThrowing |
相当于ThrowAdvice |
| 引介通知 | @DeclareParents |
相当于IntroductionInterceptor |
| 最终final通知 | @After |
不管是否异常,该通知都会执行 |
新版本Spring,建议使用AspectJ方式开发以简化AOP配置.
AspectJ-XML-AOP
使用AspectJ编写Advice无需实现任何接口,而且可以将多个通知写入一个切面类.
前置通知
- 定义通知
/**
* @author jifang
* @since 16/3/3 下午5:38.
*/
public class Aspect {
/**
* 无返回值
*/
public void before1() {
System.out.println("前置增强before1");
}
/**
* 还可以传入连接点参数 JoinPoint
*
* @param point
*/
public void before2(JoinPoint point) {
System.out.printf("前置增强before2 %s%n", point.getKind());
}
}
- 装配
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.fq.service"/>
<!-- 配置切面通知 -->
<bean id="advice" class="com.fq.advice.Aspect"/>
<!-- AOP切面配置 -->
<aop:config>
<aop:aspect ref="advice">
<aop:pointcut id="pointcut" expression="execution(* com.fq.service.impl.OrderServiceImpl.*(..))"/>
<aop:before method="before1" pointcut-ref="pointcut"/>
<aop:before method="before2" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
- 前置通知小结
- 前置通知会保证在目标方法执行前执行;
- 前置通知默认不能阻止目标方法执行(但如果通知抛出异常,则目标方法无法执行);
- 可以通过
JoinPoint参数获得当前拦截对象和方法等信息.
后置通知
- 定义通知
public void afterReturning(JoinPoint point, Object result) {
System.out.printf("后置增强, 结果为 %s%n", result);
}
- 装配
<aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/>
后置通知可以获得方法返回值,但在配置文件定义返回值参数名必须与后置通知方法参数名一致(如
result).
环绕通知
- 定义通知
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.printf("环绕前置增强 method: %s, args: %s%n", point.toShortString(), Arrays.toString(point.getArgs()));
Object result = point.proceed(point.getArgs());
System.out.printf("环绕后置增强 result: %s%n", result);
return result;
}
- 装配
<aop:around method="around" arg-names="point" pointcut-ref="pointcut"/>
环绕通知可以实现任何通知的效果, 甚至可以阻止目标方法的执行.
抛出通知
- 定义通知
private static final Logger LOGGER = LoggerFactory.getLogger(Aspect.class);
public void afterThrowing(JoinPoint point, Throwable ex) {
String message = new StringBuilder("method ").append(point.getSignature().getName()).append(" error").toString();
System.out.println(message);
LOGGER.error("{},", message, ex);
}
- 装配
<aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
throwing属性指定异常对象名, 该名称应和方法定义参数名一致.
最终通知
- 定义通知
public void after(JoinPoint point) {
System.out.println("最终通知, 释放资源");
}
- 装配
<aop:after method="after" pointcut-ref="pointcut"/>
无论目标方法是否出现异常,该通知都会执行(类似
finally代码块, 应用场景为释放资源).
AspectJ-Annotation-AOP
@AspectJ是AspectJ 1.5新增功能,可以通过JDK注解技术,直接在Bean类中定义切面.
AspectJ预定义的注解有:@Before/@AfterReturning/@Around/@AfterThrowing/@DeclareParents/@After.描述同前.
使用AspectJ注解AOP需要在applicationContext.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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 批量扫描@Component -->
<context:component-scan base-package="com.fq"/>
<!-- 启用注解自动代理@Aspect-->
<aop:aspectj-autoproxy/>
</beans>
OrderService/Client同前
@Before
- Aspect
/**
* @Aspect: 指定是一个切面
* @Component: 指定可以被Spring容器扫描到
*/
@Aspect
@Component
public class CustomAspect {
@Before("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public void before(JoinPoint point) {
System.out.printf("前置增强before2 %s%n", point.getKind());
}
}
@AfterReturning
@AfterReturning(value = "execution(* com.fq.service.impl.OrderServiceImpl.d*(..))", returning = "result")
public void afterReturning(JoinPoint point, Object result) {
System.out.printf("后置增强, 结果为 %s%n", result);
}
@Around
@Around("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
long start = System.currentTimeMillis();
Object result = point.proceed(point.getArgs());
long time = System.currentTimeMillis() - start;
System.out.printf("method %s invoke consuming %d ms%n", point.toLongString(), time);
return result;
}
如果不调用
ProceedingJoinPoint的proceed方法,那么目标方法就不执行了.
@AfterThrowing
@AfterThrowing(value = "execution(* com.fq.service.impl.OrderServiceImpl.*(..))", throwing = "ex")
public void afterThrowing(JoinPoint point, Throwable ex) {
String message = new StringBuilder("method ").append(point.getSignature().getName()).append(" error").toString();
System.out.println(message);
LOGGER.error("{},", message, ex);
}
@After
@After("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public void after(JoinPoint point) {
System.out.println("最终通知, 释放资源");
}
@Pointcut定义切点
对于重复的切点,可以使用@Pointcut进行定义, 然后在通知注解内引用.
- 定义切点方法
无参/无返回值/方法名为切点名:
/**
* @author jifang
* @since 16/3/4 上午11:47.
*/
public class OrderServicePointcut {
@Pointcut("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public void pointcut() {
}
}
- 引用切点
在Advice上像调用方法一样引用切点:
@After("OrderServicePointcut.pointcut()")
public void after(JoinPoint point) {
System.out.println("最终通知, 释放资源");
}
1) 如果切点与切面在同一个类内, 可省去类名前缀;
2) 当需要通知多个切点时,可以使用||/&&进行连接.
小结
| 通知 | 描述 |
|---|---|
| 前置通知 | 权限控制(少用) |
| 后置通知 | 少用 |
| 环绕通知 | 权限控制/性能监控/缓存实现/事务管理 |
| 异常通知 | 发生异常后,记录错误日志 |
| 最终通知 | 释放资源 |
Spring 实践 -AOP的更多相关文章
- Spring Boot实践——AOP实现
借鉴:http://www.cnblogs.com/xrq730/p/4919025.html https://blog.csdn.net/zhaokejin521/article/detai ...
- Spring基于AOP的事务管理
Spring基于AOP的事务管理 事务 事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务 ...
- Spring之AOP面向切片
一.理论基础: AOP(Aspectoriented programming)面向切片/服务的编程,在Spring中使用最多的是对事物的处理.而AOP这种思想在程序中很多地方可以使用的,比如说, ...
- Spring 实践 -拾遗
Spring 实践 标签: Java与设计模式 Junit集成 前面多次用到@RunWith与@ContextConfiguration,在测试类添加这两个注解,程序就会自动加载Spring配置并初始 ...
- Spring 实践 -IoC
Spring 实践 标签: Java与设计模式 Spring简介 Spring是分层的JavaSE/EE Full-Stack轻量级开源框架.以IoC(Inverse of Control 控制反转) ...
- Spring学习笔记(二)Spring基础AOP、IOC
Spring AOP 1. 代理模式 1.1. 静态代理 程序中经常需要为某些动作或事件作下记录,以便在事后检测或作为排错的依据,先看一个简单的例子: import java.util.logging ...
- Spring的AOP原理
转自 https://www.tianmaying.com/tutorial/spring-aop AOP是什么? 软件工程有一个基本原则叫做“关注点分离”(Concern Separation),通 ...
- Spring实现AOP的4种方式
了解AOP的相关术语:1.通知(Advice):通知定义了切面是什么以及何时使用.描述了切面要完成的工作和何时需要执行这个工作.2.连接点(Joinpoint):程序能够应用通知的一个“时机”,这些“ ...
- spring的AOP
最近公司项目中需要添加一个日志记录功能,就是可以清楚的看到谁在什么时间做了什么事情,因为项目已经运行很长时间,这个最初没有开来进来,所以就用spring的面向切面编程来实现这个功能.在做的时候对spr ...
随机推荐
- Javascript引擎单线程机制及setTimeout执行原理说明
setTimeout用法在实际项目中还是会时常遇到.比如浏览器会聪明的等到一个函数堆栈结束后才改变DOM,如果再这个函数堆栈中把页面背景先从白色设为红色,再设回白色,那么浏览器会认为DOM没有发生任何 ...
- android 关于Location of the Android SDK has not been setup in the preferences的解决方法
今天在部署android开发环境的时候,每次打开eclipse的时候点击AVD Manager的按钮就会弹出Location of the Android SDK has not been setup ...
- Create Script Template In Edit Mode
很多时候 许多类 的 格式 都是重复的,比如 从配置文件中映射出来的类. 这个时候写一个 类模板 就很节省时间了. Code public static string TestPath = " ...
- HDU1724 Ellipse(数值积分)
补一下一些小盲区,譬如simpson这种数值积分的方法虽然一直知道,但是从未实现过,做一道例题存一个模板. #pragma warning(disable:4996) #include<iost ...
- HDU 4027 Can you answer these queries?(线段树,区间更新,区间查询)
题目 线段树 简单题意: 区间(单点?)更新,区间求和 更新是区间内的数开根号并向下取整 这道题不用延迟操作 //注意: //1:查询时的区间端点可能前面的比后面的大: //2:优化:因为每次更新都 ...
- POJ 2689 Prime Distance (素数筛选法,大区间筛选)
题意:给出一个区间[L,U],找出区间里相邻的距离最近的两个素数和距离最远的两个素数. 用素数筛选法.所有小于U的数,如果是合数,必定是某个因子(2到sqrt(U)间的素数)的倍数.由于sqrt(U) ...
- 定时每天执行前一天的数据导入oracle
#!/bin/bash export LANG="en_US.UTF-8" #设定时间变量,为前一天时间 log_date=`date +%Y-%m-%d -d "-1 ...
- 利用PHP SOAP实现WEB SERVICE
php有两个扩展可以实现web service,一个是NuSoap,一个是php 官方的soap扩展,由于soap是官方的,所以我们这里以soap来实现web service.由于默认是没有打开soa ...
- Educational Codeforces Round 4 D. The Union of k-Segments 排序
D. The Union of k-Segments You re given n segments on the coordinate axis Ox and the number k. The ...
- SqlBulkCopy大批量数据插入到sql表中
alter TYPE TableType AS TABLE ( Name VARCHAR() , code VARCHAR() ) GO alter PROCEDURE usp_InsertProdu ...