1.AOP怎么样理解?
AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论、是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充,底层是用动态代理实现。怎么理解面向切面变成呢?首先要明确 "切面" 的概念。切面其实就是一个类,这个类中定义着横切关注点的信息,而横切关注点就是从每个方法中抽取出来的同一类非核心业务的代码。我们合理运用AOP思想编程,可以使我们的业务模块更加简洁,只包含核心业务代码,而且这样做每个事物逻辑位于一个位置,代码不分散便于维护。
2.AOP术语
下面介绍一些aop的常用术语:
1.横切关注点:从每个方法中抽取出来的同一类非核心业务代码。
2.切面:封装横切信息点的类,每个关注点体现为一个通知方法。
3.通知:切面必须要完成的各个具体工作,也就是切面里的一个个方法。
4.目标:被通知的对象,也就是被通知方法所作用的对象。
5.代理:像目标对象应用通知之后所创建的代理对象。
6.连接点:横切关注点在程序代码中的具体体现,对应用程序执行的某个特定位置。(通俗来讲就是一个个的方法)
7.切入点:切入点就是定位连接点的方式。每个通知上的切入点表达式找到对应的连接点,执行通知之后连接点也就变成了切入点。
为了更直观理解上述术语的关系,附上一张关系图解图方便大家理解:
3.AOP的一个实例
我们直接利用aop的思想来做一个算术计算器的例子来帮助大家理解aop的思想。
3.1 在编程中我们一般不用Spring自带的aop框架,Java社区里最完整最流行的AOP框架是AspectJ,在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。这个实例总体来说就是在每一个计算器的方法前后我们要加上日志管理功能,也就说在程序帮我们计算出结果的同时我们也要看到程序执行了那个方法、计算的参数是什么、以及结果或者异常信息等等。这些功能如果写在一起就会加重我么核心代码的负担,使得我们的代码很繁琐而且利用率不高。所以我们使用aop切面来开发,将一些日志请求的一些功能利用动态代理写到一个代理类中,无论计算器的那个方法被调用,我们的切面类都会根据我们的要求被调用,从而实现我么想要的过程和结果,这样做代码会简单很多,而且利用率会很高。
3.2搭建开发环境:首先我们新建maven工程,然后在pom.xml文件中导入我们所需要的依赖。
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
3.3 编写计算方法接口:里面我们写加减乘除的四个方法用来计算
public interface ArithmeticCalculator {
public int add(int i, int j);
public int sub(int i, int j);
public int mul(int i, int j);
public int div(int i, int j);
}
3.4 编写接口的实现类:实现我们的四个方法。
需要注意的是:我们实现类需要加入注解@Component,既然我们使用Spring AOP开发,我们就得将这个类加入我们的Sring容器中,这个注解会帮助我们将这个类管理到Spring的IOC容器中,在我们使用的时候可以方便调用。
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
public int add(int i, int j) {
int result = i+j;
return result;
}
public int sub(int i, int j) {
int result = i-j;
return result;
}
public int mul(int i, int j) {
int result = i*j;
return result;
}
public int div(int i, int j) {
int result = i/j;
return result;
}
}
3.5 配置我们的xml配置文件:我么基于注解开发和使用AspectJ来开发,就需要在xml文件中配置俩个配置。
组件扫描是将我们写的类都加入Spring的容器中,让Spring找到对应的注解进行工作。
<?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"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 基于注解开发,必须首先组件扫描-->
<context:component-scan base-package="com.wei.spring.aspectJ.annotation"></context:component-scan>
<!-- 基于注解使用AspectJ 主要的作用是为切面中通知能作用到的目标生成代理-->
<aop:aspectj-autoproxy/>
</beans>
3.6 编写我们的AOP切面:定义好了我们的四个计算方法,我们需要根据我们要实现的需求(在计算方法执行前后都要输出我们想要的日志信息)来进行切面类的编写。
AOP切面类的一些细节:
3.6.1 切面类首先要在类前面声明注解@Aspect,将其标注为切面类。因为使用Spring,还需要加@Component注解将其交给Spring容器管理。
3.6.2 通知:前面也解释了,在切面中就有通知。通知是在具体的连接点上要执行的操作,一个切面可以包括一个或者多个通知,通知所使用的注解的值往往是切入点表达式。通知可分为:前置通知,后置通知,返回通知,异常通知,环绕通知。
3.6.2.1 前置通知:在方法执行之前执行的通知,使用@Before注解。
3.6.2.2 后置通知:在方法执行之后执行的通知,使用@After注解。后置通知不管目标方法有没有抛出异常,都会执行。但是不能获取方法的结果。
3.6.2.3 返回通知:在返回方法正常执行结束之后执行。可以获取到方法的返回值,使用@AfterReturning注解
3.6.2.4 异常通知:在目标方法抛出异常之后执行,使用@AfterThrowing注解。获取方法的异常:通过throwing来指定一个名字。必须要与方法的一个形参名一致。
3.6.2.5 环绕通知:环绕着目标方法执行,可以理解为是前置,后置,返回,异常通知的结合体,使用@Around注解
3.6.3 切入点表达式:在通知属性的注解中用value="execution() "来指定。语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表])),这样可以精确定位通知要作用到的方法。
例如:@Before("execution(public int com.wei.spring.aspectJ.annotation.ArithmeticCalculator.add(int,int))")
注意:这样的指定非常局限,我们有时候想定位到整个类的全部方法,或者是方法中不仅仅只有俩个参数等等,我么可以用*来简化表达式。例如:(* com.wei.spring.aspectJ.annotation.*.*(..))。
*:任意修饰符,任意返回值
*:任意类
*:任意方法
..:任意参数列表
3.6.4 当前连接点细节:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中 。
下面我们直接上代码:
@Component
@Aspect
@Order(1)
public class LoggingAspect {
//前置通知:在目标方法(连接点)执行之前执行
@Before("execution(public int com.wei.spring.aspectJ.annotation.ArithmeticCalculator.add(int,int))")
public void beforeMethod(JoinPoint joinPoint){
//获取方法的参数
Object[] args = joinPoint.getArgs();
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("前置通知的方法名:"+methodName+","+"方法参数:"+args);
}
/**
* 后置通知:在目标方法执行之后执行,不管目标方法有没有抛出异常,都会执行。但是不能获取方法的结果
@After("execution(* com.wei.spring.aspectJ.annotation.*.add(..))")
public void afterMethod(JoinPoint joinPoint){
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("后置通知的方法名:"+methodName);
}
/**
* 返回通知:再返回方法正常执行结束之后执行。可以获取到方法的返回值
*
* 获取方法的返回值:通过returning来指定一个名字,必须要与方法的一个形参一致
*/
@AfterReturning(value = "execution(* com.wei.spring.aspectJ.annotation.*.add(..))",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("afterReturning:"+result);
}
/**
* 异常通知:在目标方法抛出异常之后执行
*
* 获取方法的异常:通过throwing来指定一个名字。必须要与方法的一个形参名一致
*
* 可以通过形参中异常的类型来设置抛出指定异常才会执行异常通知
*/
@AfterThrowing(value = "execution(* com.wei.spring.aspectJ.annotation.*.sub(..))",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("异常通知方法名"+methodName+","+"异常为"+ex);
}
/**
* 环绕通知:环绕着目标方法执行,可以理解为是前置,后置,返回,异常通知的结合体
* 更像是动态代理的全程
*
*/
@Around(value = "execution(* com.wei.spring.aspectJ.annotation.*.sub(..))")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
//执行目标方法
try {
//前置通知
System.out.println("前置通知:");
Object proceed = proceedingJoinPoint.proceed();
//返回通知
System.out.println("返回通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
//后置通知:都执行
System.out.println("后置通知");
}
return null;
}
}
3.7 编写测试类进行测试
public class test {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("spring-aspectJ_annotation.xml");
ArithmeticCalculator ac =
ctx.getBean("arithmeticCalculatorImpl", ArithmeticCalculator.class);
System.out.println(ac.getClass().getName());
int add = ac.add(1, 1);
System.out.println(add);
}
}
运行即可: