对于spring的aop,我们也通过上面几节课进行了了解,也学习了aop的两种动态代理,但是我们并没有进入springAOP的切面编程,这节课我们就讲一下spring中AOP的增强。Spring中使用增强类来进行横切的逻辑,同时增强类还包括横切逻辑在方法的方位信息。
1、增强有哪些种类,他们的接口有什么关系?
增强可以分为以下五种:
- 前置增强:实现MethodBeforeAdvice接口,在方法执行之前进行横切逻辑。
- 后置增强:实现AfterReturningAdvice接口,在方法执行后进行横切逻辑。
- 环绕增强:实现MethodInterceptor接口,分别在方法执行前后进行横切逻辑。
- 异常抛出增强:实现ThrowsAdvice接口,在目标方法抛出异常后进行横切逻辑。
- 引介增强:实现IntroductionInterceptor接口,表示在目标类中添加一些新的属性和方法。
这些接口的最终接口都是Advice。
2、前置增强如何使用?
前置增强就是在目标类方法执行之前进行横切逻辑,我们研究的都是在方法之前进行横切,所以使用MethodBeforeAdvice接口,这个接口的父接口是BeforeAdvice接口,这个父接口也是前置增强的接口,但是不在我们现在研究的范围之内。代码如下:
Lecturer.java(目标类)
package com.spring.test;
public class Lecturer{
public void ClassBegin() {
// TODO Auto-generated method stub
System.out.println("讲师开始上课,同学激烈的讨论!");
}
}
LecturerAdvice.java(增强类)
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class LecturerAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
if(method.getName().equals("ClassBegin")) {
System.out.println("讲师开始点名。");
}
}
}
现在我们需要将增强织入到目标类中,我们需要使用ProxyFactory类进行织入。ProxyFactory需要注入目标类,并使用方法addAdVice方法织入增强,结合我们学过的ioc容器知识,可以写出配置文件和测试类如下:
ApplicationContext.xml
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 目标类 -->
<bean id="lecturer" class="com.spring.test.Lecturer"></bean>
<!-- 增强类 -->
<bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
<!-- 代理工厂 -->
<bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
<!-- 注入目标类 -->
<property name="target" ref="lecturer"></property>
</bean>
</beans>
SpringTest.java
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
// 获取增强类
LecturerAdvice advice = (LecturerAdvice) content.getBean("advice");
// 获取代理工厂
ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
// 添加增强
pf.addAdvice(advice);
// 获取代理对象
Lecturer lecturer = (Lecturer) pf.getProxy();
lecturer.ClassBegin();
}
}
设置目标类,只需要将ProxyFactory对象中的target属性设置成目标类的bean,然后再使用ProxyFactory的bean调用addAdvice方法织入增强,至此,前置增强织入成功,最后控制台打印如下:
讲师开始点名。
讲师开始上课,同学激烈的讨论!
3、后置增强如何使用?
结合前置增强的使用,后置增强的使用只需要把增强类的接口修改,重写新接口的方法,就变成了后置增强,除增强类其他类不变,增强类代码如下:
LecturerAdvice.java
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class LecturerAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
if(method.getName().equals("ClassBegin")) {
System.out.println("讲师开始点名。");
}
}
}
最后控制台输出如下:
讲师开始上课,同学激烈的讨论!
讲师开始点名。
我们发现横切逻辑已经到了目标方法的后面执行,至此,后置增强完成。
4、环绕增强如何使用?
后置增强在前置增强的基础上,只是改变了增强类的接口,那环绕增强我们看一下增强类该如何编码?
package com.spring.test;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class LecturerAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
// TODO Auto-generated method stub
// 目标方法执行前的横切逻辑
System.out.println("讲师上课前点名。");
// 使用反射机制调用目标方法
invocation.proceed();
// 目标方法执行后的横切逻辑
System.out.println("讲师下课前点名");
return null;
}
}
控制台打印如下:
讲师上课前点名。
讲师开始上课,同学激烈的讨论!
讲师下课前点名
5、异常抛出增强如何使用?
异常抛出增强,只有在目标方法抛出异常的时候才会进行横切逻辑,因此为了达到目标类具有异常抛出的目的,我们简单修改一下目标类,代码如下:
Lecturer.java
package com.spring.test;
import java.util.Scanner;
public class Lecturer{
public void ClassBegin(){
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
int a = 5;
int b = input.nextInt();
System.out.println("讲师说"+a+"除以"+b+"的结果是"+a/b);
}
}
如果我们在控制台输入的数字是0,则目标方法抛出异常,就可以检测异常抛出增强是否织入成功了,下面我们开始写增强类。
LecturerAdvice.java(增强类)
package com.spring.test;
import org.springframework.aop.ThrowsAdvice;
public class LecturerAdvice implements ThrowsAdvice {
public void afterThrowing(Exception e) throws Exception{
// TODO Auto-generated method stub
System.out.println("方法报错,错误信息是"+e.getMessage()+"\n");
}
}
我们为异常抛出增强实现了ThrowsAdvice接口,却发现这个接口没有任何方法可以重写。我们就称这样的接口为标识接口,标识接口没有任何方法,只是为了让我们知道这个类是增强Advice实现类而已。
虽然接口没有提供方法重写,但是我们需要自己写个方法,让spring的反射机制可以找到横切逻辑代码,这个方法名固定为“afterThrowing”,权限必须是public的,传参方式有两种,如下:
public void afterThrowing(Method method,Object[] args,Object obj,Exception e) throws Exception;
或者
public void afterThrowing(Exception e) throws Exception;
我们看到控制台打印如下:
发现先进行了横切逻辑,又进行了异常抛出。那这样是不是就证明了横切逻辑会在目标方法之前执行呢?并不是,横切逻辑会在异常代码那一行之前执行,并不会在目标方法的前面执行。
6、异常抛出增强一定要抛出目标方法的的异常吗?
上个问题中,我们发现目标方法的异常仍然继续抛出,这样我们就捕获不到这个异常,如果在目标方法中使用try捕获异常,横切逻辑又不会执行。
这时候,我们需要让目标方法向上抛异常,在目标方法调用的地方再使用try捕获异常。这样,我们既可以捕获异常,又可以在异常抛出的时候进行横切逻辑了,代码如下:
Lecturer.java(目标类)
package com.spring.test;
import java.util.Scanner;
public class Lecturer{
public void ClassBegin() throws Exception{
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
int a = 5;
int b = input.nextInt();
System.out.println("讲师说"+a+"除以"+b+"的结果是"+a/b);
}
}
LecturerAdvice.java(增强类)
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class LecturerAdvice implements ThrowsAdvice {
public void afterThrowing(Method method,Object[] args,Object obj,Exception e) throws Exception{
// TODO Auto-generated method stub
System.out.println("方法报错,错误信息是"+e.getMessage()+"\n");
}
}
ApplicationContext.xml(spring配置文件)
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 目标类 -->
<bean id="lecturer" class="com.spring.test.Lecturer"></bean>
<!-- 增强类 -->
<bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
<!-- 代理工厂 -->
<bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
<!-- 设置目标类 -->
<property name="target" ref="lecturer"></property>
</bean>
</beans>
SpringTest.java(测试类)
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
// 获取增强类
LecturerAdvice advice = (LecturerAdvice) content.getBean("advice");
// 获取代理工厂
ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
// 添加增强
pf.addAdvice(advice);
// 获取代理对象
Lecturer lecturer = (Lecturer) pf.getProxy();
try {
lecturer.ClassBegin();
} catch (Exception e) {
// TODO Auto-generated catch block
}
}
}
这样,控制台就不会抛出异常了。使用异常抛出增强后,Lecturer类只需要负责他自己的方法就可以了,不需要再为事务管理而编写臃肿的代码,也就从事务管理中脱离出来了,java的历史揭开了新的一页。
7、引介增强如何使用?
引介增强有些特殊,上面提到的几个增强都是在方法的不同位置进行横切逻辑,都是方法级别的。而引介增强为类增加属性和方法,属于类级别的,而引介增强给目标类增加属性和方法,是通过接口实现的。
换句话说,如果引介增强可以给目标类添加了一些目标接口,就意味着给目标类添加了若干属性和方法,下面我们来举个例子。现有如下需求:
学校规定讲师在上课前要点名,但是如果学校有活动必须暂停授课
Lecturer.java(目标类)
package com.spring.test;
/**
* 讲师类,具有上课方法
* @author beijingduanluo
*
*/
public class Lecturer {
public void classBegin() {
System.out.println("讲师传授新技术,同学们讨论很激烈!");
}
}
School.java(目标接口)
package com.spring.test;
/**
* 学校的接口,具有举办活动的方法
* @author beijingduanluo
*
*/
public interface School {
public void setActive(boolean active);
}
现在需要将School接口添加到Lecturer类中。
LecturerAdvice.java(引介增强类)
package com.spring.test;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
/**
* 本类继承IntroductionInterceptor接口的支持类DelegatingIntroductionInterceptor,成为引介增强
* 本增强类实现需要添加的目标接口
* @author beijingduanluo
*
*/
public class LecturerAdvice extends DelegatingIntroductionInterceptor implements School{
private boolean active;
/**
* 代理目标类实现接口的方法
*/
public void setActive(boolean active) {
// TODO Auto-generated method stub
this.active = active;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// TODO Auto-generated method stub
// 如果目标类调用的不是classBegin方法,不进行横切逻辑
if(!mi.getMethod().getName().equals("classBegin")) {
super.invoke(mi);
return null;
}
// 判断学校是否有活动
if(active) {
// 如果有活动,则停止讲师上课
System.out.println("学校有活动,讲师暂停上课。");
}else {
// 学校没有活动,讲师点名
System.out.println("讲师开始点名。");
// 讲师正常上课
super.invoke(mi);
}
return null;
}
}
applicationContext.xml(spring的配置文件)
<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 配置目标类 -->
<bean id="lecturer" class="com.spring.test.Lecturer"></bean>
<!-- 配置增强类 -->
<bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
<!-- 配置代理工厂 -->
<bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
<!-- 注入目标类 -->
<property name="target" ref="lecturer"></property>
<!-- 因为引介增强是继承关系,所以使用CGLib,进行下面修改 -->
<property name="ProxyTargetClass" value="true"></property>
<!-- 注入需要添加的目标接口 -->
<property name="interfaces" value="com.spring.test.School"></property>
</bean>
</beans>
SpringTest.java(测试类)
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvice;
import com.spring.test.School;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
LecturerAdvice advice = (LecturerAdvice) content.getBean("advice");
ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
// 将增强加入代理工厂
pf.addAdvice(advice);
// 获取讲师的代理对象
Lecturer lecturer = (Lecturer) pf.getProxy();
// 没有举办活动时候讲师上课
lecturer.classBegin();
System.out.println("-------------学校举办活动-------------");
// 此时已经将接口加到目标类Lecturer中,如果调用接口方法,需要将目标对象强转
School school = (School) lecturer;
// 学校举办活动
school.setActive(true);
// 举办活动的时候讲师上课
lecturer.classBegin();
}
}
控制台
讲师开始点名。
讲师传授新技术,同学们讨论很激烈!
————-学校举办活动————-
学校有活动,讲师暂停上课。
这样,就可以了。有的同学会问,你为什么说接口给了目标类呢?我们看School school = (School) lecturer;这行代码,将目标类强转成了School接口,并且强转成功了,这足以说明我们给目标类添加了School接口。
8、这样是不是就是spring的AOP了呢?
这节课,我们讲的是增强,默认会把增强织入到目标类的所有方法上。而对于spring的AOP而言,不仅仅是需要添加哪些逻辑,还有将横切逻辑添加到哪些切面上。想知道切面是怎么回事,就继续我们下节课的内容吧!