什么是AOP切面编程?
官方点的回答就是:这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
就像做饭一样,如果要做辣子鸡和葫芦鸡,那么他们都要将洗干净,接下来才会出现做法的区别。
aop切面编程就像是把鸡洗干净这个过程,是两个菜共有的步骤。并且和他们自身没有关系。
用图来解释:
洗干净就像一个切面,他们经过了这个切面,就被处理成另一种样子了
aop的几个重要概念:
- 切面(Aspect):简单的理解就是把那些与核心业务无关的代码提取出来,进行封装成一个或几个模块用来处理那些附加的功能代码。
- 连接点(JoinPoint):能被添加通知代码的地点
- 切入点(CutPoint):再连接点被实现了具体的拦截方法就叫切入点
- 通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作
- 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时 完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样, 在运行时完成织入。
AOP编程的实现通过代理的技术实现
什么是动态代理:代理模式是常用的Java设计模式。代理类主要负责为委托类预处理消息、过滤信息、把消息转发给委托类,以及事后处理信息等。
在AOP中又是怎么实现的呢?
不能直接通过代理具体的java类来实现,必须让代理对象通过代理这个java类实现的接口才能进行代理。
然后把要在委托类中实现的通知全部织入到连接点,此时连接点就变成了切入点。
步骤:
1.先定义实现类的接口,然后继承接口实现具体的类
2.编写通知代码,就是要被添加到切入点的代码(连接点的位置通过这个通知继承的接口来确定)
3.在bean.xml文件中先配置被代理对象,进行对象等实例化和值的传递
4.在bean.xml文件中通知对象,也就是处理器对象。
5.在bean.xml文件中配置被代理的接口集
6.在bean.xml文件中将通知织入代理对象
(到这里将通知和被代理接口都配置在了代理对象中,则完成关联。具体关联方法通过Spring源码实现)
7.在bean.xml文件中配置被代理对象
先看类的接口:
package it.aop;
public interface TestServiceInter {//通过代理这个接口来实现代理他的实现类
public void sayHello();
}
类的具体实现:
package it.aop;
public class TestService implements TestServiceInter{//代理的时候通过代理这个接口来代理这个类
private String name;public void sayHello() {
System.out.println("say Hello");
//出异常的话会调用异常处理器 int x=9/0;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
后置处理器:在所有方法的后面运行处理器里的方法
package it.aop;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.BeforeAdvice;
public class MyMethodAfterAdvice implements AfterReturningAdvice {
/*
* returnValue:获取的方法返回值
* method:获取的方法
* args:方法的参数
* target:目标对象
* */
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置处理器实现"+method.getName());
}
}
前置处理器:在所有方法的前面运行处理器里的方法
package it.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/*
* method:被调用方法名字
* args:给method传递的参数
* target:目标对象,这个target和bean里的target并没有关系
* */
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO 自动生成的方法存根
System.out.println("记录日志前置处理器实现"+method.getName()+target.getClass());
}
}
环绕处理器有一个MethodInvocation arg0参数 ,这个参数需要调用proceed()方法得到一个对象然后函数返回,这样才能成功调用,相当于综合了前置处理器和后置处理器。
package it.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// TODO Auto-generated method stub
System.out.println("环绕处理器处理前");
Object obj=arg0.proceed();
System.out.println("环绕处理器处理前");
return obj;
}
}
异常处理器:在所有方法出问题调用这个处理器里的方法
package it.aop;
import java.lang.reflect.Method;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.ThrowsAdvice;
public class MyThrowsAdvice implements ThrowsAdvice {
/*Method m:出异常的方法
* Object[] os:异常方法的方法参数
* Object target:目标对象
* Exception e:异常
* */
public void afterThrowing(Method m,Object[] os,Object target,Exception e) {
System.out.println("出问题了"+e.getMessage());
}
}
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
>
<!--配置被代理对象-->
<bean id="testService" class="it.aop.TestService">
<property name="name" value="余飞" />
</bean>
<!--配置处理器对象-->
<bean id="myMethodBeforeAdvice" class="it.aop.MyMethodBeforeAdvice" ></bean>
<!--配置处理器对象-->
<bean id="myMethodAfterAdvice" class="it.aop.MyMethodAfterAdvice" />
<!--配置环绕处理器对象-->
<bean id="myMethodInterceptor" class="it.aop.MyMethodInterceptor" ></bean>
<!--配置错误处理器对象-->
<bean id="MyThrowsAdvice" class="it.aop.MyThrowsAdvice" />
<!-- 配置代理对象 -->
<bean id="proxyFactoryBean" class="org..springframework.aop.framework.ProxyFactoryBean" >
<!--配置代理的接口集,因为要通过代理类实现的接口来实现代理-->
<property name="proxyInterfaces" >
<list>
<value>it.aop.TestServiceInter</value>
</list>
</property>
<!--把通知织入到代理对象-->
<property name="interceptorNames">
<!--相当于把MyMethodBeforeAdvice前置通知和代理对象相关联-->
<!--因为有多个通知,所以要用list存入-->
<list>
<!--因为前置通知定义了自定义切入点,所以名字要用自定义切入点的名字-->
<value>myMethodBeforeAdvice</value>
<value>myMethodAfterAdvice</value>
<value>myMethodInterceptor</value>
<value>MyThrowsAdvice</value>
</list>
</property>
<!--配置被代理对象,可以指定-->
<property name="target" ref="testService"/>
</bean>
</beans>
如果想要定义某个处理器只对一个方法实行处理,那就需要用到引入通知可以自定义切入点,只需要在bean中配置。
<!--定义切入点,就是定义再那个函数的实现位置-->
<bean id="myMethodBeforeAdviceFliter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"><property name="advice" ref="myMethodBeforeAdvice" /> //需要控制在哪里实现的处理器
<property name="mappedNames"> //控制处理器要进行通知的方法
<list>
<value>sayHello</value>
</list>
</property>
</bean>
只需要加这段代码,选择需要进行前置配置的方法,但是还要将通知织入到代理对象中里的<value></value>值改为定义切入点的id名称。
AspectJ的aop编程:
一直发现上面的方法太麻烦,好在有发现了另一种方法,就是使用aspectJ框架完成aop编程的设置
AspectJ 意思就是Java的Aspect,Java的AOP。
写起来特别简单
首先在xml文件中配置两个对象:
user为被增强对象,myUser为增强对象
接下来设置aop代理
在execution语句中就是设置切点的位置
里面的语句格式如下:
第一个参数:*设置返回值范围的方法。
第二个参数:com表示包范围()
第三个参数:User表示类文件范围(第一个参数有空格)
第四个参数:add表示方法范围
第五个参数:(..)
匹配了一个接受任意数量参数的方法(零或者更多)。
()
匹配了一个不接受任何参数的方法,
(..)
匹配了一个接受任意数量参数的方法(零或者更多)。
(*)
匹配了一个接受一个任何类型的参数的方法。
(*,String)
匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。
-
任意公共方法的执行:
execution(public * *(..))
-
任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
-
AccountService
接口定义的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
-
在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
-
在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
-
在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)
-
在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)
-
实现了
AccountService
接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):this(com.xyz.service.AccountService)
接下来看下注解方式实现aop编程,操作更为简单、
第一步相同,先配置bean文件
第二步:
第三部:
在被增强的类上设置@Aspect
被织入的方法上设置
@Before(value ="execution(* anno.User.*(..))")