在面试中我们经常被问到spring的AOP,今天我也说一下AOP.
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP底层采用两种动态代理模式实现:
1.JDK的动态代理
2. CGLIB动态代理
当被代理的对象没有实现接口时候,使用的是CGLIB代理,当被代理的对象实现接口时候使用的是JDK代理,当然也可以强制使用CGLIB代理。
spring有支持3中类型实现AOP
- 需要实现接口,手工创建代理,最原始的实现方式
- 使用XML配置。aop命名空间
- 使用注解(@AspectJ)
我们今天主要是讲解最原始的方式,手工创建代理,实现AOP.这种当时几乎不用了,主要是让大家理解AOP的作用以及原理,学习之前我们先搞清楚几个概念.
1.切面(Aspect)
交叉业务逻辑,例如事务、安全、日志等,称为切面。
2.目标对象(Target)
业务类的对象,称为目标对象。
3.织入(Weaving)
将切面插入到目标对象的目标方法的过程,称为织入。
4.连接点(JoinPoint)
目标对象中可以被切面织入的方法。
5.切入点(Pointcut)
目标对象中真正被切面织入的方法。切入点一定是连接点,但连接点不一定是切入点。被标记为final的方法是不能作用连接点与切入点的。
6.通知(Advice)
通知是切面的一种,可以完成简单的织入功能。通知可以定义切面织入的时间点,切入点定义了切面织入的位置。
7.顾问(Advisor)
顾问是切面的一种,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为 更复杂的切面的装配器。
上面的通知(Advice)和顾问(Advisor)是两种织入的方式,他们两也是有区别的,通知是给目标对象的所有方法都会织入切面,而Advisor是有选择的给目标对象的方法织入切面。我们接下来就看一下,如何基于手动创建代理方式并且使用Advisor织入来演示Spring AOP,先看一下他的关系图
我们按照上面的图实现AOP
- 实现classFilter 和MethodMatcher
package com.jiepi.spring.util;
import com.jiepi.spring.serviceImp.Person;
import org.springframework.aop.ClassFilter;
public class MyClassFilter implements ClassFilter {
/*
* 1.一个接口下会有多个实现类
* 2.判断当前实现类是不是我们织入方式关心的目标类
* BaseService接口我们现在只想管理Person.
* 参数:就是当前被拦截类:可能Person,可能Gog
* */
@Override
public boolean matches(Class<?> clazz) {
if (clazz == Person.class) {
return true;
}
return false;
}
}
代码语言:javascript复制package com.jiepi.spring.util;
import org.springframework.aop.MethodMatcher;
import java.lang.reflect.Method;
public class MyMethodMatcher implements MethodMatcher {
/*
* 被监控接口比如(BaseService),没有重载方法
* 每一个方法名称都是以唯一
* 此时可以采用 static检测方式,只根据方法名称判断
* 参数:method: 接口中某一个方法
* targetClass: 接口中一个实现类
*
* 业务:只想为Person类中play方法提供织入
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
String methodName = method.getName();
if ("play".equals(methodName)) {
return true;
}
return false;
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
}
2.实现切入点PointCut实现获取那个对象的那个方法要进行织入切面
代码语言:javascript复制package com.jiepi.spring.util;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
public class MyPointCut implements Pointcut {
private ClassFilter classFilter;
private MethodMatcher methodMatcher;
public void setClassFilter(ClassFilter classFilter) {
this.classFilter = classFilter;
}
public void setMethodMatcher(MethodMatcher methodMatcher) {
this.methodMatcher = methodMatcher;
}
@Override
public ClassFilter getClassFilter() {
return this.classFilter;
}
@Override
public MethodMatcher getMethodMatcher() {
return this.methodMatcher;
}
}
3.实现 切面 advice
代码语言:javascript复制package com.jiepi.spring.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class MyAfterAdvice implements AfterReturningAdvice {
//切面:次要业务
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("玩完之后我要给洁癖洗澡");
}
}
4.实现PointcutAdvisor实现绑定切入点和切面
代码语言:javascript复制package com.jiepi.spring.util;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
public class MyPointCutAdvisor implements PointcutAdvisor {
private Pointcut pointcut;//当前拦截对象和对象调用主要业务方法 person对象.play()
private Advice advice; //次要业务以及次要业务与主要业务执行顺序
public void setPointcut(Pointcut pointcut) {
this.pointcut = pointcut;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public boolean isPerInstance() {
return false;
}
}
5.目标对象
代码语言:javascript复制package com.jiepi.spring.service;
public interface BaseService {
void eat();
void play();
}
代码语言:javascript复制package com.jiepi.spring.serviceImp;
import com.jiepi.spring.service.BaseService;
public class Person implements BaseService {
@Override
public void eat() {
System.out.println("洁癖主人吃汉堡包");
}
@Override
public void play() {
System.out.println("洁癖主人玩电脑");
}
}
6.配置依赖注入,实现手动创建代理对象
代码语言:javascript复制<?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:c="http://www.springframework.org/schema/c"
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">
<!-- 注册被监控实现类 -->
<bean id="person" class="com.jiepi.spring.serviceImp.Person"/>
<bean id="dog" class="com.jiepi.spring.serviceImp.Dog"/>
<!-- 注册通知实现类 -->
<bean id="after" class="com.jiepi.spring.advice.MyAfterAdvice"/>
<!-- 注册类型过滤器 -->
<bean id="classFilter" class="com.jiepi.spring.util.MyClassFilter"/>
<!-- 注册方法匹配器 -->
<bean id="methodMatcher" class="com.jiepi.spring.util.MyMethodMatcher"/>
<!-- 注册切入点 -->
<bean id="pointCut" class="com.jiepi.spring.util.MyPointCut">
<property name="classFilter" ref="classFilter"/>
<property name="methodMatcher" ref="methodMatcher"/>
</bean>
<!-- 注册顾问 -->
<bean id="myAdvisor" class="com.jiepi.spring.util.MyPointCutAdvisor">
<property name="advice" ref="after"/>
<property name="pointcut" ref="pointCut"/>
</bean>
<!-- 注册代理对象工厂 -->
<!--
此时生成代理对象,只会负责person.play方法监控
与Advice不同,不会对BaseService所有的方法进行监控
-->
<bean id="personFactory" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="person"/>
<property name="interceptorNames" value="myAdvisor"/>
</bean>
</beans>
7.测试结果
代码语言:javascript复制package com.jiepi.spring;
import com.jiepi.spring.service.BaseService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain {
@Test
public void test() {
ApplicationContext factory = new ClassPathXmlApplicationContext("spring_config.xml");
BaseService service = (BaseService) factory.getBean("personFactory");
service.play();
System.out.println("===========下面是没有进行代理的方法================");
service.eat();
}
}
运行结果:
洁癖主人玩电脑
玩完之后我要给洁癖洗澡
===========下面是没有进行代理的方法================
洁癖主人吃汉堡包
可以看到我们的结果,仅仅对person目标对象中的play使用了AOP,织入了其他业务。
这个例子仅仅是让大家理解SpringAOP.并不建议使用,因为写起来太繁琐了,我们大部分工作用的都是AspectJ.其他方式的使用有机会再和大家分享.