第一节:AOP简介
第二节:AOP的作用
对程序进行增强,不修改源码的情况下AOP可以进行权限校验、日志记录、性能监控、事务管理。
第三节:Spring底层的AOP实现原理
动态代理:
- JDK动态代理:只能对实现了接口的类产生代理。
- Cglib动态代理(类似于Javassist第三方代理技术):对没有实现接口的类产生代理对象,生成子类对象。
第四节:Spring中AOP的开发(AspectJ的XML方式)
AOP思想最早是由AOP联盟组织提出的,Spring是使用该思想最好的框架之一。
Spring的AOP有自己的实现方式(非常繁琐)。AspectJ是一个AOP的框架。Spring引入AspectJ作为自身AOP的开发。
Spring拥有两套AOP开发方式:
- Spring传统方式(弃用);
- Spring基于AspectJ的AOP的开发;
4.1 AOP的7个专业术语
- 4.1.1 增强(Advice)
增强就很好理解了,AOP(切面编程)是用来给某一类特殊的连接点,添加一些特殊的功能,那么我们添加的功能也就是增强啦~
比如:添加日志、管理事务。
不过增强不仅仅包含需要增加的功能代码而已,它还包含了方位信息。
那什么是方位信息呢?
方位信息就是相对于方法的位置信息,如:方法前、方法后、方法环绕
为什么要方位信息呢?切点不是确定了需要增强的位置了吗?
切点定位的是“在什么类的什么方法上”,也就是说,切点只是定位到了方法本身(也叫执行点,特殊的连接点),但是我们增强的内容是放在该方法的前面呢、后面呢?还是前后都要呢?这些切点却没有告诉我们,那么我们该如何确定具体位置呢?
所以,我们才需要用到方位信息,进一步的定位到具体的增强代码放置的位置。
咦?增强即包含了【功能】又包含了【方位】,那我是不是不用切点就可以匹配哪些方法,并添加功能了呢?
恩,确实如此,因为通过方位信息,虽然只是简单的描述了【功能】需要放在方法前、后、还是前后都要等信息,但是我们还是可以通过方位定位到位置。只不过,是匹配到所有类的所有方法!因为方位只是说明在方法前还是方法后,并没有要求是哪些类?哪些方法?
— So,我们可以直接使用增强来生成一个切面,而不需要切点,但这并不怎么推荐,因为它是匹配所有方法的。所以,我们才需要用切点来进一步确认位置。 - 4.4.2 切点(Pointcut)
一个项目中有很多的类,一个类有很多个连接点,当我们需要在某个方法前插入一段增强(advice)代码时,我们就需要使用切点信息来确定,要在哪些连接点上添加增强。
那么切点是什么?
如果把连接点当做数据库中的记录,那么切点就是查找该记录的查询条件。
所以,一般我们要实现一个切点时,那么我们需要判断哪些连接点是符合我们的条件的,如:方法名是否匹配、类是否是某个类、以及子类等。 - 4.4.3 连接点(Joinpoint)
连接点就是程序执行的某个特定的位置,如:类开始初始化前、类初始化后、类的某个方法调用前、类的某个方法调用后、方法抛出异常后等。Spring 只支持类的方法前、后、抛出异常后的连接点。 - 4.4.4 切面(Aspect)
切面由切点和增强(或引介)组成,或者只由增强(或引介)实现。 - 4.4.5 目标对象(Target)
目标对象就是我们需要对它进行增强的业务类~
如果没有AOP,那么该业务类就得自己实现需要的功能。 - 4.4.6 AOP代理(AOP proxy)
一个类被AOP织入后生成出了一个结果类,它是融合了原类和增强逻辑的代理类。 -
4.4.7 织入(Weaving)
织入就是将增强添加到目标类具体连接点上的过程。
编译期织入,这要求使用特殊java编译器
类装载期织入,这要求使用特殊的类装载器
动态代理织入,在运行期为目标类添加增强生成子类的方式
Spring采用的是动态代理织入,而AspectJ采用编译期织入和类装载期织入。
第五节:Spring的AOP开发入门
5.1 Spring测试类的使用
测试类中整合Spring框架的注入功能, 需引入Jar包“spring-test-4.2.4.RELEASE.jar”从而减少测试类中对Spring工厂对象的创建
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
//结合Spring对测试类进行属性注入
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserDaoServiceTest {
@Resource(name = "userDaoService")
private UserDaoService userDaoService;
@Test
public void demo01(){
userDaoService.save();
}
}
5.2 AOP切面编程的准备工作:
-
引入jar包(使用AspectJ的需求前需引入所需的jar包)
- 编写基础测试类以接口实现
public class ProductImpl implements Product {
@Override
public void save() {
System.out.println("保存商品");
}
@Override
public void update() {
System.out.println("更新商品");
}
@Override
public void search() {
System.out.println("查询商品");
}
@Override
public void del() {
System.out.println("删除商品");
}
}
- 通过配置文件,将该类交给Spring管理
<!--配置目标对象:被增强的对象-->
<bean id="product" class="com.seapp.spring02.ProductImpl"/>
- 编写切面类,并建立增强方法
public class MyAspect {
public void logInfo(){
System.out.println("实现日志增强");
}
}
- 将切面类交给Spring管理,并对目标对象配置增强
<!--将切面类交给Spring去管理-->
<bean id="myAspect" class="com.seapp.spring02.MyAspect"/>
<!--AOP配置-->
<aop:config>
<!--expression:是一个表达式,配置那些类的那些方法需要增强-->
<aop:pointcut id="pointcut01" expression="execution(* com.seapp.spring02.ProductImpl.save(..))"/>
<!--配置切面-->
<aop:aspect ref="myAspect">
<aop:before method="logInfo" pointcut-ref="pointcut01"/>
</aop:aspect>
</aop:config>
第六节:Spring的通知类型
- 前置通知:
在目标方法执行之前进行操作。 - 后置通知:
在目标方法执行之后进行操作。 - 环绕通知:
在目标方法执行之前和之后进行操作。 - 异常抛出通知:
在程序出现异常的时候进行的操作。 - 最终通知:
程序执行完成后进行操作。无论有无异常都会执行。 - 引介通知:(不常用)
//6.1 切面类的定义以及增强
public class MyAspect {
/**
* 前置通知
*/
public void logInfo(){
System.out.println("实现日志增强");
}
/**
* 后置通知
* @param result
*/
public void writeLog(Object result){
System.out.println("log日志获取" + result);
}
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知");
Object proceed = joinPoint.proceed();
System.out.println("环绕后通知");
return proceed;
}
/**
* 异常抛出通知
*/
public void afterThrowing(Throwable ex){
System.out.println("异常抛出信息" + ex );
}
/**
* 最终通知
*/
public void after(){
System.out.println("最终通知信息");
}
}
//接口实现类:
public class ProductImpl implements Product {
@Override
public void save() {
System.out.println("保存商品");
}
@Override
public String update() {
System.out.println("更新商品");
return "更新成功" ;
}
@Override
public void search() {
System.out.println("查询商品");
}
@Override
public void del() {
System.out.println("删除商品");
int i = 10/0;
}
}
//6.3 核心,在applicationContext.xml中关于切面的配置
<!--配置目标对象:被增强的对象-->
<bean id="product" class="com.seapp.spring02.ProductImpl"/>
<!--将切面类交给Spring去管理-->
<bean id="myAspect" class="com.seapp.spring02.MyAspect"/>
<!--AOP配置-->
<aop:config>
<!--expression:是一个表达式,配置那些类的那些方法需要增强-->
<aop:pointcut id="pointcut01" expression="execution(* com.seapp.spring02.ProductImpl.save(..))"/>
<aop:pointcut id="pointcut02" expression="execution(* com.seapp.spring02.ProductImpl.update(..))"/>
<aop:pointcut id="pointcut03" expression="execution(* com.seapp.spring02.ProductImpl.search(..))"/>
<aop:pointcut id="pointcut04" expression="execution(* com.seapp.spring02.ProductImpl.del(..))"/>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--前置通知配置-->
<aop:before method="logInfo" pointcut-ref="pointcut01"/>
<!--后置通知配置-->
<aop:after-returning method="writeLog" pointcut-ref="pointcut02" returning="result"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pointcut03"/>
<!--异常抛出信息-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut04" throwing="ex"/>
<!--最终通知信息-->
<aop:after method="after" pointcut-ref="pointcut04"/>
</aop:aspect>
</aop:config>
第七节:Spring切入点表达式
- 基于execution的函数完成的,语法定义如下:
[访问修饰符] 方法返回值 包名.类名.方法名(参数)
public void com.seapp.product.save(..)
//其中除以为其余字段都可以使用*号代替
* com.seapp.product.save(..)
* *.*.product.*(..)
* *.*.*.*save(..)//以
...
第八节:Spring使用AspectJ进行AOP注解开发方式
@Aspect//定义切面类的注解
/**通知类型:
*@Before :前置通知
*@AfterReturing :后置通知
*@Around :环绕通知
*@After:最终通知
*@AfterThrowing:异常抛出通知
**/
@poincut//定义切入点注解
切面类:
@Aspect
public class MyAspectAnno {
/**
* 前置通知
*/
@Before("MyAspectAnno.pointcut1()")
public void logInfo(){
System.out.println("实现日志增强");
}
/**
* 后置通知
*/
@AfterReturning("MyAspectAnno.pointcut4()" )
public void writeLog(){
System.out.println("log日志获取");
}
/**
* 环绕通知
*/
@Around("MyAspectAnno.pointcut2()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知");
Object proceed = joinPoint.proceed();
System.out.println("环绕后通知");
return proceed;
}
/**
* 异常抛出通知
*/
@AfterThrowing("MyAspectAnno.pointcut3()")
public void afterThrowing(){
System.out.println("异常抛出信息");
}
/**
* 最终通知
*/
@After("MyAspectAnno.pointcut4()")
public void after(){
System.out.println("最终通知信息");
}
/**
* 定义切入点
*/
@Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.search(..))")
private void pointcut1(){
}
@Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.save(..))")
private void pointcut2(){
}
@Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.del(..))")
private void pointcut3(){
}
@Pointcut("execution(* com.seapp.spring02.ProductDaoImpl.update(..))")
private void pointcut4(){
}
}
Spring中applicationContext.xml中的配置项:
<!--使用注解实现Spring的AspectJ AOP开发-->
<!--配置目标类-->
<bean id="productDao" class="com.seapp.spring02.ProductDaoImpl"/>
<!--配置切面类-->
<bean id="MyAspectAnno" class="com.seapp.spring02.MyAspectAnno"/>
<!--开启AOP注解的自动代理-->
<aop:aspectj-autoproxy/>