Spring AOP 初探

时间:2022-01-05 03:24:32

本文可作为北京尚学堂spring课程的学习笔记

首先谈谈什么是AOP 它能干什么

AOP Aspect Oriented Programming(面向切面的编程)

什么叫面向切面?

就是我们可以动态的在原来的程序逻辑前后(或其他特定的位置)加上我们自己想要的其他逻辑 这个原始的程序并不"知道" 我们在他前后做了补充

就例如我在前几篇文章里谈动态代理时说的 我要记录坦克运行所耗的时间 在它运动之前记录时间点 运动后记录时间点 然后记录时间差 而这个记录时间的动作与坦克本身的运动是没有关系的

既然谈到了动态代理 我建议如果没有看过动态代理这个设计模式的朋友在学习aop之前最好还是先看看 否则会有听不懂看不懂的危险(个人推荐 大家可以看看我前几篇文章 从坦克聊代理模式)



AOP有如下的几个概念

JoinPoint

PointCut

Aspect(切面)

Advice

Target

Weave

不过我现在并不准备和大家聊这个 等我们做出一个实例后 再说



我们还是做这样一个例子 在用户对数据库做插入操作前后 加一点别的操作

先是完整的项目图

Spring AOP 初探

最基本的spring

首先我们提炼出一个接口 往数据库里插入一个用户

package com.bjsxt.dao;

import com.bjsxt.model.User;

public interface UserDao {
    public void save(User u);

}

下面是UserDao的实现类UserDaoMysql 为了简便 我们只是打印出已经写入mysql

package com.bjsxt.dao;

import org.springframework.stereotype.Component;
import com.bjsxt.model.User;

@Component
public class UserDaoMysql implements UserDao {

    @Override
    public void save(User u) {
        // TODO Auto-generated method stub
        System.out.println("已经写入mysql");
    }
}

相应的service 不解释了

package com.bjsxt.services;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;
import com.bjsxt.dao.UserDao;
import com.bjsxt.dao.UserDaoMysql;

import com.bjsxt.model.User;

@Component
public class UserService {

    @Resource
    private UserDao userDaoMysql;

    public UserDao getUserDao() {
        return userDaoMysql;
    }

    public void setUserDao(UserDaoMysql userDao) {
        this.userDaoMysql = userDao;
    }

    public void Save(User u){
        userDaoMysql.save(u);
    }
}

相应的配置xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:security="http://www.springframework.org/schema/security"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="

		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

		<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
		<context:annotation-config/>
		<context:component-scan base-package="com.bjsxt"/>
	<!--	<aop:aspectj-autoproxy></aop:aspectj-autoproxy> -->
</beans>

这是测试函数 最简单的spring应用

package com.gc.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.bjsxt.model.User;
import com.bjsxt.services.UserService;
import com.gc.action.HelloWorld;

public class TestHelloWorld
{
    public static void main(String args[])
    {
        ApplicationContext actx=new ClassPathXmlApplicationContext("Spring-Customer.xml");
        User user=new User();
        UserService u=(UserService)actx.getBean("userService");
        u.Save(user);
    }
}

至于User就不赘述了 User里面一个username一个password 然后是getset方法



测试结果很简单 就是打印出一句

已经写入mysql

现在我们就给这个插入动作的前后加上日志记录功能

package com.bjsxt.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogInterceptor {
    @Pointcut("execution(public * com.bjsxt.services.UserService.Save(..))")
    public void myMethod(){};

    @Before("execution(public * com.bjsxt.services.UserService.Save(..))")
    public void beforess() {
        System.out.println("method before");
    }

    @After("execution(public * com.bjsxt.services.UserService.Save(..))")
    public void afterss() {
        System.out.println("method after");
    }

//    @Around("myMethod()")
//    public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
//        System.out.println("method around start");
//        pjp.proceed();
//        System.out.println("method around end");
//    }

}

写好这个类后 得引入四个jar文件

如下图

Spring AOP 初探

aopalliance-1.0.jar    aspectjrt.jar   aspectjweaver-1.6.12.jar  cglib-nodep-2.1_3.jar

多说几句 如出现error at ::0 can't find referenced pointcut这个问题

是因为 引入的aspectjweaver 版本太低

我自己用的是jdk1.7 eclipse3.7 spring3.2.0 aspectjweaver1.6.12

再一方面就是就是把xml里面

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

前后的注释去掉

///////////////以下为2015年10月2日 修订

最近在看spring的说明文档的时候,看到下面的示例:

Spring AOP 初探

咦?befor中还可以直接写方法,而不用加execution?

这个befor里面加不加execution有什么区别么?

百度了半天,没有找到区别,似乎都是加execution的

先不管了,试试不加execution的,结果报下面这个错误

error at ::0 can't find referenced pointcut

我升级了aspectjweaver,从1.8.6到1.7.3到1.7.4都是不行呀!!

在磨蹭了很近之后,我得出一个结论,before后面必须得加execution

后来,看了pointcut之后,我的结论又被推翻了,请看后面关于pointcut的说明

///////////////以上为2015年10月2日 修订

运行后结果

method before

已经写入mysql

method after



好 大功告成 现在咱们慢慢聊聊aop的实现过程

aop的过程

第一关于cglib

如果看了动态代理的朋友 现在肯定要抛出一个问题

     我们要代理的类 不是在jdk里要实现InvocationHandler接口么? 这里怎么没有

     (如果你没有想到这个问题 说明你动态代理掌握的还不够)

答案是spring用的是cglib这个工具来直接操纵二进制文件 自然就不用实习接口了 看看上面那个所需要的jar包图 是不是有一个cglib jar

