spring Aop的两种实现方式(xml,注释)
AOP介绍
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP的作用
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
在程序运行期间,不修改源码对已有方法进行增强。
将业务逻辑和系统处理的代码(关闭连接、事务管理、操作日志记录)解耦。
优点:
- 减少重复代码
- 提高开发效率
- 维护方便
AOP相关术语介绍
- Joinpoint(连接点) – 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点) – 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
- Advice(通知/增强) – 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介) – 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
- Target(目标对象) – 代理的目标对象
- Weaving(织入) – 是指把增强应用到目标对象来创建新的代理对象的过程
- Proxy(代理) – 一个类被AOP织入增强后,就产生一个结果代理类
- Aspect(切面) – 是切入点和通知的结合,以后咱们自己来编写和配置的
AOP实现之AspectJ
***AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)
***可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易。
***了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。
***对于这个过程,一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术
***ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。
Spring AOP实现原理分析
***Spring AOP是通过动态代理技术实现的
***而动态代理是基于反射设计的。
***动态代理技术的实现方式有两种:基于接口的JDK动态代理和基于继承的CGLib动态代理。
JDK动态代理
目标对象必须实现接口
使用Proxy类来生成代理对象的一些代码如下:
/**
* 使用JDK的方式生成代理对象
* @author Administrator
*/
public class MyProxyUtils {
public static UserService getProxy(final UserService service) {
// 使用Proxy类生成代理对象
UserService proxy =
(UserService) Proxy.newProxyInstance(
service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new InvocationHandler() {
// 代理对象方法一执行,invoke方法就会执行一次
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("记录日志...");
// 开启事务
}
// 提交事务
// 让service类的save或者update方法正常的执行下去
return method.invoke(service, args);
}
});
// 返回代理对象
return proxy;
}
}
CGLib动态代理
目标对象不需要实现接口
底层是通过继承目标对象产生代理子对象(代理子对象中继承了目标对象的方法,并可以对该方法进行增强)
2. 编写相关的代码
public static UserService getProxy(){
// 创建CGLIB核心的类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(UserServiceImpl.class);
// 设置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
// 记录日志
System.out.println("记录日志了...");
}
return methodProxy.invokeSuper(obj, args);
}
});
// 生成代理对象
UserService proxy = (UserService) enhancer.create();
return proxy;
}
Spring AOP使用
基于xml配置实现
编写目标类
UserService接口
UserServiceImpl实现类
public class MyAdvice {
public void log() {
System.out.println("MyAdvice:记录日志");
}
public void afterReturning() {
System.out.println("afterReturning");
}
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
Object ret = null;
try {
try {
System.out.println("1111");
Object[] args = joinPoint.getArgs();
ret = joinPoint.proceed(args);
System.out.println("222");
} catch (Throwable e) {
System.out.println("333");
e.printStackTrace();
} finally {
System.out.println("444");
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
}
配置目标类,将目标类交给spring IoC容器管理
<bean id="userService" class="com.fang.service.impl.UserServiceImpl">
</bean>
配置AOP 切面
<bean id="myAdvice" class="com.fang.advice.MyAdvice"></bean>
<aop:config>
<aop:aspect ref="myAdvice">
<aop:before method="log" pointcut="execution(void com.*..*.impl.*.*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(void com.*..*.impl.*.*(..))"/>
<aop:around method="aroundMethod" pointcut="execution(void com.*..*.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
切入点表达式的格式:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
** execution:必须要
** 修饰符:可省略
** 返回值类型:必须要,但是可以使用*通配符
** 包名 :
** 多级包之间使用.分割
** 包名可以使用*代替,多级包名可以使用多个*代替
** 如果想省略中间的包名可以使用 ..
** 类名
** 可以使用*代替
** 也可以写成*DaoImpl
** 方法名:
** 也可以使用*好代替
** 也可以写成add*
** 参数:
** 参数使用*代替
** 如果有多个参数,可以使用 ..代替
***通知类型(五种):前置通知、后置通知、最终通知、环绕通知、异常抛出通知。
***前置通知:
***执行时机:目标对象方法之前执行通知
***配置文件:<aop:before method="before" pointcut-ref="myPointcut"/>
***应用场景:方法开始时可以进行校验
***后置通知:
***执行时机:目标对象方法之后执行通知,有异常则不执行了
***配置文件:<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
***应用场景:可以修改方法的返回值
***最终通知:
***执行时机:目标对象方法之后执行通知,有没有异常都会执行
***配置文件:<aop:after method="after" pointcut-ref="myPointcut"/>
***应用场景:例如像释放资源
***环绕通知:
***执行时机:目标对象方法之前和之后都会执行。
***配置文件:<aop:around method="around" pointcut-ref="myPointcut"/>
***应用场景:事务、统计代码执行时机
***异常抛出通知:
***执行时机:在抛出异常后通知
***配置文件:<aop:after-throwing method=" afterThrowing " pointcut- ref="myPointcut"/>
***应用场景:包装异常
基于AspectJ的注解实现
<!-- 组件扫描器,扫描切面类 -->
<context:component-scan base-package="com.fang"></context:component-scan>
<!-- 开启AOP自动代理 -->
<aop:aspectj-autoproxy/>
@Component("myAspect")
@Aspect
public class MyAspect {
private final static String repStr="execution(void com.*..*.impl.*.*(..))";
@Before(repStr)
public void log(){
System.out.println("记录日志------------");
}
}