【一步一步学习spring】spring传统aop
提前说明一下,实际开发中一般不会使用传统aop进行开发,而是会使用aspectJ。但是这个是aspectJ的基础,有助于理解。
1. 通知类型
- AOP联盟为通知advice定义了org.aopalliance.aop.Interface.Advice,即接口规范
- Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
- 前置通知 org.springframework.aop.MethodBeforeAdvice
- 在目标方法执行前实施增强
- 后置通知 org.springframework.aop.AfterReturningAdvice
- 在目标方法执行后实施增强
- 环绕通知 org.aopalliance.intercept.MethodInterceptor
- 在目标方法执行前后实施增强
- 异常抛出通知 org.springframework.aop.ThrowsAdvice
- 在方法抛出异常后实施增强
- 前置通知 org.springframework.aop.MethodBeforeAdvice
2. 切面类型
- Advisor:代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法
3. 具体使用方式
3.1 准备工作
- pom.xml中引入传统aop依赖包
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
- 创建接口类
package com.aop.traditional;
public interface StudentDao {
public void find();
public void save();
public void update();
public void delete();
}
- 创建接口实现类
package com.aop.traditional;
public class StudentDaoImpl implements StudentDao {
public void find() {
System.out.println("查询");
}
public void save() {
System.out.println("保存");
}
public void update() {
System.out.println("更新");
}
public void delete() {
System.out.println("删除");
}
}
- 创建配置文件
<?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="studentDao" class="com.aop.traditional.StudentDaoImpl"></bean>
</beans>
3.2 普通切面增强整个目标类
我们要告诉spring,我们要在哪对谁进行增强啥。
- 创建前置通知类
package com.aop.traditional;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeAdvice implements MethodBeforeAdvice {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置通知=========");
}
}
- 配置文件注册bean
<!-- 前置通知类型 -->
<bean id="beforeAdvice" class="com.aop.traditional.BeforeAdvice"/>
- spring aop产生代理对象,借助ProxyFactoryBean产生代理对象。重要的属性如下:
<!-- spring aop产生代理对象 -->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标类名称 -->
<property name="target" ref="studentDao"/>
<!-- 目标类实现的接口类名称 -->
<property name="proxyInterfaces" value="com.aop.traditional.StudentDao"/>
<!-- 拦截器名称,需要织入目标的advice -->
<property name="interceptorNames" value="beforeAdvice"/>
<!-- 是否是对类代理而不是接口,设置为true时,使用cglib代理 -->
<property name="proxyTargetClass" value="false"/>
<!-- 返回代理是否为单例 -->
<property name="singleton" value="true"/>
<!-- 是否强制使用cglib -->
<property name="optimize" value="false"/>
</bean>
- 调用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context-aop.xml")
public class SpringDemo {
@Resource(name="studentDaoProxy")
private StudentDao studentDao;
@Test
public void demo1() {
studentDao.save();
studentDao.find();
studentDao.update();
studentDao.delete();
}
}
3.3 PointcutAdvisor 带切点的切面增强类方法
- 使用普通advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面。
- 常用的PointcutAdvisor实现类
- DefaultPointcutAdvisor
- RegexpMethodPointcut 正则表达式切点,推荐用这个
3.2中的栗子使用RegexpMethodPointcut 实现,只需要修改xml中的配置项即可,有一次显示了aop的灵活性。
对delete和save方法进行前置通知增强。
<!-- 配置目标类 -->
<bean id="studentDao" class="com.aop.traditional.StudentDaoImpl" />
<!-- 前置通知类型 -->
<bean id="beforeAdvice" class="com.aop.traditional.BeforeAdvice" />
<bean id="myadvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns" value="com.aop.traditional.StudentDaoImpl.delete,.*save.*" />
<property name="advice" ref="beforeAdvice" />
</bean>
<!-- spring aop产生代理对象 -->
<bean id="regStudentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标类名称 -->
<property name="target" ref="studentDao" />
<!-- 是否是对类代理而不是接口,设置为true时,使用cglib代理 -->
<property name="proxyTargetClass" value="true" />
<!-- 拦截器名称,需要织入目标的advice -->
<property name="interceptorNames" value="myadvisor" />
<!-- 返回代理是否为单例 -->
<property name="singleton" value="true" />
<!-- 是否强制使用cglib -->
<property name="optimize" value="false" />
</bean>
3.4 自动创建代理
- 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量都巨大。
- 解决方案:自动创建代理
- BeanNameAutoProxyCreator 根据Bean名称创建代理,比如:所有后边带dao的bean都代理
- DefaultAdvisorAutoProxyCreator 根据advisor中的信息创建代理
- AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理,这个在后边还会讲,此处略过。
3.4.1 BeanNameAutoProxyCreator 举例
对所有以dao结尾的bean的所有方法使用代理,注意该方式又只能是所有方法!!!!
<!-- 配置目标类 -->
<bean id="studentDao" class="com.aop.traditional.StudentDaoImpl" />
<!-- 前置通知类型 -->
<bean id="beforeAdvice" class="com.aop.traditional.BeforeAdvice" />
<!-- 环绕通知类型 -->
<bean id="aroundAdvice" class="com.aop.traditional.AroundAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="beforeAdvice, aroundAdvice"/>
</bean>
细心的话,可以看到,BeanNameAutoProxyCreator是没有设置id的,说明我们不会主动注入。这个其实是在bean的postProcessAfterInitialization的声明周期阶段执行的,即是在bean初始化阶段就将增强注入进去了。这个跟其他的方式不同,其他的是先创建bean,在对bean进行代理。
3.4.2 DefaultAdvisorAutoProxyCreator 举例
<bean id="myadvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns" value="com.aop.traditional.StudentDaoImpl.delete,.*save.*" />
<property name="advice" ref="beforeAdvice" />
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
这个DefaultAdvisorAutoProxyCreator将扫描上下文,寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成),将这些Advisor应用到所有符合切入点的Bean中 。