什么是AOP
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。
AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理、性能监视、缓存等方面。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示。
名称 | 说明 |
---|---|
Joinpoint(连接点) | 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截 目标类的方法。 |
Pointcut(切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象。 |
Weaving(植入) | 指把增强代码应用到目标上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切入点和通知的结合。 一个线是一个特殊的面。 一个切入点和一个通知,组成成一个特殊的面。 |
AOP实现方式之JDK 动态代理
JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的。下面通过具体的案例演示 JDK 动态代理的使用。
1、创建接口 CustomerDao
public interface CustomerDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}
2、创建实现类 CustomerDaoImpl
public class CustomerDaoImpl implements CustomerDao {
@Override
public void add() {
System.out.println("添加客户...");
}
@Override
public void update() {
System.out.println("修改客户...");
}
@Override
public void delete() {
System.out.println("删除客户...");
}
@Override
public void find() {
System.out.println("修改客户...");
}
}
3、创建切面类 MyAspect
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}
上述代码中,在切面中定义了两个增强的方法,分别为 myBefore() 方法和 myAfter() 方法,用于对目标类(CustomerDaoImpl)进行增强。
4、创建代理类 MyBeanFactory
public class MyBeanFactory {
public static CustomerDao getBean() {
// 准备目标类
final CustomerDao customerDao = new CustomerDaoImpl();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 使用代理类,进行增强
return (CustomerDao) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
new Class[] { CustomerDao.class }, new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
myAspect.myBefore(); // 前增强
Object obj = method.invoke(customerDao, args);
myAspect.myAfter(); // 后增强
return obj;
}
});
}
}
上述代码中:
- 定义了一个静态的 getBean() 方法,这里模拟 Spring 框架的 IoC 思想,通过调用 getBean() 方法创建实例
- 创建的切面类实例用于调用切面类中相应的方法;
- 使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。
- 在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。
5、创建测试类 JDKProxyTest
public class JDKProxyTest {
@Test
public void test() {
// 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
CustomerDao customerDao = MyBeanFactory.getBean();
// 执行方法
customerDao.add();
customerDao.update();
customerDao.delete();
customerDao.find();
}
}
AOP实现方式之CGLlB动态代理
JDK 动态代理使用起来非常简单,但是它也有一定的局限性,这是因为 JDK 动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用 CGLIB 代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。
1、创建目标类 GoodsDao
public class GoodsDao {
public void add() {
System.out.println("添加商品...");
}
public void update() {
System.out.println("修改商品...");
}
public void delete() {
System.out.println("删除商品...");
}
public void find() {
System.out.println("修改商品...");
}
}
2、创建代理类 MyBeanFactory
public class MyBeanFactory {
public static GoodsDao getBean() {
// 准备目标类
final GoodsDao goodsDao = new GoodsDao();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
Enhancer enhancer = new Enhancer();
// 确定需要增强的类
enhancer.setSuperclass(goodsDao.getClass());
// 添加回调函数
enhancer.setCallback(new MethodInterceptor() {
// intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
myAspect.myBefore(); // 前增强
Object obj = method.invoke(goodsDao, args); // 目标方法执行
myAspect.myAfter(); // 后增强
return obj;
}
});
// 创建代理类
GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
return goodsDaoProxy;
}
}
上述代码中,应用了 CGLIB 的核心类 Enhancer。
- 调用了 Enhancer 类的 setSuperclass() 方法,确定目标对象。
- 调用 setCallback() 方法添加回调函数;
- intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;
- 调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回。
3、创建测试类
public class CGLIBProxyTest {
@Test
public void test() {
// 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
GoodsDao goodsDao = MyBeanFactory.getBean();
// 执行方法
goodsDao.add();
goodsDao.update();
goodsDao.delete();
goodsDao.find();
}
}
Spring AOP
Spring AOP通知类型
AOP联盟为通知Advice定义了org.aopalliance.aop.Advice
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
- 前置通知 org.springframework.aop.MethodBeforeAdvice:在目标方法执行前实施增强
- 后置通知 org.springframework.aop.AfterReturningAdvice:在目标方法执行后实施增强
- 环绕通知 org.aopalliance.intercept.MethodInterceptor:在目标方法执行前后实施增强
- 异常抛出通知 org.springframework.aop.ThrowsAdvice:在方法抛出异常后实施增强
- 引介通知 org.springframework.aop.IntroductionInterceptor:在目标类中添加一些新的方法和属性
Spring AOP切面类型
- Advisor:代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:代表具有切入点的切面,可以指定拦截目标类的哪些方法
- IntroductionAdvisor:代表引介切面,针对引介通知而使用的切面
声明式 Spring AOP
Spring 创建一个 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。
ProxyFactoryBean 类中的常用可配置属性如表所示。
属性名称 | 描述 |
---|---|
target | 代理的目标对象 |
proxyInterfaces | 代理要实现的接口,如果有多个接口,则可以使用以下格式赋值: <list> <value ></value> ...... </list> |
proxyTargetClass | 是否对类代理而不是接口,设置为 true 时,使用 CGLIB 代理 |
interceptorNames | 需要植入目标的 Advice |
singleton | 返回的代理是否为单例,默认为 true(返回单实例) |
optimize | 当设置为 true 时,强制使用 CGLIB |
Advisor一般切面案例
在 Spring 通知中,环绕通知是一个非常典型的应用。下面通过环绕通知的案例演示 Spring 创建 AOP 代理的过程。
1、导入 JAR 包
- spring-aop:是 Spring 为 AOP 提供的实现,在 Spring 的包中已经提供。
- aopalliance:是 AOP 提供的规范
2、创建切面类 MyAspect
/**
* 切面类中确定通知,需要实现不同接口,接口就是规范,从而就确定方法名称。
* * 采用“环绕通知” MethodInterceptor
*
*/
public class MyAspect implements MethodInterceptor {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("方法执行之前");
// 执行目标方法
Object obj = mi.proceed();
System.out.println("方法执行之后");
return obj;
}
}
上述代码中,MyAspect 类实现了 MethodInterceptor 接口,并实现了接口的 invoke() 方法。MethodInterceptor 接口是 Spring AOP 的 JAR 包提供的,而 invoke() 方法用于确定目标方法 mi,并告诉 Spring 要在目标方法前后执行哪些方法,这里为了演示效果在目标方法前后分别向控制台输出了相应语句。
3、创建 Spring 配置文件
<?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">
<!--目标类 -->
<bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl" />
<!-- 通知 advice -->
<bean id="myAspect" class="com.mengma.factorybean.MyAspect" />
<!--生成代理对象 -->
<bean id="customerDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理实现的接口 -->
<property name="proxyInterfaces" value="com.mengma.dao.CustomerDao" />
<!--代理的目标对象 -->
<property name="target" ref="customerDao" />
<!--用通知增强目标 -->
<property name="interceptorNames" value="myAspect" />
<!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />
</bean>
</beans>
上述代码中,首先配置目标类和通知,然后使用 ProxyFactoryBean 类生成代理对象;配置了代理实现的接口;配置了代理的目标对象;配置了需要植入目标的通知;当value 属性值为 true 时,表示使用 CGLIB 代理,属性值为 false 时,表示使用 JDK 动态代理。
4、创建测试类
public class FactoryBeanTest {
@Test
public void test() {
String xmlPath = "com/mengma/factorybean/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
CustomerDao customerDao = (CustomerDao) applicationContext
.getBean("customerDaoProxy");
customerDao.add();
customerDao.update();
customerDao.delete();
customerDao.find();
}
}
PointcutAdvisor有切入点的切面案例
- 使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面
- 常用PointcutAdvisor实现类
- DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面
- JdkRegexpMethodPointcut:构造正则表达式切点
1、创建实现类 CustomerDao
public class CustomerDao {
public void add() {
System.out.println("添加客户...");
}
public void update() {
System.out.println("修改客户...");
}
public void delete() {
System.out.println("删除客户...");
}
public void find() {
System.out.println("修改客户...");
}
}
2、创建切面类 MyAspect
/**
* 切面类中确定通知,需要实现不同接口,接口就是规范,从而就确定方法名称。
* * 采用“环绕通知” MethodInterceptor
*
*/
public class MyAspect implements MethodInterceptor {
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("方法执行之前");
// 执行目标方法
Object obj = mi.proceed();
System.out.println("方法执行之后");
return obj;
}
}
3、创建 Spring 配置文件
<?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">
<!--目标类 -->
<bean id="customerDao" class="com.mengma.dao.CustomerDao" />
<!-- 通知 advice -->
<bean id="myAspect" class="com.mengma.factorybean.MyAspect" />
<!--一般的切面是使用通知作为切面的,因为要对目标类的某个方法做增强,需要配置带有切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正则表达式,.任意字符,*任意次数-->
<!--<property name="pattern" ref=".*add.*" />-->
<property name="patterns" ref=".*add.*,.*update.*" />
<property name="advice" ref="myAspect" />
</bean>
<!--生成代理对象 -->
<bean id="customerDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!--代理实现的接口 -->
<property name="proxyInterfaces" value="com.mengma.dao.CustomerDao" />
<!--代理的目标对象 -->
<property name="target" ref="customerDao" />
<!--用通知增强目标 -->
<property name="interceptorNames" value="myAdvisor" />
<!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />
</bean>
</beans>
4、创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class FactoryBeanTest {
@Resource(name="customerDaoProxy")
private CustomerDao customerDao;
@Test
public void test() {
customerDao.add();
customerDao.update();
customerDao.delete();
customerDao.find();
}
}
自动创建代理
前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的bean每个都配置ProxyFactoryBean开发维护量巨大。
解决方案:自动创建代理
- BeanNameAutoProxyCreator:根据Bean名称自动创建代理
- DeafultAdvisorAutoProxyCreator:根据Advisor本身包含信息创建代理
- AnnotationAwareAspectJAutoProxyCreator:基于Bean中的AspectJ注解进行自动代理
BeanNameAutoProxyCreator方式
这个方式其他配置和之前的一样,只是使用BeanNameAutoProxyCreator替换ProxyFactoryBean
<!--生成代理对象 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao" />
<!--用通知增强目标 -->
<property name="interceptorNames" value="myAdvisor" />
<!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />
</bean>
DeafultAdvisorAutoProxyCreator方式
这个方式其他配置和之前的一样,只是使用DeafultAdvisorAutoProxyCreator替换BeanNameAutoProxyCreator
<!--配置切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--<property name="pattern" ref="com\.yibo\.dao\.CustomerDao\.save" />-->
<property name="advice" ref="myAspect" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DeafultAdvisorAutoProxyCreator">
</bean>
参考:
http://c.biancheng.net/view/4268.html