Spring AOP四种实现方式Demo详解与相关知识探究

一、AOP相关概念

(1)AOP是什么?AOP与拦截器的区别?

        AOP通常叫面向切面编程(Aspect-oriented Programming,简称AOP),它是一种编程范式,通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术。
        通常用来对隔离不同业务逻辑,比如常见的事务管理、日志管理等。同时实现AOP的方式也有两种:cglib 以及 jdk两种方式来实现。

(2)使用AOP需要的一些概念。

1.通知(Advice)

    通知定义了在切入点代码执行时间点附近需要做的工作。支持五种类型的通知:

  • Before(前)  org.apringframework.aop.MethodBeforeAdvice
  • After-returning(返回后) org.springframework.aop.AfterReturningAdvice
  • After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
  • Arround(周围) org.aopaliance.intercept.MethodInterceptor
  • Introduction(引入) org.springframework.aop.IntroductionInterceptor

2.连接点(Joinpoint)

    程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等。

3.切入点(Pointcut)

    通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

4.切面(Aspect)

    通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。

5.引入(Introduction)

    引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。

6.目标(Target)

    即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合!

7.代理(proxy)

    应用通知的对象,详细内容参见设计模式里面的动态代理模式。

8.织入(Weaving)

    把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:

    (1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;

    (2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;

    (3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。

二、使用AOP的几种方式

    1.经典的基于代理的AOP

    2.@AspectJ注解驱动的切面

    3.纯POJO切面

    4.注入式AspectJ切面

        Spring aop 旨在提供一个跨 Spring IoC 的简单的 aop 实现, 以解决程序员面临的最常见问题。它不打算作为一个完整的 AOP 解决方案 —— 它只能应用于由 Spring 容器管理的 bean。而AspectJ 是原始的 aop 技术, 目的是提供完整的 aop 解决方案。它更健壮, 但也比 Spring AOP 复杂得多。还值得注意的是, AspectJ 可以在所有域对象中应用。


 

三、Demo详解

        在讲Demo之前,先把项目结构贴一下,我用的的一般的Java Project+Maven进行测试,用Web Project的小区别一会会说到。有一点很重要,jar依赖必须导入正确,我在测试过程中,很多bug都是因为依赖问题引起的,这里也贴一下。

包结构:

pom.xml: 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
 
	<groupId>com.springAOP</groupId>
	<artifactId>springAOP</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
 
	<name>springAOP</name>
	<url>http://maven.apache.org</url>
 
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
	</properties>
 
 
	<dependencies>
 
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
 
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
 
		<!-- Spring AOP + AspectJ -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.8.9</version>
		</dependency>
		
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.8.9</version>
		</dependency>
	
	</dependencies>
</project>

下面开始正式的讲解:
1、经典的基于代理的AOP实现,以一个睡觉的例子实现。

(1)可睡觉的接口,任何可以睡觉的人或机器都可以实现它。

public interface Sleepable {
	public void sleep();
}

(2)接口实现类,“Me”可以睡觉,“Me”就实现可以睡觉的接口。

public class Me implements Sleepable{
	public void sleep() {
		System.out.println("\n睡觉!不休息哪里有力气学习!\n");
	}
}

        Me关注于睡觉的逻辑,但是睡觉需要其他功能辅助,比如睡前脱衣服,起床脱衣服,这里开始就需要AOP替“Me”完成!解耦!首先需要一个SleepHelper类。因为一个是切入点前执行、一个是切入点之后执行,所以实现对应接口。

public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
 
	public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
		System.out.println("睡觉前要脱衣服!");
	}
 
	public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
		System.out.println("起床后要穿衣服!");
	}
 
}


(4)最关键的来了,Spring核心配置文件application.xml配置AOP。

<?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-3.0.xsd">

        <!-- 定义被代理者 -->
    <bean id="me" class="com.springAOP.bean.Me"></bean>

        <!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
    <bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>

        <!-- 定义切入点位置 -->
    <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*sleep"></property>
    </bean>

        <!-- 使切入点与通知相关联,完成切面配置 -->
    <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="sleepHelper"></property>
        <property name="pointcut" ref="sleepPointcut"></property>
    </bean>

        <!-- 设置代理 -->
    <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 代理的对象,有睡觉能力 -->
        <property name="target" ref="me"></property>
        <!-- 使用切面 -->
        <property name="interceptorNames" value="sleepHelperAdvisor"></property>
        <!-- 代理接口,睡觉接口 -->
        <property name="proxyInterfaces" value="com.springAOP.bean.Sleepable"></property>
    </bean>
</beans>


其中:
       <beans>是Spring的配置标签,beans里面几个重要的属性:

