Spring的第三天AOP之xml版
Spring的第三天AOP之xml版
AOP介绍
AOP(Aspect Oriented Programming),面向切面编程。它出来的目的并不是去取代oop,而是对它的完善和补充。在oop中,人们的是去定义纵向的关系,但是会出现一个问题:在程序中,日志代码往往是横向的地散布在各种对象层次中,而在oop的模式设计中,导致了大量重复工作的代码。
可以这样说:oop是面向名词领域,AOP是面向动词领域。AOP适合通用的工作,不适合个性化的工作。
图来自网络,侵删
在AOP中,我们将那些与多个类相关的行为放在一起变成一个模块,命名为Aspect
【切面】。讲个故事:
村里来了一个
通告
,以前是到每家每户去通知,假如通告进行了改变,又要重新进行通知,然后村里面的人觉得太麻烦了,就做了一个声音传输管道,每当有通告来的时候,村长
就选择要通知的人
,告诉他们某个时间
去做通告里面的东西,然后打开管道进行通知。
-
通告:通知(Advice)
就是你想要的东西,比如说日志,事物。
-
人:PointCut【切入点】
切入点里面定义了Advice发生的地点,例如某个类或方法的名称,为被切的地方。
-
时间:Joinpoint【连接点】
连接点就是告诉程序什么时候去使用通知,例如当方法被调用时,或者是异常抛出时。
-
村长:Proxy【代理】
Proxy不能算是AOP的家庭成员,它是一个管理部门,管理AOP如何融入OOP,是AOP的实践者。同时AOP的代理离不开spring的IOC容器。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理。
那么什么是切面呢?
切面其实就是Advice+PointCut,代表了切面的所有元素,而将切面织入代码中就是依靠Proxy
maven依赖
导入Spring依赖和aop依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>
头部文件xmlns文件配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
类的java代码
package com.weno.pojo;
public class User {
public void print(){
System.out.println("我这是在执行一个方法");
throw new RuntimeException("我故意的报错");
}
public String msg(){
return "竟然有返回值";
}
}
建议类:
package com.weno.aop;
public void beforeMethod(){
System.out.println("一千年以前");
}
public void afterMethod(){
System.out.println("一千年以后");
}
public void returnMethod(Object rvt){
System.out.println("返回值>>>>>>"+rvt);
System.out.println("方法返回");
}
public void errorMethod(){
System.out.println("程序竟然报错了");
}
}
xml版本的使用
首先先说一下切入点,切入点分为:
-
前置通知:在连接点之前运行但无法阻止执行流程进入连接点的建议(除非它引发异常,该异常将中断当前方法链的执行而返回)。
-
后置通知:在连接点
正常
完成后运行的建议(例如,如果方法返回而不抛出异常)。 -
异常通知:如果方法通过抛出异常退出,则执行建议。
-
后置最终通知:无论连接点退出的方式(正常或异常返回),都要执行建议。
-
环绕通知:环绕在连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
前置通知,后置通知,异常通知,返回通知
<bean class="com.weno.pojo.User" id="user"/>
<bean class="com.weno.aop.Method" id="method"/>
<aop:config>
<!--定义一个切面-->
<aop:aspect ref="method">
<!--切点,定义切点的id exexution后面写要切入的地点:在print这个方法进行切-->
<aop:pointcut id="positon" expression="execution(* com.weno.pojo.User.print())"/>
<!-- 切的时间 method表示切的方法 -->
<!-- beforeMethod 在执行方法之前切入 -->
<aop:before method="beforeMethod" pointcut-ref="positon"/>
<!-- 在执行方法之后切入 -->
<aop:after method="afterMethod" pointcut-ref="positon"/>
<!-- 在方法报错的时候切入 -->
<aop:after-throwing method="errorMethod" pointcut-ref="positon"/>
<!-- 在方法有返回值的时候切
同时可以加上returning,将值传给returnMethod()方法
-->
<aop:after-returning method="returnMethod" returning="rvt" pointcut="execution(* com.weno.pojo.User.msg())"/>
</aop:aspect>
</aop:config>
测试代码一:
@Test
public void m01(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-test1.xml");
// 在这里,如果直接通过new实例化User,那么aop功能将失效,因为AOP是要在spring IOC容器里面实现的
User user = ctx.getBean("user",User.class);
user.print();
}
输出结果
一千年以前
我这是在执行一个方法
一千年以后
程序竟然报错了
测试代码二:
@Test
public void m03(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-test1.xml");
User user = ctx.getBean("user",User.class);
user.msg();
}
结果:
返回值>>>>>>竟然有返回值
方法返回
环绕通知
环绕通知是所有通知类型中功能中最为强大的,能够全面地控制连接点,甚至能够控制方法是否执行,同时,他还可以实现before和after的功能。
切面程序的代码
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//允许程序进行执行
proceedingJoinPoint.proceed();
}
public void aroundMethod2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//允许程序进行执行,并将其参数进行改变。
proceedingJoinPoint.proceed(new String[]{"你最帅"});
}
被切的程序
public void msg1(){
System.out.println("这是米有参数的msg");
}
public void msg2(String msg){
System.out.println("进行执行方法输出"+msg);
}
xml文件配置
<aop:around method="aroundMethod" pointcut="execution(* com.weno.pojo.User.msg1())"/>
<aop:around method="aroundMethod2" pointcut="execution(* com.weno.pojo.User.msg2(..))"/>
测试文件
@Test
public void m04() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-test1.xml");
User user = ctx.getBean("user", User.class);
user.msg1();
user.msg2("我好帅");
}
结果
这是米有参数的msg
//在这里面,参数进行了改变,由我好帅变成了你最帅
进行执行方法输出你最帅
JoinPoint的神奇之处
这个是官方文档截取过来的
使用上面的那个例子来获得参数:
public void aroundMethod2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object[] args = proceedingJoinPoint.getArgs();
System.out.println(Arrays.toString(args));
//允许程序进行执行,并将其参数进行改变。
proceedingJoinPoint.proceed(new String[]{"你最帅"});
}
输出结果当然是:
这是米有参数的msg
[我好帅]
进行执行方法输出你最帅
当然,如果你将Object[] args = proceedingJoinPoint.getArgs(); System.out.println(Arrays.toString(args));
放在后面,那输出参数当然就变成了[你最帅]。
基本数据类型和包装类对于execution严格区分
首先先简单的介绍一下execution,先定义一个表达式:
execution(* com.weno...(..))
在这里面
标识符 | 含义 |
---|---|
execution | 表达式的主体 |
第一‘*’号 | 表示返回值的类型,*号代表任意类型 |
com.weno | 代表包,被切的地方 |
包后面的‘..’ | 代表当前包及其子包 |
第二个‘*’号 | 代表类,*号代表所有类 |
第三个‘*’号 | 代表方法,‘*’代表任意方法 |
(..) | 括号里面表示参数,两个点表示任意参数,也可以不加 |
在execution表达式中,参数严格区分基本数据类型和包装类。例如:
在com.weno.pojo.User.hasAge()中
public void hasAge(Integer age){
}
<!-- 这样是可以切到的 -->
<aop:before method="beforeAge" pointcut="execution(* com.weno.pojo.User.hasAge(Integer))"/>
<!-- 加入将Integer换成int,那么,无法执行切面 -->
<aop:before method="beforeAge" pointcut="execution(* com.weno.pojo.User.hasAge(int))"/>
好了,Spring的第三天就到这里了。明天就星期六了,IG加油(ง •_•)ง
在下一篇博客中,我将介绍一下aop注解版的使用