一、AOP思想
在开发中,为了给业务方法中增加日志记录,权限检查,事务控制等功能,此时我们需要去修改业务方法代码,考虑到代码的重用性,我们可以考虑使用OOP的继承或组合关系
来消除重复, 但是无论怎么样, 我们都会在业务方法中纵向地增加这些功能方法的调用代码。
此时,既不遵循开闭原则
,也会为后期系统的维护带来很大的麻烦。这些零散存在于业务方法中的功能代码,我们称之为横切面关注点
,横切面关注点不属于业务范围,应该从业务代码中剥离出来。为了解决该问题, OOP思想是不行了,得使用AOP思想。
AOP(Aspect Oritention Programming):
把一个个的横切关注点
放到某个模块中去,称之为切面。那么**每一个的切面都能影响业务的某一种功能,**切面的目的就是功能增强,如日志切面就是一个横切关注点,应用中许多方法需要做日志记录的只需要插入日志的切面即可。
这种面向切面编程的思想就是AOP思想
二、AOP底层原理(了解)
AOP,面向切面编程,底层使用动态代理方式实现。针对实现了接口的类的横向扩展,采用的是JDK动态代理进行实现。在JDK动态代理中,为对原有类(实现了某一接口的类)进行横向扩展,内部创建了实现了同一接口的类代理对象,具有与原有类对象相同的功能,且进行了增强。对于未实现接口的类,想实现横向的扩展,则需要使用cglib动态代理实现,内部创建原有类的子类的代理对象,即在子类中调用父类的方法完成增强。
三、核心概念
- 连接点(Joinpoint):被拦截到的点。在Spring中只支持方法类型的连接点,因此这些点在Spring中指的是方法。简单来说,我们可以认为是可以被增强功能的方法。
- 切入点(Pointcut):对连接点进行拦截的定义。在Spring中通过表达式(后续将进行说明)形式来定义所匹配的类下的方法,以此定义切入点。
- 通知/增强(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为:
- 前置通知(before 在方法之前执行)
- 后置(after-returning 在方法之后执行)
- 环绕(around 在方法之前和之后执行)
- 通知异常(after-throwing 在方法出现异常时执行)(了解)
- 最终(after 在后置之后执行,无论方法是否执行成功)(了解)。
- 目标对象(Target):代理的类或是要被增强的类。
- 织入(Weaving):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程
- 代理类(Proxy):一个类进行织入后将产生一个结果类,称为代理类。
- 切面(Aspect):是切入点pointcut和通知advice的结合。
四、Pointcut语法
- AOP的规范本应该由SUN公司提出,但是被AOP联盟捷足先登。AOP联盟在制定AOP规范时,首先就要解决一个问题: 怎么表示切入点。也就是说怎么表达需要在哪些方法上做增强。这是一个如何表示WHERE的问题。
- AOP规范后,面向切面编程的框架AspectJ也就应运而生了, 同时也确定如何去表达这个WHERE。
AspectJ切入点语法如下(表示在哪些包下的哪些类中的哪些方法上做切入增强):
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
execution(<修饰符>? <返回类型> <声明类型>? <方法名><参数>) <异常>?)
注: ? 表示可写可不写
表达式参数详细:
- 修饰符[一般省略]
- public 公共方法
*
任意
- 返回值类型[不能省略,一般使用*]
- void 返回没有值
- String 返回值字符串
*
任意
- 包[可以省略,但一般不省]
- com.hxzy.service.impl 固定
- com.hxzy.*.impl com.hxzy下面的所有impl子包
- com.hxzy… com.hxzy下面的所有子包(包含自己)
- 类[一般省略用*来指代]
- UserServiceImpl 指定类
- *Impl 以Impl结尾
- User* 以User开头
*
任意
- 方法名[不能省略]
- addUser 固定方法
- add* 以add开头
- *Do 以Do结尾
*
任意
- (参数)
- () 无参
- (int) 一个整型
- (int ,int) 两个整型
- (…) 参数任意
- throws [可省略,一般不写]
切入点表达式中的通配符:
*
: 匹配任何部分,但是只能表示一个单词..
: 可用于全限定名中和方法参数中,分别表示 包以及子包 和 0到N个参数。
常见的写法:(对应表达式从后往前看)
execution(* com.myw.service.*.*(..))
: 表示对service包下的所有类所有方法做增强
execution(* com.myw.service.*Serice.*(..))
: 表示对service包下所有以Service为后缀的类的所有方法做增强
execution(* com.myw..service.*Service.*(..)
: 表示对myw包及其子包下的service包中的以Service为后缀的类的所有方法做增强
五、AOP小案例
这里的案例不是使用Maven构建,而是采用较为原始的方法
使用AOP的准备操作
1、导入jar包
所需jar包如下:
Spring基础jar包:
1. spring-beans
2. spring-context
3. spring-core
4. spring-expression
5. commons-logging-1.2.jar
Spring AOP相关的jar包:
1. aopalliance-1.0.jar
2. aspectjweaver-1.8.7.jar
3. spring-aop
4. spring-aspects
2、准备目标对象(被代理对象,被通知的对象,被增强的类对象)
package com.myw.service.Impl;
import com.myw.service.IUserService;
public class UserServiceImpl implements IUserService {
public void add() {
System.out.println("增加用户");
}
public void delete() {
System.out.println("删除用户");
}
public void update() {
System.out.println("修改用户");
}
public void query() {
System.out.println("查询用户");
}
}
3、准备通知(被增强方法的代码,想要实现功能的方法代码)
package com.myw.Aspect;
import org.aspectj.lang.ProceedingJoinPonit;
public class MyAspect {
/*
一般前置、后置、环绕用得比较多
*/
//前置通知
public void before(){
System.out.println("前置通知");
}
//后置通知
public void after(){
System.out.println("后置通知");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知的前置部分");
//用来获取被环绕方法的返回值
Object proceed = pjp.proceed();
System.out.println("环绕通知的后置部分");
return proceed;
}
}
4、配置 applicationContext.xml
- 导入aop(约束)命名空间
- 配置目标对象
- 配置通知对象
- 配置将通知织入目标对象
<!--application.xml-->
<?xml version="1.0" encoding="UTF-8">
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org">
<!--1.配置目标对象-->
<bean name="userService" class="com.myw.service.Impl.UserServiceImpl"></bean>
<!--2.配置通知对象-->
<bean name="myAspect" class="com.myw.Aspect.MyAspect"></bean>
<!--3.配置将通知织入目标对象中-->
<aop:config>
<!--配置切入点-->
<!--
public void com.myw.service.Impl.UserServiceImpl.*(..)
* com.myw.service.Impl.UserServiceImpl.*(..)
-->
<aop:pointcut expression="execution(* com.myw.service.Impl.UserServiceImpl.*(..))" id="pc">
<aop:aspect ref="myAspect">
<!--指定名为before的方法作为前置通知-->
<aop:before method="before" pointcut-ref="pc"/>
<!--后置通知-->
<aop:after method="after" pointcut-ref="pc"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pc">
</aop:aspect>
</beans>
5、使用
当用目标类(UserServiceImpl)去调用其本身被增强的方法时,通知会附加上去
IUserService u = new UserServiceImpl();
u.add()
输入结果:
前置通知
环绕通知的前置部分
增加用户
环绕通知的后置部分
后置通知
总结:通知的几种类型
- 前置通知———目标方法运行之前调用
- 后置通知———目标方法运行之后调用(如果出现异常不调用)
- 环绕通知———目标方法之前和之后都调用
六、spring中的aop使用注解配置(推荐)
1、applicationContext.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: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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!--1.确定扫描注解的范围,这里扫描com.myw的所有子包-->
<context:component-scan base-package="com.myw"></context:component-scan>
<!--2.配置目标对象-->
<bean name="userService" class="com.myw.service.Impl.UserServiceImpl"></bean>
<!--3.配置通知对象-->
<bean name="myAspect" class="com.myw.Aspect.MyAspect"></bean>
<!--4.使Spring的AOP注解生效,后面的true表示使用cglib动态代理模式-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
2、@Aspect注解代表该类是个通知类,书写切点表达式@Pointcut(“execution(返回值 全类名.方法名(参数))”)
package com.myw.Aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
//表示这是一个通知类
@Aspect
public class MyAspect{
//创建一个方法,使用Pointcut注解,为后面的方法做准备
@Pointcut("execution(* com.myw.service.Impl.UserServiceImpl(..))")
public void pc(){}
@Before("MyAspect.pc()")
public void before(){
System.out.println("前置通知");
}
@After("MyAspect.pc()")
public void after(){
System.out.println("后置通知");
}
@Around("MyAspect.pc()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
//环绕方法执行前
//目标方法(会获取目标方法的返回值)
Object obj = joinPoint.proceed();
//环绕方法执行后
return obj;
}
}