spring AOP是面向切面编程,主要思想是,将代码中的与主业务逻辑无关的公共代码,抽离出来,单独模块化为类即切面,在运行的时候动态的将切面的功能即通知加入到业务执行逻辑中。AOP模块常用于日志处理、事务管理、权限验证、参数验证等。(前面统一异常处理,拦截器就是典型的AOP思想)
优点:
–每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
–业务模块更简洁, 只包含核心业务代码.
•切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象,即共通处理的组件
•目标(Target): 被通知的对象
•通知(Advice): 切面必须要完成的工作,用于指定切面和目标组件作用的时机。例如切面功能在目标方法之前或者之后执行等时机。
Spring框架提供五种类型的通知:
前置通知:先执行方面功能在执行目标功能。
后置通知:先执行目标功能再执行方面功能(正常异常都会执行)
返回通知:在方法返回结果之后执行(异常之后就不执行)
异常通知:先执行目标,抛出后执行方面
环绕通知:限制性方面前置部分,然后执行目标,最后再执行方面后置部分。
•代理(Proxy): 向目标对象应用通知之后创建的对象
•连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
切入点(pointcut):每个类都拥有多个连接点,是用于指定哪些组件和方法使用切面功能,在spring中利用一个表达式指定切入目标。
Spring常用的切入点表达式:
方法限定表达式:
execution(修饰符?返回类型 方法名(参数) throws 异常类型)
类型限定表达式:
within(包名.类型)
bean名称限定表达式
Bean(bean的id或者name的属性值)
Spring AOP实现主要是基于动态代理技术,当spring采用aop配置后,spring容器返回目标对象,实质上是spring利用动态代理技术生成一个代理类型,代理类型重写了原目标组件方法的功能,在代理类中调用方面对象功能和目标对象的功能。
Spring框架采用了两种动态代理实现:
利用cglib工具包 (可以代理一切类)
目标没有接口时采用此方法。代理类是利用继承方法生成一个目标子类。
利用jdk proxy api (局限性,但效率高)
目标有接口时采用此方法,代理类是采用实现目标接口方法生成一个类。
代理的目的:是创建一个可以代理目标类的代理类,该代理类要重写目标类的方法。
动态代理:在程序运行阶段给要代理的接口/类生成实现类/子类 生成类存在于内存中,不存在硬盘上,在运行阶段需要创建类,再编译执行,因此效率低,但灵活度高。
创建方面组:
创建一个类,充当方面组件,实现通用业务逻辑。
声明方面组件:
在applicationContext.xml中,声明方面组件
使用方面组件:
在applicationContext.xml中,将方面组件作用到目标组件的方法上,并设置通知类型以确认方面组件调用的时机。
需求:记录查询的操作日志。
org.springframework.aop-3.1.1.RELEASE.jar ---- ----spring 的面向切面编程,提供AOP(面向切面编程)实现
org.springframework.aspects-3.1.1.RELEASE.jar ---- ----spring 提供对 AspectJ 框架的整合
aopalliance.jar
aspectjweaver-1.7.4.jar
aspectjrt-1.7.4.jar
public class Logger { public void log(){ System.out.println("用户操作的信息"); }
} |
<bean id="logs" class="com.eduask.it.controller.Logger">bean> |
此处用了前置通知作用到目标上,如果要使用返回通知,后置通知则将before改为,after-returning,after
<aop:config> <aop:aspect ref="logs"> <aop:before method="log" pointcut="within(com.eduask.it.controller..*)"/> aop:aspect> aop:config> |
/** * 测试aop运用到项目里面 */ @Test public void testAOP(){ ApplicationContext a = new ClassPathXmlApplicationContext("resource/applicationContext.xml"); UserController mapper = a.getBean(UserController.class); mapper.findUserList(); } |
测试结果:输出结果将比目标结果先打印。
编写方面组件的具体方法
public void exLog(Exception e){ System.out.println("操作异常了"+e); } |
编写配置文件
<aop:aspect ref="logs"> <aop:after-throwing method="exLog" throwing="e" pointcut="within(com.eduask.it.controller..*)"/> aop:aspect> |
注意:方法参数中的变量名要与配置文件throwing的属性值一样。否则不提示异常类型。
直接在上面的案例中执行。
编写方面组件的具体方法
import org.aspectj.lang.ProceedingJoinPoint;
/** * Proceedingjoinpoint 继承了 JoinPoint 。 * 是在JoinPoint的基础上暴露出 proceed 这个方法。 * proceed很重要,这个是aop代理链执行的方法。 * 暴露出这个方法,就能支持 aop:around 这种切面 */ public void opLog(ProceedingJoinPoint p) throws Throwable{ System.out.println("用户操作前"); p.proceed(); //环绕通知中执行目标的方法 System.out.println("用户操作后"); } |
编写配置文件
<aop:aspect ref="logs"> <aop:around method="opLog" pointcut="within(com.eduask.it.controller..*)"/> aop:aspect> |
然后测试,直接在上面的案例中执行。
@Component @Aspect public class CalculatorLogging { @Before("within(com.eduask.it.controller..*)") public void beforLogging(){ System.out.println("用户操作了。。。"); } } |
说明
@Aspect注解表示这个类作为一个切面
@Component注解表示这个类同样也要放在IOC容器中
@Before注解表示这个方法是用来作为前置通知,也就是在它签名中所标识的具体方法调用之前就会进入这个类
("within(com.eduask.it.controller..*)")
如果要使用后置通知,和返回通知则使用
@After注解表示这个方法是用来作为后置通知,也就是在它签名中所标识的具体方法调用之后才会进入这个方法
@AfterReturning注解表示这个方法是用来作为返回通知,也就是在它签名中所标识的具体方法调用并返回之后才会进入这个方法
若方法出现执行中出现异常, 则不会进入到返回通知
<aop:aspectj-autoproxy proxy-target-class="true"/> |
测试
在基于xml配置的案例中测试
@Component @Aspect public class CalculatorLogging { @AfterThrowing(value="within(com.eduask.it.controller..*)",throwing="ex") public void beforLogging(Exception ex){ System.out.println("用户操作了。。。"+ex); } } |
@AfterThrowing注解表示这个方法是用来作为异常通知,也就是在它签名中所标识的具体方法调用并出现异常之后才会进入这个方法
并且要在方法参数里面添加一个"Exception ex",这个变量名要与刚才throwing所匹配的名字一致!
value属性表示所装配的类和方法
throwing表示返回的异常对象
@Component @Aspect public class CalculatorLogging { @Around(value="within(com.eduask.it.controller..*)") public Object beforLogging(ProceedingJoinPoint jp) throws Throwable{ System.out.println("用户操作了。。。"); Object proceed = jp.proceed(); System.out.println("ddddd"); return proceed; } } |
@Around注解表示这个方法是用来作为环绕通知,也就是在它签名中所标识的具体方法调用会进入这个方法
环绕通知其实就相当于一个代理,可以在里面写上前置、后置、异常或返回等