什么是AOP?
面向切面编程 (AOP是Aspect Oriented Program的字母首写)。
意为:在程序运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。
AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
在Spring中使用AOP功能
Spring中集成了AOP功能,如果我们要使用AOP功能,只需要导入相应的jar包、并做相应配置即可使用。 下面我们简单的使用一下Spring中的AOP功能。
1. 引入aop相关的jar文件
spring-aop.4.3.2.RELEASE.jar // spring源码中可以找到
aopalliance.jar // aopalliance 可在网上下载
aspectjweaver.jar // aspectj 源码
aspectjrt.jar // aspectj 源码
2. 命名空间
在spring的配置文件中需要引入和aop相关的命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
分别是上面三条。下面是配置文件的完整约束:
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
3. 开启aop注解
在配置文件中加入下方的配置,表示开启aop功能
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
4. 使用aop注解
**1. 我们再IOC容器中声明需要加入容器的类。UserDao、Aop。两个类的内容如下:
UserDao :
public class UserDao implements IUserDao {
@Override
public void save() {
System.out.println("------业务方法 : 数据存储完毕------");
}
}
Aop :
@Aspect
public class Aop {
@Before("execution(* com.mon7ey.proxy.dao.impl.UserDao.*(..))")
public void before(){
System.out.println("业务逻辑执行之前做一些羞羞的事情!");
}
@After("execution(* com.mon7ey.proxy.dao.impl.UserDao.*(..))")
public void after(){
System.out.println("业务逻辑执行之后抽一根完事烟。!");
}
}
@Aspect:被这个注解修饰的类将会被Spring视为 “切面类”。
@Before:这个注解用来修饰 “切面类”中的方法。这些方法我们通常叫它 “切入点”。被这个注解修饰的方法表示该方法会在核心方法执行之前执行。
@After:含义和@Before注解相同。被这个注解修饰的方法表示在核心方法执行之后执行。
"execution(* com.mon7ey.proxy.dao.impl.UserDao.*(..))":注解括号内的字符串叫做“切入点表达式”。用来指定哪些类的哪些方法需要在执行时进行扩展 一一 即为哪些类生成代理对象。表达式的具体含义我们会在后面细说,这里只是大致了解一下。
2. 切面类编写完成后,我们在IOC容器中配置这两个类:
<bean id="userDao" class="com.mon7ey.proxy.dao.impl.UserDao"></bean>
<bean id="aop" class="com.mon7ey.proxy.aop.Aop"></bean>
做完这些配置后,我们就已经完成了aop的中代码的植入了。下面写个测试方法测试一下。
@Test
public void testAop() throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("com/mon7ey/proxy/bean.xml");
// IUserDao proxy = (IUserDao) ac.getBean("proxyUserDao");
IUserDao proxy = (IUserDao) ac.getBean("userDao");
proxy.save();
}

常用的AOP注解
上面我们使用了@Before、@After 注解,下面我们介绍下其他注解
@Before() 前置通知:在被拦截的方法之前执行。
@After() 后置通知:在被拦截的方法执行之后执行。(始终执行)。
@AfterReturning() 返回后通知 :在方法执行完成后执行,如果被拦截的方法抛出异常则不执行。
@AfterThrowing() 异常通知:被拦截的方法内发生异常时执行。
@Around() 环绕通知: 环绕目标方法通知,类似于前置 + 后置
@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
下面我们用代码演示下@AfterReturning、@AfterThrowing()、@Around()、@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))")的具体使用。我们还是以上面的代码为例,使用UserDao和Aop类做演示。
@AfterReturning(): 返回后通知
在Aop类中添加方法afterReturning(),并在方法上添加@AfterReturning()注解
@AfterReturning("execution(* com.mon7ey.proxy.dao.impl.UserDao.*(..))")
public void afterReturning(){
System.out.println("afterReturning: 返回后通知!");
}
运行测试方法查看结果:

从运行结果中我们看到,返回后通知会在业务方法执行完成后执行,类似于After注解的效果。但是我们前面说过@AfterReturning()是在被拦截方法“执行完成”后执行,如果被拦截方法中出现错误导致方法未执行完成呢?
我们再被拦截方法中加入一句代码,让它报错,然后再看下结果
public class UserDao implements IUserDao {
// @Override
public void save() {
System.out.println("------业务方法 : 数据存储完毕------");
// 出错代码
int i = 1 / 0;
}
}
任何数都不能除以0,所以核心代码是不会执行完成。运行测试方法查看结果:

这时我们看到,被拦截的方法没有执行完成,被@AfterReturning修饰的方法是不会运行,但是@After依然会执行,这就是他们二者的区别。
@AfterThrowing():异常通知
在Aop类中创建afterThrowing方法,并用@AfterThrowing()修饰。 依然使用上面存在错误的UserDao进行测试
@AfterThrowing("execution(* com.mon7ey.proxy.dao.impl.UserDao.*(..))")
public void afterThrowing(){
System.out.println("@AfterThrowing: 异常通知。");
}
测试结果:

可以看到,被拦截的方法报错。被@AfterThrowing修饰的方法执行了
@Around(): 环绕通知
在Aop类中创建around方法。并用注解修饰
@Around("execution(* com.mon7ey.proxy.dao.impl.UserDao.*(..))")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
运行结果:

@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))") 指定切入点表达式
如果每个切入方法要我们写一遍切入点表达式,那么在方法很多的请款,就会很痛苦,而且切入点表达式是由字符串组成的,如果不小心写错了。字符串异常排除是非常麻烦。@Pointcut()注解就是为了解决这个问题的
我们用@Before注解和@Pointcut注解配合使用。
@Pointcut("execution(* cn.itcast.e_aop_anno.*.*(..))")
public void pointCut_(){
}
// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}
这样写的结果。单独在@Before()中写入切入点表达式相同
被@Pointcut修饰的方法的方法名可以随意写。其他方法想要是用被@Pointcut修饰的方法上的切入点表达式时,只需在响应的注解中填入对应的方法名即可。
通过配置文件使用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"
xmlns:p="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- dao 实例 -->
<bean id="userDao" class="cn.itcast.f_aop_xml.UserDao"></bean>
<bean id="orderDao" class="cn.itcast.f_aop_xml.OrderDao"></bean>
<!-- 切面类 -->
<bean id="aop" class="cn.itcast.f_aop_xml.Aop"></bean>
<!-- Aop配置 -->
<aop:config>
<!-- 定义一个切入点表达式: 拦截哪些方法 -->
<aop:pointcut expression="execution(* cn.itcast.f_aop_xml.*.*(..))" id="pt"/>
<!-- 切面 -->
<aop:aspect ref="aop">
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt"/>
<!-- 前置通知: 在目标方法调用前执行 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 后置通知: -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
网友评论