AOP
一、AOP概念
AOP即面向切面编程。我们先来理解下为什么会需要AOP?
如果在编程时多个类中有重复出现的代码,那么我们就应该考虑将这些重复代码抽取出来定义成一个抽象类,这种情况我们称为纵向抽取。但是如果这些重复的代码是分散在各个业务逻辑中的,比如事务控制中有大量的try-catch-finally代码,这些重复代码嵌套在主要的业务逻辑代码中,我们用上面的方法就抽取不了,这时我们就可以通过横向切割的方式,把这些重复代码抽取到一个独立的模块中,在以后调用对应的业务逻辑方法时,又将这些重复代码横向切入进去。从而达到让业务逻辑类保持最初的单纯。
我们用一个例子来说明。如下图:是一个对数据库进行操作的步骤:
1、连接数据库
2、执行SQL语句
3、是否有异常,有就回滚事务,没有就提交事务
4、关闭资源
我们可以看到每次对数据库进行一次操作后,都会有如下的步骤,其中的1-3-4步骤都是重复的,而作为开发人员我们每次真正需要关心的只是步骤2的SQL语句执行(因为这在每个操作中是不一样的),而对于其他的我们则可以交由AOP去实现这些步骤,我们只需通过注解或者XML的配置来告诉AOP我们需要在那些类下的哪些方法中切入这些重复的代码,这样在编写代码时我们就只需关注核心逻辑代码,而在运行时AOP会对我们需要加入重复代码的方法进行拦截,并将这些重复代码切入进去。
值得一提的是AOP并不是Spring框架特有的,Spring只是支持AOP编程的框架之一,且SpringAOP是一种基于方法拦截的AOP,也就是说Spring只能支持方法拦截的AOP。
二、AOP术语
1、切面(Aspect):切面由切点和增加(需要切入的代码、方法也叫通知)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
2、增强、通知(Advice):是切面的方法,也就是一段需要切入到目标对象的代码。根据他在代理对象真实方法调用前、后和逻辑分为以下几种:
前置通知(before):在真实对象方法调用前执行
后置通知(after):在真实对象方法调用后执行
返回通知(afterReturning):在执行业务代码后无异常执行
异常通知(afterThrowing):在执行业务代码发送异常后执行
环绕通知(around):最强的通知,可以取代原对象方法。但使用也较复杂
3、切点(Pointcut):告诉SpringAOP在什么时候启动拦截并织入对应的流程中。
4、连接点(join point):连接点对应的是一个具体的方法,比如通过切点的正则表达式去判断那些方法是连接点。
5、织入(Weaving):织入就是将增强添加到目标具体连接点上的过程。
三、基于纯注解的AOP实现:
主要步骤:
1、创建业务逻辑接口,实现接口
2、创建切面,切面中包含切点和需要切入的方法
3、创建AOP配置类,启动Aspectj自动代理、扫描bean、生成切面类
4、测试AOP
1、创建一个接口 里面的方法就是我们的连接点,也就是我们需要拦截这个方法并将通知织入
package com.test.aop;
public interface Student {
void getName();
}
2、创建这个接口的实现
package com.test.aop;
import org.springframework.stereotype.Component;
@Component
public class StudentImpl implements Student {
@Override
public void getName() {
System.out.println("我叫唐鹏,我自豪");
}
}
3、创建切面,并定义切点和逻辑方法
package com.test.aop;
import org.aspectj.lang.annotation.*;
@Aspect
public class TestAspect {
@Pointcut("execution(* com.test.aop.*.getName(..))")
public void pointCut(){}
@Before("pointCut()")
public void before(){
System.out.println("=====前置:小么小么小二郎哟,背着书包上学堂咯");
}
@After("pointCut()")
public void after(){
System.out.println("=====后置:终于放学了!");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("=====返回:今天真呀真高兴");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("=====异常:oh 我考试考了0分,可不高兴");
}
}
4、创建AOP配置类
package com.test.aop;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
//启用Aspectj自动代理
@EnableAspectJAutoProxy
//配置扫描的包 这是扫描bean的配置
@ComponentScan("com.test")
public class AopConfig {
//生成切面类,自定义的类里面设置了切点
@Bean
public TestAspect getTestAspect(){
return new TestAspect();
}
}
5、测试AOP
package com.test.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestMain {
public static void main(String[] args) {
//因为是采用注解所以使用AnnotationConfigAopApplicationContext来实现
ApplicationContext ac = new AnnotationConfigApplicationContext(AopConfig.class);
Student tp = (Student) ac.getBean("studentImpl");
tp.getName();
}
}
6、结果打印
=====前置:小么小么小二郎哟,背着书包上学堂咯
我叫唐鹏,我自豪
=====后置:终于放学了!
=====返回:今天真呀真高兴
四、基于纯XML开发AOP的方式:
基本步骤:
1、同样先创建接口和接口实现类
2、创建切面类和切入方法,因为是采用XML的方式,所以这里不需要指明他是个切面类,也不需要指定切入方法对应的切入点。,只需要生成对应的方法和方法代码即可。
3、创建spirng配置文件
4、测试
1、接口和实现同上,这里就不在给出。(注意如果是在不同包下使用相同类名,注意包的引用)
2、编写切面类
package com.test.aopXml;
public class TestAspect {
public void before(){
System.out.println("=====XML前置:小么小么小二郎哟,背着书包上学堂咯");
}
public void after(){
System.out.println("=====XML后置:终于放学了!");
}
public void afterReturning(){
System.out.println("=====XML返回:今天真呀真高兴");
}
public void afterThrowing(){
System.out.println("=====XML异常:oh 我考试考了0分,可不高兴");
}
}
3、spring的配置文件applicationContext.xml 注意放在resources源码文件下面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id ="student" class="com.test.aopXml.StudentImpl"/>
<bean id ="testAspect" class="com.test.aopXml.TestAspect"/>
<!--aop配置-->
<aop:config >
<!--定义切面,和切点配置-->
<aop:aspect ref="testAspect">
<!--定义需要重复使用的切点-->
<aop:pointcut id="pointCut" expression="execution(* com.test.aopXml.StudentImpl.*(..))"/>
<!--定义切点,method是指切面类中对应的方法名-->
<aop:before method="before" pointcut-ref="pointCut"/>
<aop:after method="after" pointcut-ref="pointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
4、测试
package com.test.aopXml;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) ac.getBean("student");
student.getName();
}
}
5、测试结果
=====XML前置:小么小么小二郎哟,背着书包上学堂咯
我叫唐鹏,我自豪
=====XML后置:终于放学了!
=====XML返回:今天真呀真高兴
五、注解和XML混合开发的方式
注解和XML混合使用:切点信息定义在切面类,xml中只需要开启对aspectj自动代理和配置扫描就好
步骤如下:
1、同样给出接口和实现类
2、编写切面类,切面类需要加入容器,让ioc管理
3、配置applicationContext1.xml文件
4、测试
1、接口和实现同上
2、编写切面类,这里切面类加上了@Component注解 而在使用纯注解时是没有加上的
package com.test.aop;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TestAspect {
@Pointcut("execution(* com.test.aop.*.getName(..))")
public void pointCut(){}
@Before("pointCut()")
public void before(){
System.out.println("=====混合前置:小么小么小二郎哟,背着书包上学堂咯");
}
@After("pointCut()")
public void after(){
System.out.println("=====混合后置:终于放学了!");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("=====混合返回:今天真呀真高兴");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("=====混合异常:oh 我考试考了0分,可不高兴");
}
}
3、applicationContext1.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--此配置是为了让spring知道我们配置了切面,让他扫描bean的时候注意到切面类-->
<aop:aspectj-autoproxy/>
<!--扫描bean-->
<context:component-scan base-package="com.test.aop"/>
</beans>
4、测试
package com.test.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext1.xml");
Student student = (Student) ac.getBean("studentImpl");
student.getName();
}
}
5、测试结果
=====混合前置:小么小么小二郎哟,背着书包上学堂咯
我叫唐鹏,我自豪
=====混合后置:终于放学了!
=====混合返回:今天真呀真高兴