Spring面向对象到面向切面实例分析
本篇内容主要讲解“Spring面向对象到面向切面实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Spring面向对象到面向切面实例分析”吧!
一.OOP&AOP
OOP将组件视为对象,AOP将对象的切面视为“对象”
OOP&AOP让程序通过极其简单的方式变得更加全面、强大
AOP(Aspect Oriented Programming)面向切面编程、OOP(Object Oriented Programming)面向对象编程
OOP是一种编程思想,AOP也是一种编程思想,编程思想主要的内容就是指导程序员该如何编写程序,两者都是不同的编程范式各有特色
二.AOP核心
通过以下一个计算程序运行时间的功能,引出AOP相关概念
@Repository public class AImpl implements A { public void save() { //记录程序当前执行执行(开始时间) Long startTime = System.currentTimeMillis(); //业务执行万次 for (int i = 0;i<10000;i++) { System.out.println("START ..."); } //记录程序当前执行时间(结束时间) Long endTime = System.currentTimeMillis(); //计算时间差 Long totalTime = endTime-startTime; //输出信息 System.out.println("执行万次程序消耗时间:" + totalTime + "ms"); } public void m1(){ System.out.println(" m1 ..."); } public void m2(){ System.out.println(" m2 ..."); } }
(1)save
,m1
和m2
方法,这些方法我们给起了一个名字叫连接点
(2)对于需要增强的方法我们给起了一个名字叫切入点
(3)将功能抽取到一个方法中,换句话说就是存放共性功能的方法,我们给起了个名字叫通知
(4)通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们给起了个名字叫切面
(5)通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫通知类
三.第一个AOP案例
1.环境准备
创建一个Maven项目
pom.xml添加Spring依赖
spring-context
添加A和AImpl类
public interface A { public void save(); public void m1(); } @Repository public class AImpl implements A { public void save() { System.out.println(System.currentTimeMillis()); System.out.println("book dao save ..."); } public void m1(){ System.out.println("book dao m1 ..."); } }
创建Spring的配置类
@Configuration @ComponentScan("yu7daily") public class Config { }
编写Show运行类
public class Show { public static void main(String[] args) { ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class); A A = ctx.getBean(A.class); A.save(); } }
2.AOP实现步骤
1.@EnableAspectJAutoProxy 开启注解格式AOP功能
2.@Aspect设置当前类为AOP切面类
3.@Pointcut 设置切入点方法
4.@Before设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
**1.添加依赖
pom.xml
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
因为spring-context
中已经导入了spring-aop
,所以不需要再单独导入spring-aop
.
导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发。
2.定义接口与实现类:环境准备的时候,AImpl已经准备好,不需要做任何修改
3.定义通知类和通知
通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印
public class Test { public void method(){ System.out.println(System.currentTimeMillis()); } }
类名和方法名没有要求,可以任意。
4.定义切入点
AImpl中有两个方法,分别是save和m1,我们要增强的是m1方法,该如何定义呢?
public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} public void method(){ System.out.println(System.currentTimeMillis()); } }
说明:
切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
execution及后面编写的内容
5.制作切面
切面是用来描述通知和切入点之间的关系,如何进行关系的绑定?
public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Before("po1()") public void method(){ System.out.println(System.currentTimeMillis()); } }
绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
说明:@Before翻译过来是之前,也就是说通知会在切入点方法执行之前执行,除此之前还有其他四种类型
6.将通知类配给容器并标识其为切面类
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Before("po1()") public void method(){ System.out.println(System.currentTimeMillis()); } }
7.开启注解格式AOP功能
@Configuration @ComponentScan("yu7daily") @EnableAspectJAutoProxy public class Config { }
8.运行程序
public class Show { public static void main(String[] args) { ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class); A A = ctx.getBean(A.class); A.m1(); } }
看到在执行m1方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功!!!
四.切入点表达式
前面的案例中,有涉及到如下内容:
对于AOP中切入点表达式,我们总共会学习三个内容,分别是语法格式、通配符和书写技巧。
1.语法格式
首先我们先要明确两个概念:
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
描述方式一:执行yu7daily.dao包下的A接口中的无参数m1方法
execution(void yu7daily.dao.A.m1())
描述方式二:执行yu7daily.dao.impl包下的AImpl类中的无参数m1方法
execution(void yu7daily.dao.impl.AImpl.m1())
因为调用接口方法的时候最终运行的还是其实现类的方法,所以上面两种描述方式都是可以的。
对于切入点表达式的语法为:
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
execution(public User yu7daily.service.UserService.findById(int))
切入点表达式就是要找到需要增强的方法,所以它就是对一个具体方法的描述,但是方法的定义会有很多,所以如果每一个方法对应一个切入点表达式,极其复杂可以通过以下方式进行简化
2.通配符
使用通配符描述切入点,主要的目的就是简化之前的配置
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * yu7daily.*.UserService.find*(*))
匹配yu7daily包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+
:专用于匹配子类类型
execution(* *..*Service+.*(..))
使用切入点表达式来分析下:
execution(void yu7daily.dao.A.m1())
匹配接口,能匹配到
execution(void yu7daily.dao.impl.AImpl.m1())
匹配实现类,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1())
返回值任意,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1(*))
返回值任意,但是m1方法必须要有一个参数,无法匹配,要想匹配需要在m1接口和实现类添加参数
execution(void com.*.*.*.*.m1())
返回值为void,com包下的任意包三层包下的任意类的m1方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.m1())
返回值为void,com包下的任意两层包下的任意类的m1方法,匹配到的是接口,能匹配
execution(void *..m1())
返回值为void,方法名是m1的任意包下的任意类,能匹配
execution(* *..*(..))
匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..u*(..))
匹配项目中任意包任意类下只要以u开头的方法,m1方法能满足,能匹配
execution(* *..*e(..))
匹配项目中任意包任意类下只要以e结尾的方法,m1和save方法能满足,能匹配
execution(void com..*())
返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
execution(* yu7daily.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
execution(* yu7daily.*.*Service.save*(..))
将项目中所有业务层方法的以save开头的方法匹配
五.AOP通知类型
它所代表的含义是将通知添加到切入点方法执行的前面。
除了这个注解外,还有没有其他的注解,换个问题就是除了可以在前面加,能不能在其他的地方加?
(1)前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容
(2)后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容
(3)返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加
(4)抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加
(5)环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能
环境准备
1.pom.xml添加Spring依赖spring-context、aspectjweaver
2.添加A和AImpl类
public interface A { public void m1(); public int m2(); } @Repository public class AImpl implements A { public void m1(){ System.out.println(" m1 ..."); } public int m2() { System.out.println(" m2 is running ..."); return 1; } }
创建Spring的配置类
@Configuration @ComponentScan("yu7daily") @EnableAspectJAutoProxy public class Config { }
创建通知类
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} public void around(){ System.out.println("around before advice ..."); System.out.println("around after advice ..."); } }
编写Show运行类
public class Show { public static void main(String[] args) { ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class); A A = ctx.getBean(A.class); A.m1(); } }
环绕通知
(1)原始方法有返回值的处理
修改Test,对A中的m2方法添加环绕通知,
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @Around("po2()") public void aroundM2(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); //表示对原始操作的调用 pjp.proceed(); System.out.println("around after advice ..."); } }
修改Show类,调用m2方法
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @Around("po2()") public Object aroundM2(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); //表示对原始操作的调用 Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; } }
说明:
返回的是Object而不是int的主要原因是Object类型更通用随时可以转型
在环绕通知中是可以对原始方法返回值就行修改的
1.返回后通知
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @AfterReturning("po2()") public void afterReturning() { System.out.println("afterReturning advice ..."); } }
注意:返回后通知是需要在原始方法m2
正常执行后才会被执行,如果m2()
方法执行的过程中出现了异常,那么返回后通知是不会被执行。后置通知则是不管原始方法有没有抛出异常都会被执行
2.异常后通知
@Component @Aspect public class Test { @Pointcut("execution(void yu7daily.dao.A.m1())") private void po1(){} @Pointcut("execution(int yu7daily.dao.A.m2())") private void po2(){} @AfterReturning("po2()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); } }
环绕通知注意事项
1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型
4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常
到此,相信大家对“Spring面向对象到面向切面实例分析”有了更深的了解,不妨来实际操作一番吧!这里是蜗牛博客网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
评论