【一步一步学习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
      • 在方法抛出异常后实施增强

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中 。

posted @ 2018-09-06 15:29  麦兜爱学习  阅读(530)  评论(0编辑  收藏  举报