在java中oop是面向对象编程,这个从我们刚接触java的时候就知道了,今天我们要说的是aop面向切面的编程,其实关于aop的具体思想我也不能完整的表达出来,对于初学者来说只需要知道他是代理模式的一种实现。
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
对于程序员来说使用Spring AOP只需要关注下面几个点就可以了。
- 配置切面aspect
- 配置切入点pointcut
- 配置Advice
配置切面aspect
aspect就是定义在代理模式中添加到方法前或方法后的代码。
Spring中所有关于aop的配置都必须放在< aop:config>标签内
<bean id="aBean" class=""></bean>
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
</aop:aspect>
</aop:config>
上面的代码首先声明一个bean,然后在< aop:config>标签下声明< aop:aspect>标签,Id代表切面的名称,ref是这个切面引用的资源,也就是一个类(我们在上面声明过的bean),这个类里有需要在代理中添加的方法,现在说有一点抽象,看了后面的例子之后就能理解。
首先创建两个类:
public class AspectBiz {
}
public class MoocAspect {
}
然后在配置文件中添加:
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>
<bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
</aop:aspect>
</aop:config>
</beans>
配置切入点Pointcut
切入点Pointcut其实就是一个过滤器,过滤需要代理的方法。具体的过滤规则有很多中,这里就不详细讲了,大家可以查看官方文档或者直接百度。
接着上面的例子,我们接着写:
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>
<bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:pointcut expression="execution( *com.mss.aop.AspectBiz.*(..))"
id="moocPiontcut" />
</aop:aspect>
</aop:config>
</beans>
这里我们只是修改了一下配置文件添加了一个切入点,这个切入点代表(过滤)所有com.mss.aop.AspectBiz包下的方法。
配置Advice
当一个方法执行,Spring AOP 可以劫持一个执行的方法,在方法执行之前或之后添加额外的功能,Advice就是配置添加的功能的位置。
在Spring AOP中,有 5 种类型通知(advices)的支持:
- 通知(Advice)之前 - 方法执行前运行< aop:before />
- 通知(Advice)返回之后 – 方法执行后返回一个结果后运行< aop:after-returning />
- 通知(Advice)之后 – 该方法执行后运行< aop:after />(在aop:after-returning之后运行)
- 通知(Advice)抛出之后 – 运行方法抛出异常后< aop:after-throwing />
- 环绕通知 – 环绕方法执行运行,结合以上这三个通知< aop:around />
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>
<bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:pointcut expression="execution(* com.mss.aop.AspectBiz.*(..))"
id="moocPiontcut" />
//引用pointcut-ref
<aop:before method="before" pointcut-ref="moocPiontcut" />
//直接定义pointcut
<aop:before method="before"
pointcut="execution(* com.mss.aop.AspectBiz.*(..))" />
</aop:aspect>
</aop:config>
</beans>
配置Advice需要指定pointcut,我们可以引用上面声明的pointcut,或者直接在标签内定义一个pointcut。method是指定一个方法名,这个方法必须是在aspect中配置的类中的方法。由于我们使用的是
public class MoocAspect {
//切面,也就是插入的代码
public void before() {
System.out.println("MoocAspect before.");
}
}
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
}
}
写个测试类测试一下:
@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{
public TestOneInterface() {
super("classpath*:spring-ioc.xml");
}
@Test
public void test() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.biz();
}
}
运行结果:
MoocAspect before.
AspectBiz biz.
可以看到,我们成功把before() 中的代码插入到 biz() 之中,并且在biz()之前运行。
上面我们使用了< aop:before />,其他通知的使用方法是一样的,大家可以自己去测试一下,但是环绕通知有点特殊,下面我们讲一讲环绕通知:
环绕通知就是其他四种通知的结合,环绕通知需要在切面方法中添加一个ProceedingJoinPoint类型的参数,并且在切面方法中添加一行代码,看下面的方法:
public Object around(ProceedingJoinPoint pjp) {
Object obj = null;
try {
System.out.println("MoocAspect around 1.");
obj = pjp.proceed();
System.out.println("MoocAspect around 2.");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
可以看到方法中有一个ProceedingJoinPoint 类型的参数,并且多了一行obj = pjp.proceed();这行代码就是调用业务方法,这下就清晰了,我们可以在调用业务方法之前、之后、抛出异常中添加代码。这个方法有一个返回值,这个返回值就是业务方法的返回值,最后我们需要把这个返回值返回,如果你愿意甚至可以返回一个与业务方法完全无关的返回值。下面我们直接上例子:
首先在配置文件中添加环绕通知:
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>
<bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:pointcut expression="execution(* com.mss.aop.AspectBiz.*(..))"
id="moocPiontcut" />
<aop:around method="around" pointcut-ref="moocPiontcut"/>
</aop:aspect>
</aop:config>
</beans>
修改MoocAspect中的代码:
public class MoocAspect {
public Object around(ProceedingJoinPoint pjp) {
Object obj = null;
try {
//方法运行前
System.out.println("MoocAspect around 1.");
obj = pjp.proceed();
//方法运行后
System.out.println("MoocAspect around 2.");
} catch (Throwable e) {
//方法抛出异常后
System.out.println("MoocAspect afterThrowing.");
}
return obj;
}
}
修改AspectBiz中的代码:
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
}
}
测试代码:
@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{
public TestOneInterface() {
super("classpath*:spring-ioc.xml");
}
@Test
public void test() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.biz();
}
}
运行结果:
MoocAspect around 1.
AspectBiz biz.
MoocAspect around 2.
配置带参数的Advice
我们的业务方法大部分都是有参数的,如果想要在切面方法中使用这些方法怎么办呢?下面介绍一下带参数的Advice:
其实带参数和不带参数的Advice只是切入点Pointcut配置不一样,下面看一个例子:
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>
<bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:pointcut expression="execution(* com.mss.aop.AspectBiz.*(..))"
id="moocPiontcut" />
<aop:around method="aroundInit"
pointcut="execution(* com.mss.aop.AspectBiz.init(String, int)) and args(bizName, times)" />
</aop:aspect>
</aop:config>
</beans>
可以看到我们在Advice内配置了单独的pointcut,这个pointcut指定了具体的方法,以及方法的参数类型。下面在看一下这个方法:
public class AspectBiz {
public void init(String bizName, int times) {
System.out.println("AspectBiz init : " + bizName + " " + times);
}
}
public class MoocAspect {
public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) {
System.out.println(bizName + " " + times);
Object obj = null;
try {
System.out.println("MoocAspect aroundInit 1.");
obj = pjp.proceed();
System.out.println("MoocAspect aroundInit 2.");
} catch (Throwable e) {
System.out.println("MoocAspect afterThrowing.");
}
return obj;
}
}
切面方法除了必须的ProceedingJoinPoint 类型的参数之外,还添加了业务方法中的两个参数。下面看一下测试方法:
@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{
public TestOneInterface() {
super("classpath*:spring-ioc.xml");
}
@Test
public void test() {
AspectBiz aspectBiz=super.getBean("aspectBiz");
aspectBiz.init("mss", 1);
}
}
运行结果:
mss 1
MoocAspect aroundInit 1.
AspectBiz init : mss 1
MoocAspect aroundInit 2.
introduction
一个完整的introduction配置:
<aop:declare-parents types-matching=""
implement-interface=""
default-impl="" />
关于introduction的意思,我举一个例子:
有个类叫小明(biz),小明隔壁住着老王(FitImpl),老王实现了一个技能叫开豪车(Fit)
现在上帝声明了一个切面,这个切面给小明指定一个新的爹叫老王,于是小明每次叫爸爸的时候就能开豪车了~
类匹配(小明):type-matching
接口(开豪车):implement-interface
接口的实现类(老王):default-impl
introduction的作用:小明干自己的事的时候(叫爸爸)能莫名其妙地开上豪车而不用做多余的工作,这些工作由上帝(AOP)帮他完成,这叫“解耦”
例子:
创建一个接口以及一个接口实现类:
public interface Fit {
void filter();
}
public class FitImpl implements Fit {
public void filter() {
System.out.println("FitImpl filter.");
}
}
在配置文件中配置introduction:
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="moocAspect" class="com.mss.aop.MoocAspect"></bean>
<bean id="aspectBiz" class="com.mss.aop.AspectBiz"></bean>
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:declare-parents
//匹配所有com.mss.aop包下的类
types-matching="com.mss.aop.*(+)"
//定义的接口
implement-interface="com.mss.aop.Fit"
//定义的接口实现类
default-impl="com.mss.aop.FitImpl" />
</aop:aspect>
</aop:config>
</beans>
测试类(将上一节中的类强转):
@RunWith(BlockJUnit4ClassRunner.class)
public class TestOneInterface extends UnitTestBase{
public TestOneInterface() {
super("classpath*:spring-ioc.xml");
}
@Test
public void test() {
Fit fit=(Fit)super.getBean("aspectBiz");
fit.filter();
}
}
运行结果:
FitImpl filter.
重点内容:所有基于配置文件的aspect只支持singleton