什么是AOP的技术
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
Spring框架的AOP的底层实现
Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种
-
基于JDK的动态代理
必须是面向接口的,只有实现了具体接口的类才能生成代理对象 -
基于CGLIB动态代理
对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式,Spring的核心包已经集成了cglib的包
具体采用哪种是由Spring来决定的,如果代理的类实现了接口则使用JDK动态代理完成AOP, 如果没有实现接口则采用CGLIB动态代理完成AOP
AOP的相关术语
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点) :所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
- Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
- Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
- Target(目标对象):代理的目标对象
- Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
- Aspect(切面): 是切入点和通知的结合,以后咱们自己来编写和配置的
AspectJ的配置步骤
步骤一:创建JavaWEB项目,引入具体的开发的jar包
先引入Spring框架开发的基本开发包(4个核心包+2个日志包)
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.apache.log4j-1.2.15.jar
spring-beans-4.2.4.RELEASE.jar
spring-context-4.2.4.RELEASE.jar
spring-core-4.2.4.RELEASE.jar
spring-expression-4.2.4.RELEASE.jar
再引入Spring框架的AOP的开发包
spring-aop-4.2.4.RELEASE.jar
(aop包)
com.springsource.org.aopalliance-1.0.0.jar
(aop的规范,在依赖包中找,具体在\spring-framework-3.0.2.RELEASE-dependencies\org.aopalliance\com.springsource.org.aopalliance\1.0.0)
还需引入2个aspectJ的开发包
spring-aspects-4.2.4.RELEASE.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
(注意这个是在依赖包下的,具体在\spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE)
最后为了方便测试,我还引入了测试包
spring-test-4.2.4.RELEASE
导入完后,如下图所示:
步骤二:创建Spring的配置文件,引入具体的AOP的schema约束
约束头在哪里找? 还是找这个文件xsd-configuration.html,具体路径前面的文章也讲过.
复制这段约束头到核心配置文件中.
步骤三:编写和配置目标类
package blog.csdn.net.mchenys.dao;
public interface UserDao {
void saveUser();
void updateUser();
}
package blog.csdn.net.mchenys.dao.impl;
import blog.csdn.net.mchenys.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存用户成功...");
}
@Override
public void updateUser() {
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"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册目标类 -->
<bean id="userDao" class="blog.csdn.net.mchenys.dao.impl.UserDaoImpl"/>
</beans>
步骤四:编写和配置切面类
package blog.csdn.net.mchenys.dao;
/**
* 切面类
* @author mChenys
*
*/
public class MyAspectXml {
// 定义通知
public void log() {
System.out.println("记录日志...");
}
}
在核心配置文件中加入这句
<bean id="myAspectXml" class="blog.csdn.net.mchenys.dao.MyAspectXml"/>
步骤五:在配置文件中完成aop的配置
完整的xml配置如下:
<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册目标类 -->
<bean id="userDao" class="blog.csdn.net.mchenys.dao.impl.UserDaoImpl"/>
<!-- 注册切面类 -->
<bean id="myAspectXml" class="blog.csdn.net.mchenys.dao.MyAspectXml"/>
<!-- 完成aop的配置 -->
<aop:config>
<!-- 引入切面类 -->
<aop:aspect ref="myAspectXml">
<!--
aop:after :定义通知类型,常用的有5种通知类型,后面会介绍
method:切面类的方法
pointcut:切入点的表达式,用于指定要切入的目标类的方法, execution()固定写法,里面的表达式可以有简写方式,后面介绍
-->
<aop:after method="log" pointcut="execution(public void blog.csdn.net.mchenys.dao.impl.UserDaoImpl.saveUser())"/>
</aop:aspect>
</aop:config>
</beans>
测试
package blog.csdn.net.mchenys.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import blog.csdn.net.mchenys.dao.UserDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo1 {
@Resource(name = "userDao")
private UserDao userDao; // 通过ioc注入值(name的值对应的是xml配置中bean的id属性)
@Test
public void test1() {
userDao.saveUser();
}
}
测试结果如下:
可以可以发现,我们的目标方法执行之后会自动的运行定义在切面类MyAspectXml的log方法.说明我们的切面是生效了.至于为啥是在目标方法之后执行呢,那是因为我们在核心配置文件中定义的通知类型是aop:after
,那么下面就介绍一下常用的通知类型及其特点吧.
5种常用的AOP通知类型
-
前置通知
在目标类的方法执行之前执行。
配置文件信息:<aop:before />
应用:可以对方法的参数来做校验 -
最终通知
在目标类的方法执行之后执行,如果程序出现了异常,最终通知也会执行。
在配置文件中编写具体的配置:<aop:after />
应用:例如像释放资源 -
后置通知
方法正常执行后的通知。
在配置文件中编写具体的配置:<aop:after-returning />
应用:可以修改方法的返回值 -
异常抛出通知
在抛出异常后通知
在配置文件中编写具体的配置:<aop:after-throwing />
应用:包装异常的信息 -
环绕通知
方法的执行前后执行。
在配置文件中编写具体的配置:<aop:around />
要注意:目标的方法默认不执行,需要使用ProceedingJoinPoint对来让目标对象的方法执行。
由于第五点比较特殊,需要手动调用ProceedingJoinPoint的方法,这里介绍一下,其他都挺类似的,看英文单词也能猜到啥意思.
修改一下xml的aop通知类型及通知方法如下所示:
<aop:around method="around" pointcut="execution(public void blog.csdn.net.mchenys.dao.impl.UserDaoImpl.saveUser())"/>
修改切面类,添加一个通知方法
package blog.csdn.net.mchenys.dao;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类
* @author mChenys
*
*/
public class MyAspectXml {
// 定义通知
public void log() {
System.out.println("记录日志...");
}
//定义另一个通知方法
public void around(ProceedingJoinPoint joinPoint) {
System.out.println("目标方法执行前...");
//执行目标方法
try {
//proceed有带参数和不带参数的,主要看目标方法是否带参数,返回值也一样
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("目标方法执行h后...");
}
}
测试方法不变,测试结果如下:
切入点表达式的简写
完整表达式为:execution([权限修饰符] 返回值类型 包名.类名.方法名(参数))
简写规则如下:
1.权限修饰符非必填,如果目标方法是private的,则必填
2.返回值类型必写,根据你的方法来编写返回值。但是可以使用*
代替。
3.包名必写,中间的包名可以省略,例如 blog.csdn..*.
表示以指定报名开头下的任意包, 如果是*..*
则表示任意的包的结构
4.类名必写,也可以使用 *
号代替任何,同样有类似的写法:*DaoImpl
表示以什以什么结尾的类名
5.方法必写,也可以使用 *
号代替任何,或者以save*
表示以save开头的方法名
6.参数非必填,如果是一个参数可以使用 *
号代替,如果想代表任意参数使用..
以上面的配置为例
execution(public void blog.csdn.net.mchenys.dao.impl.UserDaoImpl.saveUser())
可以简写成
execution(* *..*.*DaoImpl.save*(..))