xmlns:

       是默认的xml文档解析格式,即spring的beans。地址是http://www.springframework.org/schema/beans;通过设置这个属性,所有在beans里面声明的属性,可以直接通过<>来使                    用,比如<bean>等等。一个XML文件,只能声明一个默认的语义解析的规范。例如上面的xml中就只有beans一个是默认的,其他的都需要通过特定的标签来使用,比如aop,它自                  己有很多       的属性,如果要使用,前面就必须加上aop:xxx才可以。类似的,如果默认的xmlns配置的是aop相关的语义解析规范,那么在xml中就可以直接写config这种标签了。

xmlns:xsi:

       是xml需要遵守的规范,通过URL可以看到,是w3的统一规范,后面通过xsi:schemaLocation来定位所有的解析文件。

xmlns:aop:

       这个是重点,是我们这里需要使用到的一些语义规范,与面向切面AOP相关。

xmlns:tx:

       Spring中与事务相关的配置内容。

(5)测试类,Test,其中,通过AOP代理的方式执行Me的sleep()方法,会把执行前、执行后的操作执行,实现了AOP的效果!

public class Test {
	public static void main(String[] args){
		@SuppressWarnings("resource")
		//如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
	    //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
		ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
		Sleepable me = (Sleepable)appCtx.getBean("proxy");
		me.sleep();
	}
}

执行结果:

(6)通过org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator简化配置。

将配置文件中设置代理的代码去掉,加上:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

然后,在Test中,直接获取me对象,执行sleep方法,就可以实现同样的功能!通过自动匹配,切面会自动匹配符合切入点的bean,会被自动代理,实现功能!

 

2、更简单的方式,通过AspectJ提供的注解实现AOP。

(1)同样的例子,修改后的SleepHelper:

@Aspect
public class SleepHelper{
 
    public SleepHelper(){
    }
    
    @Pointcut("execution(* *.sleep())")
    public void sleeppoint(){}
    
    @Before("sleeppoint()")
    public void beforeSleep(){
        System.out.println("睡觉前要脱衣服!");
    }
    
    @AfterReturning("sleeppoint()")
    public void afterSleep(){
        System.out.println("睡醒了要穿衣服!");
    }
    
}

(2)在方法中,可以加上JoinPoint参数以进行相关操作,如:

//当抛出异常时被调用
    public void doThrowing(JoinPoint point, Throwable ex)
    {
        System.out.println("doThrowing::method "
                + point.getTarget().getClass().getName() + "."
                + point.getSignature().getName() + " throw exception");
        System.out.println(ex.getMessage());
    }

(3)然后修改配置为:

        <aop:aspectj-autoproxy />
	<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
	<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
	<!-- 定义被代理者 -->
	<bean id="me" class="com.springAOP.bean.Me"></bean>

(4)最后测试,一样的结果! 

public class Test {
	public static void main(String[] args){
		@SuppressWarnings("resource")
		//如果是web项目,则使用注释的代码加载配置文件,这里是一般的Java项目,所以使用下面的方式
	    //ApplicationContext appCtx = new ClassPathXmlApplicationContext("application.xml");
		ApplicationContext appCtx = new FileSystemXmlApplicationContext("application.xml");
		Sleepable me = (Sleepable)appCtx.getBean("me");
		me.sleep();
	}
}

3、使用Spring来定义纯粹的POJO切面(名字很绕口,其实就是纯粹通过<aop:fonfig>标签配置,也是一种比较简单的方式)。

(1)修改后的SleepHelper类,很正常的类,所以这种方式的优点就是在代码中不体现任何AOP相关配置,纯粹使用xml配置。

public class SleepHelper{
 
    public void beforeSleep(){
        System.out.println("睡觉前要脱衣服!");
    }
    
    public void afterSleep(){
        System.out.println("睡醒了要穿衣服!");
    }
}

(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:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
 
	<!-- 定义通知内容,也就是切入点执行前后需要做的事情 -->
	<bean id="sleepHelper" class="com.springAOP.bean.SleepHelper"></bean>
	<!-- 定义被代理者 -->
	<bean id="me" class="com.springAOP.bean.Me"></bean>
 
	<aop:config>
		<aop:aspect ref="sleepHelper">
			<aop:before method="beforeSleep" pointcut="execution(* *.sleep(..))" />
			<aop:after method="afterSleep" pointcut="execution(* *.sleep(..))" />
		</aop:aspect>
	</aop:config>
 
</beans>

 (3)配置的另一种写法

	<aop:config>
		<aop:aspect ref="sleepHelper">
            <aop:pointcut id="sleepHelpers" expression="execution(* *.sleep(..))" />
            <aop:before pointcut-ref="sleepHelpers" method="beforeSleep" />
            <aop:after pointcut-ref="sleepHelpers" method="afterSleep" />       	
        </aop:aspect>
	</aop:config>

四、AOP实现原理

学东西还是要深入进去的,推荐一篇网评还不错的博文,http://blog.csdn.net/moreevan/article/details/11977115/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值