第二 几个术语

JoinPoint

@Before("execution(public * com.bjsxt.services.UserService.Save(..))")
    public void beforess() {
        System.out.println("method before");
    }

上面的代码表示 我要在com.bjsxt.services包下UserService类里面的Save方法之前(参数不论 返回值不论 另外这个之前是因为 @Befor 而不是函数名 所以我给函数名后面加了ss 以示区别)执行beforess这个方法

这下说的够清楚了吧

com.bjsxt.services包下UserService类里面的Save方法就是JoinPoint

/////////////////以下为2015年10月2日修订

咱们再说说说execution里面的表达式

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)

            throws-pattern?)

modifiers-pattern指的是方法的修饰符,就是public private protected 后面有一个问号 表示这个参数可以不要

ret-type-pattern 返回值 不用多说

declaring-type-pattern 表示特定的类

name-pattern 特定的方法

param-pattern 参数

throws-pattern 异常的类型

关于这个,给几个例子大家看看就明白了了

Spring AOP 初探

掌握这么多就够了,剩下的那种特别复杂的语法,大家不看也罢

另外

	@Before("execution( public * com.bjsxt.service..*.add(..) ) ||  execution( public * com.bjsxt.service..*.delete(..)  ) ")
	public void before() {
		System.out.println("my method before");
	}

多个表达式也可以用||连接起来,既然有||那自然就用&&,有!喽

/////////////////以上为2015年10月2日修订

PointCut

@Pointcut("execution(public * com.bjsxt.services.UserService.Save(..))")
    public void myMethod(){};

这里我还是用了上面的那个JoinPoint 其实execution括号里面可以写一个"方法簇"

这一系列JoinPoint 合起来就是一个PointCut 我们给他起了一个名字叫myMethod

其他的操作在指定插入点的时候在Advice里面指定这个名字即可

    @Around("myMethod()")
    public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("method around start");
        pjp.proceed();
        System.out.println("method around end");
    }

请注意,这个myMehod不是一个方法,而是一个pointcut的名字,这pointcut是com.bjsxt.services.UserService.Save的所有不同返回值,不同参数的方法组成的集合

现在想想之前那个before,它后面是可以直接加一个字符串,并且不以execution开头

问题是那个字符串并不是指定在什么地方织入我们的逻辑,它只是用来表面我引用的是那个pointcut而已

Aspect

我们这个LogInterceptor类就是一个切面 类的前面我们打上了标签 @Aspect

Advice

就是执行附加逻辑的时间

@Before @After @AfterReturning    @AfterThrowing @Around

最常用的也就是这几个 前两个我想不用解释了

/////////////////一下为2015年10月2日修订

@AfterThrowing这个是抛出异常后执行的,这个我们举个例子

	@AfterThrowing(pointcut="myMethod()",throwing="ex")
	public void afterThrowdMethod(DataAccessException ex)  {
		System.out.println(ex.getMessage()+" ex message");
		System.out.println("after throw");
	}
上面的代码说明,在myMethod指定的pointcut范围内,如果抛出了DataAccessException类型的exception,就执行下面的方法(当然,如果抛出的是其他类型的,这里就不管了)
AfterThrowing在什么地方有应用?
struts2的异常处理。

////////////////以下为2015年10月2日修订

@Around是可以同时在所拦截方法的前后执行一段逻辑

例如上面那个aroundMethod方法 单个运行它(为什么是单个 大家可能会想pjp.proceed()前面的逻辑和 @Before里面的逻辑哪个先执行呢? 这个没有什么意思 大家自己做个实验就ok 总之一句话 不要把 前后的逻辑顺序按照这个around和before来区别)

结果是

method around start

已经写入mysql

method around end

当我把aroundMethod方法改成如下

@Around("myMethod()")
    public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("method around start");
        pjp.proceed();
        System.out.println("method around end");
        pjp.proceed();
        System.out.println("method around endssss");
    }

结果就成了

method around start

已经写入mysql

method around end

已经写入mysql

method around endssss

这个 @Around里面pjp.proceed();就是指代原始逻辑的运行

Weave

weave是编织的意思 aop是这样的运作的 "横"着运行业务逻辑 "纵"着加入其它如日志权限等操作。

就像织布一样横着看是一种逻辑 纵着看又是一种逻辑。

Weave一般翻译成织入 就表示这个过程。

使用xml配置aop

这个很简单,大家一看就懂

package com.bjsxt.aop;

import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Component;

@Component
public class LogInterceptorXML {

	public void beforess() {
		System.out.println("my method before");
	}

	public void afterThrowdMethod(DataAccessException ex)  {
		System.out.println("after throw");
	}

}
<?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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
	<context:component-scan base-package="com.bjsxt"/>

	<aop:config>
		<aop:pointcut
			expression="execution( public * com.bjsxt.service..*.add(..) ) ||
						execution( public * com.bjsxt.service..*.delete(..)  ) "
			id="myPointCut" />

		<aop:aspect ref="logInterceptorXML">
			<aop:before method="beforess" pointcut-ref="myPointCut" />
		</aop:aspect>

	</aop:config>

</beans>

如果会用注解形式的aop,那么xml式的真的就是看一眼就懂。

这里,我还得说,相比较于注解形式,我们更得掌握xml式的配置。 为什么? 因为spring与hibernate结合后,aop的一大亮点就是声明式事务管理

参考资料

http://***/forum/posts/list/281.html

http://outofmemory.cn/code-snippet/3025/spring-AOP-Around-Before-After-differentiate

http://blog.csdn.net/wanglang3081/article/details/17164207

http://jinnianshilongnian.iteye.com/blog/1415606