Java是一种oop(面对对象设计)的语言,但由于在实际开发中,如果仅仅只站在“对象”的角度来做开发,容易产生耦合以及不易维护。因此在Spring框架中引入了AOP(面向切面编程)的概念及模块,使得开发者可以将软件系统解为一个个的“切面”,从aspect的角度来对软件进行设计
援引百度百科对于AOP的概述:
“在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。”
也就是说,简单的OOP,我们可以看做将对象纵向进行排列处理业务和事件。而AOP又提供了一种途径,使得我们可以从横向层面对业务进行划分。
AOP具有四大功能:
日志记录(方便查找和Debugger),性能统计(分析系统性能),安全控制,事务处理,异常处理。
通过前面提到的四大功能,aop可以将这些逻辑从代码中分离出来,独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
也就是说,以往当我们只是单纯地采用oop时,由于是对象视角,当触及到以上几方面业务时,常常需要对对象进行大批量的改动。这样既增大了业务量,也给开发带来了难度。因此我们需要对业务进行分离。
除此以外,还有引入、目标对象、代理等概念,后面会提及。
AOP的实现需要动态代理,因此先阐述这一概念。
首先介绍一下何为代理模式:
代理模式是设计模式的一种,代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
比如我们要买房,找房这一费劲的过程我们会交给房屋中介,客户只要在有合适房源后付钱即可,这就是一个简单的代理模式。
优点如下:
说起动态代理就不得不说下静态代理:静态代理虽然体现了代理模式的优点,但代理只能为一个类服务,因此如果有很多个类,就会很麻烦。
但是由于Java有反射机制,就可以实现动态代理。
在将Spring动态代理前先说一下Java中比较常见的JDK动态代理:
JDK中的动态代理的实现必须依赖于接口。因此我们先创建一个接口:
package com.dynamic;
public interface testImp {
public void modify();
}
创建一个代理类,作增强处理:
package com.dynamic;
public class testDy1 implements testImp{
@Override
public void modify() {
System.out.println("模拟");
}
}
在另一个包中创建一个切面类,可定义多个通知
package com.asp;
public class MyASp {
public void log(){
System.out.println("模拟日志处理机制");
}
}
回到动态代理,创建一个代理类:
package com.dynamic;
import com.asp.MyASp;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Proxy implements InvocationHandler {
//使用Proxy实现的动态代理
private testDy1 testDy1;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//创建切面
MyASp myASp=new MyASp();
Object obj=method.invoke(testDy1,args);
myASp.log();
return obj;
}
}
最后建立一个测试类
package com.dynamic;
public class MainTest {
public static void main(String[] args) {
//创建代理对象
Proxy proxy=new Proxy();
//创建目标对象
testDy1 testDy1=new testDy1();
testDy1 advice=testDy1.getBean("xml文件的名字");//执行方法
advice.modify();
}
}
以上便是JDK动态代理的大致框架,通过对UML图
的查看可以看出,几个类之间依赖并不强,同时动态代理的实现依赖于接口的调用。
同时,动态管理的步骤如下:
Spring中的AOP是由AOP联盟进行维护的一组接口,使得用户在依赖注入AOP模块后可以调用功能函数。
同使用其他的spring模块一样,我们首先在maven的pom.xml文件里里加入依赖项
aopalliance-1.0.jar是由AOP联盟提供的规范包
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
来看一下aoplliance的UML图
可看出,类与类、接口之间的联系进一步降低。
切面类的实现需要org.aopalliance.intercept.MethodInterceptor接口。通过对这一接口的调用可以创建切面
public class test1 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("模拟");
return null;
}
}
切面类还需要配置Bean,这样才能被识别。在同一个包下创建配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 配置Bean -->
<bean id="mainTest"
class="com.test.mainTest">
</bean>
<!-- 配置切面的Bean -->
<bean id="loggingAspect"
class="com.test.test1">
</bean>
<bean id="vlidationAspect"
class="com.test.mainTest">
</bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.hk.spring.aop.xml.ArithmeticCalculator.*(int,int))"
id="pointcut"/>
<!-- 配置切面和通知 -->
<aop:aspect ref="loggingAspect" order="2" >
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result" />
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
配置完成之后,在包里创建一个新的测试类便可运行(对于配置文件的读取需要)
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class mainTest {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("C:\\Users\\MECHREVO\\IdeaProjects\\aoptest\\src\\main\\java\\com\\test\\applicationContext.xml");
test1 t=new test1();
}
}
AspectJ是一个Java的aop框架,有两种配置方式,一是基于XML,二是基于注解。由于基于注解的开发比较便捷,在此主要讲基于注解的方法(注解本质上就是对XML文件的配置,在操作上更加简便)。
通过观察AspectJ的UNL图
以及其中最常用到的lang包
可以看出,在AOP中,进一步体现了解耦的特点,方法的实现更多的依靠对接口的调用。在建立切入点后,进一步地开展业务。
使用AspectJ模块,首先要引入aspectJ的j相关jar包:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
使用注解后的大致框架如下:
package com.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Aspect //定义一个切面类
@Component //定义配置路径
@Repository("ASP")//将类注解为目标对象
public class MyASp {
@Pointcut("***");//***是切入点的名称
@Before("");//前置通知,函数
public void before(JoinPoint joinPoint){
}
public void after(JoinPoint joinPoint){
}
@Around("");//环绕通知,和前置通知一个动作;
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//需要设立异常机制
Object obj=proceedingJoinPoint.proceed();
return obj;
}
@AfterThrowing(value = "之前的动作",throwing = "e");
public void excpect(Throwable e){
System.out.println("异常");
}
@After("");//依旧是相同的动作
public void after(){
}
}
可见,通过AspectJ以及使用注解的方式,可以大大简化工作量,同时又减少业务之间的耦合。
AOP是一种新的编程模式,其并不向oop那样深刻地体现在某一语言的设计逻辑里,但为代码、项目的构建提供了一种新的思路。
而Spring作为Java中最重要的框架之一,通过自动创建类、接口,使得AOP这一逻辑的实现变得更加简单。当然,同大多数设计模式一样,这一过程的实现最主要的还是依靠对接口的调用。