SpringAOP编程的作用是:对spring管理的所有类,在不改变类代码的情况下,对类中的方法进行增强
1.什么是AOP
AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想。
AOP思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码
AOP应用场景:
- 场景一: 记录日志
- 场景二: 监控方法运行时间 (监控性能)
- 场景三: 权限控制
- 场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
- 场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )
2.Spring AOP编程两种方式
- Spring 1.2 开始支持
AOP编程 (传统SpringAOP 编程)
,编程非常复杂 ---- 更好学习Spring 内置传统AOP代码 - Spring 2.0 之后支持
第三方 AOP框架(AspectJ )
,实现另一种 AOP编程 – 推荐
3.AOP编程相关术语
- Aspect(切面)
是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容—它的功能、在何时和何地完成其功能 - joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点. - Pointcut(切入点)
所谓切入点是指我们要对哪些joinpoint进行拦截的定义.
通知定义了切面的”什么”和”何时”,切入点就定义了”何地”. - Advice(通知)
所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) - Target(目标对象)
代理的目标对象 - 织入
是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象 - Introduction(引入)
在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
4.Spring AOP方式
参考spring第三天
传统的AOP编程- xml方式
- 注解方式
4.1 传统的AOP编程(了解即可)
面向切面编程开发步骤(动态织入)
- 1、确定目标对象(target—>bean)
- 2、编写Advice通知方法 (增强代码)
- 3、配置切入点和切面
传统SpringAOP的Advice 必须实现对应的接口!
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
- 1.前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强 - 2.后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强 - 3.环绕通知 org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强 - 4.异常抛出通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常后实施增强 - 5.引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性
步骤1.编写传统AOP的通知类(切面要做的事情)
这里使用环绕通知
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
//传统的aop的advice通知,增强类,必须实现org.aopalliance.intercept.MethodInterceptor接口(注意和cglib代理接口区分开)
public class TimeLogInterceptor implements MethodInterceptor {
//log4j记录器
private static Logger LOG=Logger.getLogger(TimeLogInterceptor.class);
//回调方法
//参数:目标方法回调函数的包装类,获取调用方法的相关属性、方法名、调用该方法的对象(即封装方法、目标对象,方法的参数)
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//业务:记录目标的方法的运行时间
//方法调用之前记录时间
long beginTime = System.currentTimeMillis();
//目标对象原来的方法的调用,返回目标对象方法的返回值。
Object object = methodInvocation.proceed();//类似于invoke
//方法调用之后记录时间
long endTime = System.currentTimeMillis();
//计算运行时间
long runTime=endTime-beginTime;
//写日志:
/**
* 1:记录日志到数据库(优势:便于查询;劣势:要占用数据库空间,日志一般都非常庞大)
* 2:记录日志到log4j(优势:文件存储,可以记录非常大的日志数据,而且还有日志级别的特点;劣势:不便于查询)
*/
LOG.info("方法名为:"+methodInvocation.getMethod().getName()+"的运行时间为:"+runTime+"毫秒");
return object;
}
}
要增强的target对象,对于spring来说,目标:就是bean对象
public class CustomerServiceImpl implements ICustomerService {
@Override
public void save() {
System.out.println("客户保存了。。。。。");
}
@Override
public int find() {
System.out.println("客户查询数量了。。。。。");
return 100;
}
}
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
}
public int find() {
System.out.println("商品查询数量了。。。。。");
return 99;
}
}
使用SpringTest进行测试
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;
//测试
@Test
public void test(){
//基于接口
customerService.save();
customerService.find();
//基于类的
productService.save();
productService.find();
}
}
但是发现,此时并没有执行TimeLogInterceptor 类的invoke()方法,也就是说,并没有计算执行Service类的时间,那怎么办呢?我们往下看,需要在spring容器中配置spring的aop。
这是因为我们并没有配置切入点和切面
我们只编写了通知类,知道了要对目标对象进行如何增强,但通知类和目标对象还需要通过xml建立关系
步骤2.配置切入点和切面
目的:让哪个类(切面)、哪个方法(切入点),进行怎样的增强(通知)。
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.advice.CustomerServiceImpl"/>
<!-- 基于一般类(没有实现接口的类) -->
<bean id="productService" class="cn.itcast.advice.ProductService"/>
<!--
2.配置增强:原则bean能增强bean
Advice:通知,增强
-->
<bean id="timeLogAdvice" class="cn.itcast.advice.TimeLogInterceptor"/>
<!-- 配置切入点和切面 :aop:config-->
<aop:config>
<!--
配置切入点:即你要拦截的哪些 连接点(方法)
* expression:表达式:匹配方法的,语法:使用aspectj的语法,相对简单
* 表达式:bean(bean的名字),你要对哪些bean中的所有方法增强
* bean(customerService):要对customerservice的bean中的所有方法进行增强
-->
<!--
<aop:pointcut expression="bean(customerService)" id="myPointcut"/>
* expression=bean(*Service):在spring容器中,所有以Service单词结尾的bean的都能被拦截
* id="myPointcut":为切入点定义唯一标识
-->
<!--
<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
* 配置切面:通知(增强的方法)关联切入点(目标对象调用的方法)
* 告诉:你要对哪些方法(pointcut),进行怎强的增强 (advice)
-->
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
</aop:config>
切入点表达式的语法整理如下(aspectj的语法):
- bean(bean Id/bean name)
例如 bean(customerService) 增强spring容器中定义id属性/name属性为customerService的bean中所有方法 - execution(<访问修饰符>?<返回类型>空格<方法名>(<参数>)<异常>?)
例如:
execution(* cn.itcast.spring.a_jdkproxy.CustomerServiceImpl.(…)) 增强bean对象所有方法
execution( cn.itcast.spring….(…)) 增强spring包和子包所有bean所有方法
提示:最灵活的
4.2 AspectJ 切面编程(xml方式)
普通的pojo即可。(不需要实现接口)
AspectJ提供不同的通知类型:
- Before 前置通知,相当于BeforeAdvice
- AfterReturning 后置通知,相当于AfterReturningAdvice
- Around 环绕通知,相当于MethodInterceptor
- AfterThrowing抛出通知,相当于ThrowAdvice
- After 最终final通知,不管是否异常,该通知都会执行
- DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
第一步:确定目标对象,即确定bean对象:
<!-- 1.确定了要增强的target对象 -->
<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基于一般类 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
第二步:编写Before 前置通知Advice增强 :
不用实现接口
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//前置通知
//普通的方法。方法名随便,但也不能太随便,一会要配置
public void firstbefore(){
System.out.println("------------第一个个前置通知执行了。。。");
}
}
第三步:配置切入点和切面(让切入点关联通知) :
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>
<!-- 3:配置aop -->
<aop:config>
<!-- 切入点:拦截哪些bean的方法 -->
<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
<!--
切面:要对哪些方法进行怎样的增强
aop:aspect:aspejctj的方式!
ref:配置通知
-->
<aop:aspect ref="myAspectAdvice">
<!-- 第一个前置通知 :在访问目标对象方法之前,先执行通知的方法
method:advice类中的方法名,
pointcut-ref="myPointcut":注入切入点
目的是让通知关联切入点
-->
<aop:before method="firstbefore" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
和传统的aop配置相比,更灵活,advice不需要实现接口,简单的pojo就可以了;一个通知可以增强多个连接点,一个连接点可以被多次增强。
和传统aop的对比发现,aspectj更灵活,一个类中可以写N个增强方法,但传统的只能是一个类对应一个方法。
4.3 @Aspectj注解配置切面编程
1.创建目标对象
//实现类
/**
* @Service("customerService")
* 相当于spring容器中定义:
* <bean id="customerService" class="cn.itcast.spring.a_aspectj.CustomerServiceImpl">
*/
@Service("customerService")
public class CustomerServiceImpl implements CustomerService{
public void save() {
System.out.println("客户保存了。。。。。");
}
public int find() {
System.out.println("客户查询数量了。。。。。");
return 100;
}
}
//没有接口的类
/**
* @Service("productService")
* 相当于spring容器中定义:
* <bean id="productService" class="cn.itcast.spring.a_aspectj.ProductService">
*/
@Service("productService")
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
}
public int find() {
System.out.println("商品查询数量了。。。。。");
return 99;
}
}
使用bean注解的扫描(自动开启注解功能)
<!-- 1。确定目标 -->
<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>
2.编写通知,配置切面
编写通知类,在通知类 添加@Aspect 注解,代表这是一个切面类,并将切面类交给spring管理(能被spring扫描到@Component)
。
@Component(“myAspect”):将增强的类交给spring管理,才可以增强
@Aspect:将该类标识为切面类(这里面有方法进行增强),相当于<aop:aspect ref=”myAspect”>
//advice通知类增强类
@Component("myAspect")//相当于<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相当于<aop:aspect ref="myAspect">
public class MyAspect {
}
在切面的类,通知方法上添加
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)
在spring容器中开启AspectJ 注解自动代理机制
使用<aop:aspectj-autoproxy/>
作用:能自动扫描带有@Aspect的bean,将其作为增强aop的配置,有点相当于:<aop:config>
<!-- 1。确定目标 -->
<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>
<!-- 2:编写通知 -->
<!-- 3:配置aop的aspectj的自动代理:
自动扫描bean组件中,含有@Aspect的bean,将其作为aop管理,开启动态代理 -->
<aop:aspectj-autoproxy/>
前置通知
//advice通知类增强类
@Component("myAspect")//相当于<bean id="myAspect" class="cn.itcast.spring.a_aspectj.MyAspect"/>
@Aspect//相当于<aop:aspect ref="myAspect">
public class MyAspect {
//前置通知
//相当于:<aop:before method="before" pointcut="bean(*Service)"/>
//@Before("bean(*Service)"):参数值:自动支持切入点表达式或切入点名字
@Before("bean(*Service)")
public void before(JoinPoint joinPoint){
System.out.println("=======前置通知。。。。。");
}
}