我们再来回忆一下切面是什么,切面就是将增强织入到某个切点。上节课我们知道,增强一方面包含了横切逻辑,另一方面包含了连接点(也就是方法的前面、后面、环绕等等),但是增强不能决定给哪个类的哪个方法横切(也就是不能决定切点),这节课我们就能找到这个切点,然后增强和切点共同组成切面。
1、如何研究切面比较简单?
笼统来说,切面可以分成增强和切点,上节课我们讲过了很多增强,而切面也会有很多种类,可以说增强和切面两两组合可以组成切面,为了方便研究,本节课如果不特殊说明,都默认使用前置增强。
切面分为以下三类:
- Advisor:代表一般切面,他仅仅包含一个advice。简单来说就是一个增强给目标类的所有方法进行织入,我们上节课研究的就是一个一般切面。
- PointcutAdvisor:代表切点切面,包含Advice和Pointcut两个类,他比一般切面多了切点,因此切面更加完整,所以很少用到上面的一般切面。
- IntroductionAdvisor:代表引介切面,他是对应引介增强的切面,因此研究这个切面不能使用前置增强,必须使用引介增强。
对于一般切面,我们上节课一直在说,这节课不再赘述。切点切面分很多种类,我们先对引介切面进行研究,然后再仔细研究切点切面。
2、引介切面如何使用?
前面我们讲过了引介增强,其实引介切面就是对引介增强的一个包装,使用引介切面我们可以更简单灵活的为目标类添加接口。仍然使用上节课的案例,看一下引介切面的代码:
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);
}
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(配置文件)
<?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="advisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg>
<bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
</constructor-arg>
</bean>
<!-- 配置代理工厂 -->
<bean id="proxyFactory" class="org.springframework.aop.framework.ProxyFactory">
<!-- 注入目标类 -->
<property name="target" ref="lecturer"></property>
<!-- 因为引介增强是继承关系,所以使用CGLib,进行下面修改 -->
<property name="proxyTargetClass" value="true"></property>
<!-- 没有去注入interfaces属性值,会默认将引介增强中所有接口给到目标类 -->
</bean>
</beans>
SpringTest.java(测试类)
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
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");
DefaultIntroductionAdvisor advisor = (DefaultIntroductionAdvisor) content.getBean("advisor");
ProxyFactory pf = (ProxyFactory) content.getBean("proxyFactory");
// 没有配置引介增强,而是配置引介增强的包装,引介切面
pf.addAdvisor(advisor);
// 获取讲师的代理对象
Lecturer lecturer = (Lecturer) pf.getProxy();
// 没有举办活动时候讲师上课
lecturer.classBegin();
System.out.println("-------------学校举办活动-------------");
// 此时已经将接口加到目标类Lecturer中,如果调用接口方法,需要将目标对象强转
School school = (School) lecturer;
// 学校举办活动
school.setActive(true);
// 举办活动的时候讲师上课
lecturer.classBegin();
}
}
3、切点切面的体系结构是什么样的?
首先看一下切点切面PointcutAdvisor类的结构图:
PointcutAdvisor类有六个具体的实现类,这六个实现类分别是上图中蓝色的部分。这些类没必要深入研究,我们知道有这么几个类,而且他们都实现PointcutAdvisor接口就可以了。
那这些蓝色标准的类怎么使用呢?我们会在下面一一讨论。切点切面的匹配有这么几种方法:静态普通方法名匹配切面、静态正则表达式方法匹配切面、动态切面、流程切面、符合切点切面。我们只对两种静态切面进行研究。
4、静态普通方法名匹配切面是如何实现的?
静态普通方法名匹配切面,通过扩展StaticMethodMatcherPointcutAdvisor类实现,是程序运行前就匹配好,而不是匹配静态方法。
现有如下需求:
学校要求讲师在上课前进行点名。
上代码:
Lecturer.java(目标类)
package com.spring.test;
/**
* 讲师类,具有上课方法
* @author beijingduanluo
*
*/
public class Lecturer {
public void classBegin() {
System.out.println("讲师传授新技术,同学们讨论很激烈!");
}
}
Others.java(目标类干扰类)
package com.spring.test;
/**
* 讲师类的干扰类,判断是否定位到Lecturer类中。
* @author beijingduanluo
*
*/
public class Others {
public void classBegin() {
System.out.println("其他人上课不需要点名");
}
}
LecturerAdvice.java(增强类)
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 使用前置增强
* @author beijingduanluo
*
*/
public class LecturerAdvice implements MethodBeforeAdvice{
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("讲师开始点名");
}
}
LecturerAdvisor.java(切面类)
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class LecturerAdvisor extends StaticMethodMatcherPointcutAdvisor{
/**
* 重写matches方法,匹配方法名,但是所有的类都会符合
*/
public boolean matches(Method method, Class<?> targetClass) {
// TODO Auto-generated method stub
// 匹配成功classBegin方法返回true
return method.getName().equals("classBegin");
}
/**
* 手动重写getClassFilter方法,匹配响应的类
*/
@Override
public ClassFilter getClassFilter() {
// TODO Auto-generated method stub
// 返回带有Lecturer或者其子类的类过滤器
return new ClassFilter() {
public boolean matches(Class<?> clazz) {
// TODO Auto-generated method stub
// 匹配Lecturer类或者其子类返回true
return Lecturer.class.isAssignableFrom(clazz);
}
};
}
}
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 -->
<bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
<!-- 配置切面的bean -->
<bean id="advisor" class="com.spring.test.LecturerAdvisor">
<!-- 将增强的bean注入到切面中 -->
<property name="advice" ref="advice"></property>
</bean>
<!-- 分别创建两个目标类的bean -->
<bean id="lecturer" class="com.spring.test.Lecturer"></bean>
<bean id="others" class="com.spring.test.Others"></bean>
<!-- 配置代理工厂 -->
<bean id="proxyFactory" abstract="true" class="org.springframework.aop.framework.ProxyFactory"></bean>
<!-- 通过继承上面的抽象bean创建Lecturer的代理工厂 -->
<bean id="lecturerpf" parent="proxyFactory">
<property name="target" ref="lecturer"></property>
</bean>
<!-- 通过继承上面的抽象bean创建Others的代理工厂 -->
<bean id="otherspf" parent="proxyFactory">
<property name="target" ref="others"></property>
</bean>
</beans>
SpringTest.java(测试类)
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.LecturerAdvisor;
import com.spring.test.Others;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
LecturerAdvisor advisor = (LecturerAdvisor) content.getBean("advisor");
System.out.println("======lecturer的代理工厂,并调用Lecturer的方法");
ProxyFactory lpf = (ProxyFactory) content.getBean("lecturerpf");
lpf.addAdvisor(advisor);
Lecturer lecturer = (Lecturer) lpf.getProxy();
lecturer.classBegin();
System.out.println("======Others的代理工厂,并调用Others的方法");
ProxyFactory opf = (ProxyFactory) content.getBean("otherspf");
opf.addAdvisor(advisor);
Others others = (Others) opf.getProxy();
others.classBegin();
}
}
控制台打印
======lecturer的代理工厂,并调用Lecturer的方法
讲师开始点名
讲师传授新技术,同学们讨论很激烈!
======Others的代理工厂,并调用Others的方法
其他人上课不需要点名
我们发现,对于Lecturer的classBegin方法,织入了增强,而对于Others的classBegin没有织入增强,这就是Advisor进行了类和方法的匹配的结果。
5、静态正则表达式方法匹配切面如何实现?
静态正则表达式方法匹配切面扩展的是RegexpMethodPointcutAdvisor实现类,这个实现类已经有了完善的功能,我们只需要在配置文件中配置这个实现类,不需要重写去创建advisor了。代码如下:
Lecturer.java(目标类)
package com.spring.test;
/**
* 讲师类,具有上课方法
* @author beijingduanluo
*
*/
public class Lecturer {
public void classBegin() {
System.out.println("讲师传授新技术,同学们讨论很激烈!");
}
}
Others.java(干扰目标类)
package com.spring.test;
/**
* 讲师类的干扰类,判断是否定位到Lecturer类中。
* @author beijingduanluo
*
*/
public class Others {
public void classBegin() {
System.out.println("其他人上课不需要点名");
}
}
LecturerAdvice.java(增强类)
package com.spring.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 使用前置增强
* @author beijingduanluo
*
*/
public class LecturerAdvice implements MethodBeforeAdvice{
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("讲师开始点名");
}
}
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 -->
<bean id="advice" class="com.spring.test.LecturerAdvice"></bean>
<!-- 配置切面的bean -->
<bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 将增强的bean注入到切面中 -->
<property name="advice" ref="advice"></property>
<!-- 使用正则表达式匹配方法的全限定名 -->
<property name="patterns">
<list>
<value>com.spring.test.Lecturer.classBegin</value>
</list>
</property>
</bean>
<!-- 分别创建两个目标类的bean -->
<bean id="lecturer" class="com.spring.test.Lecturer"></bean>
<bean id="others" class="com.spring.test.Others"></bean>
<!-- 配置代理工厂 -->
<bean id="proxyFactory" abstract="true" class="org.springframework.aop.framework.ProxyFactory"></bean>
<!-- 通过继承上面的抽象bean创建Lecturer的代理工厂 -->
<bean id="lecturerpf" parent="proxyFactory">
<property name="target" ref="lecturer"></property>
</bean>
<!-- 通过继承上面的抽象bean创建Others的代理工厂 -->
<bean id="otherspf" parent="proxyFactory">
<property name="target" ref="others"></property>
</bean>
</beans>
SpringTest.java(测试类)
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import org.springframework.aop.support.RegexpMethodPointcutAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.test.Lecturer;
import com.spring.test.Others;
public class SpringTest{
public static void main(String[] args) {
ApplicationContext content = new ClassPathXmlApplicationContext("applicationcontext.xml");
RegexpMethodPointcutAdvisor advisor = (RegexpMethodPointcutAdvisor) content.getBean("advisor");
System.out.println("======lecturer的代理工厂,并调用Lecturer的方法");
ProxyFactory lpf = (ProxyFactory) content.getBean("lecturerpf");
lpf.addAdvisor(advisor);
Lecturer lecturer = (Lecturer) lpf.getProxy();
lecturer.classBegin();
System.out.println("======Others的代理工厂,并调用Others的方法");
ProxyFactory opf = (ProxyFactory) content.getBean("otherspf");
opf.addAdvisor(advisor);
Others others = (Others) opf.getProxy();
others.classBegin();
}
}
控制台打印
======lecturer的代理工厂,并调用Lecturer的方法
讲师开始点名
讲师传授新技术,同学们讨论很激烈!
======Others的代理工厂,并调用Others的方法
其他人上课不需要点名
我们发现,这样也可以将增强织入到响应的类的响应方法上。在配置文件中,我写的是方法的全限定名,如果使用正则表达式也是可以匹配多个对应方法的。