Spring——AOP
AOP:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
首先我们先创建一个简单的例子,使用多种方式实现下面功能,最后用AOP的方式再实现它:
例子:编写一个加法器,要使在调用前和调用后打印相关信息:
常规方法
CalculatorImpl:
package com.chenx.spring.beans;
public class CalculatorImpl implements Calculator{
@Override
public int add(int a, int b) {
System.out.println("begin");
int result=a+b;
System.out.println("end");
return result;
}
}
Main:
package com.chenx.spring.beans;
public class Main {
public static void main(String[] args) {
Calculator calculator=new CalculatorImpl();
calculator.add(1,2);
}
}
从以上代码可以想象,如果有加减乘除等多个方法,修改和编写的量会大大加深,因此将使用动态代理将对象包装起来,使用该代理对象取代原先的对象,从而方便。
动态代理:
创建一个代理类,传入Calculator,接着为其代理:
LoggingProxy:
package com.chenx.spring.beans;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LoggingProxy {
private Calculator target;
public LoggingProxy(Calculator target) {
this.target = target;
}
public Calculator getLoggingProxy(){
Calculator proxy=null;
//指明代理对象由哪个类加载器加载
ClassLoader loader=target.getClass().getClassLoader();
//代理对象类型
Class [] interfaces=new Class[]{Calculator.class};
//当调用代理对象其中方法时候,执行的方法,在此编写
InvocationHandler h=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:返回的对象
//method:正在调用的方法
//args:调用方法时,传入的参数
System.out.println("begin");
Object result=method.invoke(target,args);
System.out.println("end");
return result;
}
};
proxy= (Calculator) Proxy.newProxyInstance(loader,interfaces,h);
return proxy;
}
}
Main:
package com.chenx.spring.beans;
public class Main {
public static void main(String[] args) {
Calculator calculator=new CalculatorImpl();
Calculator proxy=(Calculator) new LoggingProxy(calculator).getLoggingProxy();
proxy.add(1,2);
}
}
Spring AOP
AOP是面向切面编程,OOP是面向对象编程
Aspect(切面):横切关注点被模块化的对象
Advice(通知):切面必须完成的工作(切面中的每一个方法)
Target(目标 ):被通知的对象
Proxy(代理) :向目标对象应用通知之后创建的对象
Joinpoint(连接点):程序执行的某个特殊的位置
pointcut (切点) :连接点是Calculator中的add方法,通过切点定位到特定的连接点
Advice(通知)类型:
1.@Before 前置通知:在目标方法前执行
2.@After 后置通知:在目标方法执行后执行(无论是否报错)
3.@AfterReturning 返回通知:在目标方法正常执行后执行
4.@AfterThrowing 异常通知:在目标方法执行后出现异常后执行
5.@Around 环绕通知:围绕着目标方法执行
接下来在之前的Add基础上使用AOP的方式,我们使用 aspectj ,加入相关的包:
创建日志的切面文件:
1.@Component 使其加入IOC容器
2.@Aspect 声明为一个切面
3.@Before 声明为前置通知 并在后说明在什么方法前调用
1.@Before 前置通知
package com.chenx.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.chenx.spring.aop.Calculator.add(int,int))")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("before");
}
}
还需在配置文件中配置:
<context:component-scan base-package="com.chenx.spring.aop"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
上方只是简单的输出,我们可以通过JoinPoint 做一些操作:
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
测试如下:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(public int com.chenx.spring.aop.Calculator.add(int,int))")
public void beforeMethod(JoinPoint joinPoint){
//获取目标方法名
String name=joinPoint.getSignature().getName();
System.out.println(name);
//获取目标方法传入的参数
List<Object> args= Arrays.asList(joinPoint.getArgs());
System.out.println(args);
//获取被代理的对象(类型是一个类)
System.out.println(joinPoint.getTarget().getClass());
//获取代理对象本身(类型是一个代理)
System.out.println(joinPoint.getThis().getClass());
System.out.println("before");
}
}
如果切点需要匹配多个连接点,则可以使用占位符:(这样就会匹配所有参数为二个的连接点)
@Before("execution(* com.chenx.spring.aop.Calculator.*(*,*))")
2.@After 后置通知
@After("execution(public int com.chenx.spring.aop.Calculator.add(int,int))")
public void afterMethod(){
System.out.println("end");
}
3.@AfterReturning 返回通知
@AfterReturning("execution(public int com.chenx.spring.aop.Calculator.add(int,int))")
public void afterReturningMethod(){
System.out.println("end no error");
}
4.@AfterThrowing 异常通知
可以加入throwing来访问异常,在方法中定义异常的类型,只有符合此类型的异常才可以执行
@AfterThrowing(value = "execution(public int com.chenx.spring.aop.Calculator.add(int,int))",throwing = "ex")
public void afterThrowingMethod(Exception ex){
System.out.println("end with error");
}
5.@Around 环绕通知
这是功能最强大的通知,其类似于我们之前写的Proxy动态代理,用此就可以模拟所有的通知:
@Around("execution(public int com.chenx.spring.aop.Calculator.add(int,int))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
Object result=null;
try{
System.out.println("begin");
result=proceedingJoinPoint.proceed();
System.out.println("end no error");
}catch (Throwable e){
System.out.println("end with error");
throw new RuntimeException(e);
}
System.out.println("end");
return result;
}
6.@Pointcut
从上面那么多代码可以看到,我们每次使用切点的时候都是使用的同一个,但是写了很多遍,这样导致编写代码的不便,现在我们使用重用代码:
@Pointcut("execution(public int com.chenx.spring.aop.Calculator.add(int,int))")
public void declareJoinPointExpression(){}
@After("declareJoinPointExpression()")
public void afterMethod(){
System.out.println("end");
}
在别的切面中可以这样调用:
@After("LoggingAspect.declareJoinPointExpression()")
public void afterMethod(){
System.out.println("end");
}
切面的优先级
如果有多个切面,就会有哪个切面先后的问题,可以使用order进行优先级的设定:
参数越小,优先级越高
@Order(1)
@Aspect
@Component
public class LoggingAspect {
2.使用XML配置文件的方式配置SpringAOP
<bean id="calculator" class="com.chenx.spring.aop.CalculatorImpl"></bean>
<bean id="loggingAspect" class="com.chenx.spring.aop.LoggingAspect"></bean>
<!-- 配置Aop-->
<aop:config>
<aop:pointcut id="addcut" expression="execution(public int com.chenx.spring.aop.Calculator.add(int,int ))"/>
<aop:aspect ref="loggingAspect">
<aop:before method="beforeMethod" pointcut-ref="addcut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="addcut"></aop:after>
<aop:after-returning method="afterReturningMethod" pointcut-ref="addcut"></aop:after-returning>
<aop:after-throwing throwing="ex" method="afterThrowingMethod" pointcut-ref="addcut"></aop:after-throwing>
</aop:aspect>
</aop:config>