1. Spring核心之AOP
1.1 什么是AOP
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态 代理实现程序功能的统一维护的一种技术。
AOP的作用:不修改源码的情况下,程序运行期间对方法进行功能增强
好处:
1、减少代码的重复,提高开发效率,便于维护。
2、专注核心业务的开发。
核心业务和服务性代码(切面)混合在一起
开发中:各自做自己擅长的事情,运行的时候将服务性代码织入到核心业务中。
通过spring工厂自动实现将服务性代码以切面的方式加入到核心业务代码中。
1.2 AOP的实现机制-动态代理
什么是代理模式:
代理:自己不做,找人帮你做。
代理模式:在一个原有功能的基础上添加新的功能。
分类:静态代理和动态代理。
2. SpringAOP
2.1 Spring AOP相关概念
Spring的AOP实现底层就是对动态代理的代码进行了封装,封装后我们只需要对需要关注的部 分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP的相关术语:
Target(目标对象)
要被增强的对象,一般是业务逻辑类的对象。
Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面)
表示增强的功能,就是一些代码完成的某个功能,非业务功能。是切入点和通知的结合。
Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方
法),因为 Spring只支持方法类型的连接点。
Pointcut(切入点)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方
法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
切入点定义切入的位置,通知定义切入的时间。
Weaving(织入).
是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
切面的三个关键因素:
1、切面的功能–切面能干啥
2、切面的执行位置–使用Pointcut表示切面执行的位置
3、切面的执行时间–使用Advice表示时间,在目标方法之前还是之后执行。
2.2 AspectJ 对 AOP 的实现
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。 AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式
AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。
2.2.1 AspectJ的通知类型
AspectJ 中常用的通知有5种类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
2.2.2 AspectJ的切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。
表达式的原型如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
说明:
modifiers-pattern 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选的部分
以上表达式共 4 个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。
所以,execution 表达式中就是方法的签名。
表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
例如:
execution(* com.wjx.service..(…))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.wjx.service….(…))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后
面必须跟 “”,表示包、子包下的所有类。
execution( com.wjx.service.IUserService+.*(…))
指定切入点为:IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方
法;若为类, 则为该类及其子类中的任意方法。
2.3 注解方式实现AOP
开发阶段:关注核心业务和AOP代码
运行阶段:spring框架会在运行的时候将核心业务和AOP代码通过动态代理的方式编织在一起
代理方式的选择:是否实现了接口:有接口就选择JDK动态代理;没有就选择CGLIB动态代理。
1、 引入依赖
2 创建spring配置文件引入约束
核心业务类:
public interface IService {
void add(int id,String name);
boolean update(int num);
}
TeamService核心业务类
@Service
public class TeamService implements IService{
@Override
public void add(int id, String name) {
System.out.println("TeamService---- add----");
}
@Override
public boolean update(int num) {
System.out.println("TeamService---- update----");
//int res=10/0;
if(num>666){
return true;
}
return false;
}
}
NBAService核心业务类:
@Service("nbaService")
public class NBAService implements IService{
@Override
public void add(int id, String name) {
System.out.println("NBAService---- add----");
}
@Override
public boolean update(int num) {
System.out.println("NBAService---- update----");
//int res=10/0;
if(num>666){
return true;
}
return false;
}
}
切面类
@Component //切面对象的创建权限依然交给spring容器
@Aspect //aspectj 框架的注解 标识当前类是一个切面类
public class MyAspect{
/**
* 当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
* AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
* 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value属性值
* 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。
* 这个使用@Pointcut 注解方法一般使用 private 的标识方法,即没有实际作用的方法。
*/
@Pointcut("execution(* com.kkb.service..*.*(..))")
private void pointCut(){
}
@Pointcut("execution(* com.kkb.service..*.add*(..))")
private void pointCut2(){ }
/**
* 声明前置通知
* @param jp
*/
@Before("pointCut()")
public void before(JoinPoint jp){
System.out.println("前置通知:在目标方法执行之前被调用的通知");
String name = jp.getSignature().getName();
System.out.println("拦截的方法名称:"+name);
Object[] args = jp.getArgs();
System.out.println("方法的参数格式:"+args.length);
System.out.println("方法参数列表:");
for (Object arg : args) {
System.out.println("\t"+arg);
}
}
/**
* AfterReturning 注解声明后置通知
* value: 表示切入点表达式
* returning 属性表示 返回的结果,如果需要的话可以在后置通知的方法中修改结果
*/
@AfterReturning(value = "pointCut2()",returning = "result")
public Object afterReturn(Object result){
if(result!=null){
boolean res=(boolean)result;
if(res){
result=false;
}
}
System.out.println("后置通知:目标方法执行之后调用的通知,result="+result);
return result;
}
/**
* Around 注解声明环绕通知
* ProceedingJoinPoint 中的proceed方法表示目标方法被执行
*/
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕方法---目标方法的执行之前");
Object proceed = pjp.proceed();
System.out.println("环绕方法---目标方法的执行之后");
return proceed;
}
/**
* AfterThrowing 注解声明异常通知方法
* value: 表示切入点表达式
* returning 属性表示 返回的结果,如果需要的话可以在后置通知的方法中修改结果
*/
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void exception(JoinPoint jp,Throwable ex){
//一般会把异常发生的时间、位置、原有都记录下来
System.out.println("异常通知:目标方法出现异常的时候才会别调用的通知");
System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"
=+ex.getMessage());
}
/**
* After 注解声明为最终通知
*/
@After( "pointCut()")
public void myFinally(){
System.out.println("最终通知:无论是否出现异常都是最后被调用的通知");
}
}
测试类:
public class Test01 {
@Test
public void test01(){
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
TeamService teamService = (TeamService) ac.getBean("teamService");
teamService.add(1001,"湖人队");
System.out.println("--------------------------");
boolean update = teamService.update(888);
System.out.println("update 结果="+update);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
NBAService nbaService = (NBAService) ac.getBean("nbaService");
nbaService.add(1002,"热火");
System.out.println("--------------------------");
boolean update2 = teamService.update(888);
System.out.println("update 结果="+update2);
}
}
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。
spring.xml配置文件中开启包扫描和 注册aspectj的自动代理
2.4 XML方式实现AOP
切面类:
@Component //切面对象的创建权限依然交给spring容器
@Aspect //aspectj 框架的注解 标识当前类是一个切面类
public class MyAOP {
public void before(JoinPoint jp){
System.out.println("AOP前置通知:在目标方法执行之前被调用的通知");
}
public void afterReturn(Object result){
System.out.println("AOP后置通知:在目标方法执行之后被调用的通知
,result="+result);
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP环绕方法---目标方法的执行之前");
Object proceed = pjp.proceed();
System.out.println("AOP环绕方法---目标方法的执行之后");
return proceed;
}
public void exception(JoinPoint jp,Throwable ex){
//一般会把异常发生的时间、位置、原有都记录下来
System.out.println("AOP异常通知:在目标方法执行出现异常的时候才
会别调用的通知,否则不执行");
System.out.println(jp.getSignature()+"方法出现异常,异常信息 是:"
+ex.getMessage());
}
public void myFinally(){
System.out.println("AOP最终通知:
无论是否出现异常都是最后被调用的通知");
}
}
配置文件: