Spring AOP 面向切面编程
什么是aop?
Spring把横切式的应用,比如事务,日志等。单独抽出来,声明给某些目标对象,然后这些被声明过得对象就具有了该横切式应用能力,或者说会执行横切应用。理解上可以参照filter和interceptor。
概念:
· 通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
· 切入点(Pointcut): 匹配连接点(Joinpoint)的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
Aop底层是怎么实现的呢,如何做到在拦截到某个业务类之后,执行通知的动作?
答案是采用代理模式
什么是代理:
为某个对象提供一个代理,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。
示意图:
代理分类:静态代理和动态代理
Java静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
所谓代理就是在方法前后加上执行代码,但不更改被代理的类本身的方法。
课堂举例:
动态代理
动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
使用jdk的动态代理需要为被代理类设置接口。
方面代码:
1. 首先定义一个接口:
public interface IUserService {
publicvoid addUser();
}
2. 定义一个目标类,实现这个接口
public class UserServiceImp implements IUserService {
@Override
publicvoid addUser() {
//TODOAuto-generated method stub
System.out.println("添加一个用户成功!!!!");
}
}
3. 定义切面代码
/**
* 自定义的切面
* @authorchidianwei
*
*/
public class MyAspectHanlder implements InvocationHandler {
private Object target;//被代理者,目标对象
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 横切时候做的事情
System.out.println("执行具体任务之前,额外做的事情,获取页面传过来的参数形成user对象");
// 反射机制执行target的method方法
Object obj=method.invoke(target, args);
return obj;
}
}
4. 定义测试类Test,动态生成代理类对象
public class Test {
public static void main(String[] args) {
// 代理类刚开始并不是写死的,而是通过jdk的动态代理机制,根据反射自动生成的。
MyAspectHanlder in=new MyAspectHanlder();
in.setTarget(new UserServiceImp());//注入一个目标对象
IUserService proxy=(IUserService)Proxy.newProxyInstance(Test.class.getClassLoader(),
new Class[]{IUserService.class},
in);
proxy.addUser();
}
}
jdk动态代理机制
要求被代理类必须实现一个接口,然后还要有一个处理类,来完成被代理类本来就要完成的业务。
Spring aop的配置使用
判断具体addUser等具体业务之前,用户是否登录?具体编程步骤:
1. 引入aop依赖的jar包
2.编写横切方面业务代码MyHanlder
@Component(value="securityHandler")
public class MyHandler {
public void checkSecurity(){
System.out.println("check login or nothanlder....");
}
}
2.配置文件中定义横切点piontcut和advice
<?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/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scanbase-package="sdibt.group"></context:component-scan>
<aop:config>
<aop:aspect id="securityAspect" ref="securityHandler">
<!--以add开头的方法
<aop:pointcutid="addAddMethod" expression="execution(* add*(..))"/>
-->
<!--
<aop:pointcutid="addAddMethod" expression="execution(*com.jmt.bean.*.*(..))"/>
-->
<aop:pointcutid="addAddMethod" expression="execution(* sdibt.group.action.*.add*(..)) ||execution(* sdibt.group.dao.*.add*(..))"/>
<aop:before method="checkSecurity" pointcut-ref="addAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
注意,如果是环绕通知,要求切面定义这样:
publicvoidcheckSecurity(ProceedingJoinPoint p) throws Throwable{
System.out.println("在此处检验用户是否已经登录....before...");
Object ret=p.proceed();
System.out.println("在此处检验用户是否已经登录...after....");
}
也可以直接使用注解方式:
1. 开发切面的代码
@Aspect
@Component(value="securityHandler")
public class MyHandler {
@Pointcut("execution(* sdibt.group.action.*.add*(..))|| execution(* sdibt.group.dao.*.add*(..))")
private void addAddMethod(){}
@Before("addAddMethod()")
public void checkSecurity(){
System.out.println("check login or nothanlder....");
}
}
2. 配置文件
<?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/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scanbase-package="sdibt.group"></context:component-scan>
<!-- 启用AspectJ对Annotation的支持 -->
<aop:aspectj-autoproxy/>
</beans>
如果想要在执行切面代码时获得切入点相关信息,代码如下:
Lijie
了解部分:
AOP 是 Spring 独有的概念吗?
不是,除了Spring AOP,AOP 常见的实现还有:
Aspectj
Guice AOP
Jboss AOP
等
AOP Alliance 是什么, 为什么 Spring AOP 需要 aopalliance.jar ?
AOP Alliance 是AOP的接口标准,定义了 AOP 中的基础概念(Advice、CutPoint、Advisor等),目标是为各种AOP实现提供统一的接口,本身并不是一种 AOP 的实现。
Spring AOP, GUICE等都采用了AOP Alliance中定义的接口,因而这些lib都需要依赖 aopalliance.jar。
Update: Spring 4.3之后内置了AOP Alliance接口,不再需要单独的aopalliance.jar。
Spring AOP 和 Aspectj 的区别?
Spring AOP采用动态代理的方式,在运行期生成代理类来实现AOP,不修改原类的实现;Aspectj 使用编译期字节码织入(weave)的方式,在编译的时候,直接修改类的字节码,把所定义的切面代码逻辑插入到目标类中。
Spring AOP可以对其它模块正常编译出的代码起作用,Aspectj 需要对其它模块使用acj重新编译
由于动态代理机制,Spring AOP对于直接调用类内部的其它方法无效,无法对定义为final的类生效。Aspectj没有这些限制
Spring AOP使用XML配置文件的方式定义切入点(CutPoint),Aspectj使用注解方式
注:Aspectj 除了编译期静态织入的方式之外,也支持加载时动态织入修改类的字节码。
Spring AOP 如何生成代理类?
Spring AOP使用JDK Proxy或者cglib实现代理类生成。对于有实现接口的类使用JDK Proxy,对于无接口的则是用cglib.通过
<aop:aspectj-autoproxyproxy-target-class="true"/>
指定proxy-target-class为true可强制使用cglib.
JDK Proxy 和 cglib 代理类生成什么区别?
JDK Proxy只适用于类实现了接口的情况,关系图:
Interface ----------> OriginClass
|---------> ProxyClass
生成的代理类实现了原类的接口,但和原类没有继承关系.
cglib则是生成原来的子类,对于没有实现接口的情况也适用:
OriginClass --------> ProxyClass
cglib采用字节码生成的方式来在代理类中调用原类方法, JDK Proxy 则是使用反射调用,由于反射存在额外security check 的开销一集目前jvm jit对反射的内联支持不够好,JDK Proxy在性能上弱于cglib
Spring-aspects又是什么鬼?
因为Spring AOP XML配置文件定义的方式太繁琐遭到吐槽,所以spring从Aspectj中吸收了其定义AOP的方式,包括Aspectj Annotation和Aspectj-XML配置。然而其实现依然是动态代理的方式,与aspectj 字节码织入的方式不同。
为什么spring-aspects还需要 aspectjweaver.jar才能工作
Spring-aspects 实现XML配置解析和类似 Aspectj 注解方式的时候,借用了 aspectjweaver.jar 中定义的一些annotation 和 class,然而其并不使用 Aspectj 的字节码织入功能。
Spring-aspects不能把这些所需的类定义抄一份吗,这样就不需要aspectjweaver.jar了
他们可以,但是他们偏不这样做。
Spring 3.1 之前 spring-aspects 对 aspectjweaver 的依赖还是 optional 的,需要自己再添加依赖;Sprint 3.2 之后 依赖取消了 optional 设置,可以不用自己添加了。