SpringAOP

越来越多的非业务需求(日志和验证等)加入后, 原有的业务方法急剧膨胀.  每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点

 

1.     使用动态代理解决

代理设计模式的原理: 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理. 代理对象决定是否以及何时将方法调用转到原始对象上

SpringAOP_第1张图片

JDK

Main

package com.spring.aop.helloworld;
 
public class Main {
 
       public static void main(String[] args) {
              ArithmeticCalculcator target = new ArithmeticCalculcatorImpl();
              ArithmeticCalculcator proxy = new ArithmeticCalculcatorLoggingProxy(target).getLoggingProxy();
              System.out.println(proxy.getClass().getName());
              System.out.println(proxy.add(1, 3));
              System.out.println(proxy.div(7, 3));
       }
 
}
/*
com.sun.proxy.$Proxy0
The method add beagins with[1, 3]
The method add ends with4
4
The method div beagins with[7, 3]
The method div ends with2
2
*/

ArithmeticCalculcator

package com.spring.aop.helloworld;
 
public interface ArithmeticCalculcator {
       int add(int i,int j);
       int sub(int i,int j);
      
       int mul(int i,int j);
       int div(int i,int j);
 
}

ArithmeticCalculcatorImpl

package com.spring.aop.helloworld;
 
public class ArithmeticCalculcatorImpl implements ArithmeticCalculcator{
 
       @Override
       public int add(int i, int j) {
              int result = i + j;
              return result;
       }
 
       @Override
       public int sub(int i, int j) {
              int result = i - j;
              return result;
       }
 
       @Override
       public int mul(int i, int j) {
              int result = i * j;
              return result;
       }
 
       @Override
       public int div(int i, int j) {
              int result = i / j;
              return result;
       }
 
}

ArithmeticCalculcatorLoggingProxy

package com.spring.aop.helloworld;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
 
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader.Array;
 
public class ArithmeticCalculcatorLoggingProxy {
       //要代理的对象
       private ArithmeticCalculcator target;
       public ArithmeticCalculcatorLoggingProxy(ArithmeticCalculcator target) {
              this.target = target;
       }
       public ArithmeticCalculcator getLoggingProxy() {
              ArithmeticCalculcator proxy = null;
             
              //代理对象由哪一个类加载器负责加载
              ClassLoader loader = target.getClass().getClassLoader();
              //代理对象的类型,即其中有哪些方法
              Class[] interfaces = new Class[] {ArithmeticCalculcator.class};
              //当调用代理对象其中的方法时,该执行的代码
              InvocationHandler h = new InvocationHandler() {
                     /**
                      * proxy:正在返回的那个代理对象,一般情况下在invoke方法中都不使用该对象。
                      * method:正在被调用的方法
                      * args:调用方法时传入的参数
                      */
                     @Override
                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            String methodName = method.getName();
                            System.out.println("The method "+methodName+" beagins with"+Arrays.asList(args));
                            //执行方法
                            Object result = null;
                            try {
                                   //前置通知
                                   result = method.invoke(target, args);
                                   //返回通知,可以访问到方法返回值
                            } catch (Exception e) {
                                   // 异常通知,
                            }
                            //后置通知,发生异常时访问不到方法的返回值
                            System.out.println("The method "+methodName+" ends with"+result);
                            return result;
                     }
              };
              proxy = (ArithmeticCalculcator)Proxy.newProxyInstance(loader, interfaces, h);
              return proxy;
       }
 
}

2.     Spring AOP

AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 是对传统OOP(Object-Oriented Programming, 面向对象编程) 的补充。

 

2.1AOP术语

切面(Aspect): 横切关注点

通知(Advice): 切面必须要完成的工作

目标(Target): 被通知的对象

代理(Proxy): 向目标对象应用通知之后创建的对象

连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。

切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

 

2.2AspectJ

Java 社区里最完整最流行的 AOP框架.

在 Spring 中启用AspectJ 注解支持

1)要在 Spring 应用中使用 AspectJ 注解, 必须在classpath 下包含 AspectJ 类库:aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar

2)将 aop Schema 添加到 根元素中.

3)要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在Bean 配置文件中定义一个空的 XML 元素

当 Spring IOC 容器侦测到Bean 配置文件中的 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.

 

用 AspectJ 注解声明切面

切面只是一个带有 @Aspect 注解的 Java 类.

AspectJ 支持 5 种类型的通知注解:

@Before: 前置通知, 在方法执行之前执行

@After: 后置通知, 在方法执行之后执行

@AfterRunning: 返回通知,在方法返回结果之后执行

@AfterThrowing: 异常通知, 在方法抛出异常之后

@Around: 环绕通知, 围绕着方法执行

 

可以在通知方法中声明一个类型为 JoinPoint 的参数. 然后就能访问链接细节. 如方法名称和参数值.

对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.

 

切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.

在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的

Main

package com.spring.aop.impl;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class Main {
       public static void main(String[] args) {
              //1.创建spring IOC 容器
              ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
              //2.从IOC容器中获取bean的实例
              ArithmeticCalculcator a = (ArithmeticCalculcator)ac.getBean("arithmeticCalculcatorImpl");
              //3.使用bean
              int result = a.add(3, 1);
              System.out.println(result);
              System.out.println(a.div(9, 3));
       }
}
/*
validate:[3, 1]
 around:The method add begins with [3, 1]
before:The method add begins with[3, 1]
 around:The method add ends with4
 around: The method add ends
after:The method add ends
afterReturn:The method add ends with4
4
validate:[9, 3]
 around:The method div begins with [9, 3]
before:The method div begins with[9, 3]
 around:The method div ends with3
 around: The method div ends
after:The method div ends
afterReturn:The method div ends with3
3
*/

applicationContext.xml



           
       
       
      
       
       
 

ArithmeticCalculcator

package com.spring.aop.impl;
 
public interface ArithmeticCalculcator {
       int add(int i,int j);
       int sub(int i,int j);
      
       int mul(int i,int j);
       int div(int i,int j);
 
}

ArithmeticCalculcatorImpl

package com.spring.aop.impl;
 
import org.springframework.stereotype.Component;
 
@Component
public class ArithmeticCalculcatorImpl implements ArithmeticCalculcator{
 
       @Override
       public int add(int i, int j) {
              int result = i + j;
              return result;
       }
 
       @Override
       public int sub(int i, int j) {
              int result = i - j;
              return result;
       }
 
       @Override
       public int mul(int i, int j) {
              int result = i * j;
              return result;
       }
 
       @Override
       public int div(int i, int j) {
              int result = i / j;
              return result;
       }
 
}

LogginAspect

package com.spring.aop.impl;
 
import java.util.Arrays;
import java.util.List;
 
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
2.1加入jar包
aopalliance-alpha1.jar
aspectjweaver.jar
spring-aop-4.3.2.RELEASE.jar
spring-aspects-4.3.2.RELEASE.jar
 
commons-logging-1.2.jar
spring-beans-4.3.2.RELEASE.jar
spring-context-4.3.2.RELEASE.jar
spring-core-4.3.2.RELEASE.jar
spring-expression-4.3.2.RELEASE.jar
 
2.2配置文件中加入aop命名空间
 
2.3基于注解的方式
 
2.3.1在配置文件中加入如下配置:

 
2.3.2把横切关注点的代码抽象到切面的类中
i.切面首先是一个IOC中的bean,即加入@Component注解
ii.切面还需加入@Aspect注解
 
2.3.3在类中声明各种通知
i.声明一个方法
ii.在方法前加入@Before注解
 
2.3.4可以在通知方法中声明一个类型为JoinPoint的参数,然后就能访问链接细节,如方法名称和参数值
@Aspect
@Component
public class LogginAspect {
      
       //声明目标方法是一个前置通知:在目标方法开始之前通知
       @Before("execution(public int com.spring.aop.impl.ArithmeticCalculcator.*(int, int ))")
       public void beforeMethod(JoinPoint joinPoint) {
              String methodName = joinPoint.getSignature().getName();
              List args = Arrays.asList(joinPoint.getArgs());
              System.out.println("The method "+methodName+" begins with"+args);
       }
}
 
 *
 */
 
/**
 * 把这个类声明为一个切面
 * 1.把该类放在IOC容器中
 * 2.声明为一个切面
 *
 */
@Order(2)
@Aspect
@Component
public class LogginAspect {
      
       //声明目标方法是一个前置通知:在目标方法开始之前通知
       @Before("ValidaAspect.declareJoinPointExpression()")
       public void beforeMethod(JoinPoint joinPoint) {
              String methodName = joinPoint.getSignature().getName();
              List args = Arrays.asList(joinPoint.getArgs());
              System.out.println("before:The method "+methodName+" begins with"+args);
       }
       //后置通知:目标方法执行后执行的通知,无论方法是否出现异常
       //还不能对目标方法的返回值进行访问
       @After(value="ValidaAspect.declareJoinPointExpression()")
       public void afterMethod(JoinPoint joinPoint) {
              String methodName = joinPoint.getSignature().getName();
              System.out.println("after:The method "+methodName+" ends");
       }
      
       //返回通知:目标方法正常返回后执行的通知
       //可以访问目标方法的返回值
       @AfterReturning(value="ValidaAspect.declareJoinPointExpression()",
                     returning="result")
       public void afterReturningMethod(JoinPoint joinPoint,Object result) {
              String methodName = joinPoint.getSignature().getName();
              System.out.println("afterReturn:The method "+methodName+" ends with"+result);
       }
       //异常通知:目标方法出现异常时执行的通知
       //可以访问到异常对象,可以指定出现特定异常时在执行代码
       @AfterThrowing(value="ValidaAspect.declareJoinPointExpression()",
                     throwing="ex")
       public void afterThrowingMethod(JoinPoint joinPoint,Exception ex) {
              String methodName = joinPoint.getSignature().getName();
              System.out.println("afterThrow:The method "+methodName+" occurs "+ex);
       }
       //环绕通知需要携带ProceedingJoinPoint类型的参数
       //环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
       //环绕通知必须有返回值,返回值是目标方法的返回值
       @Around(value="ValidaAspect.declareJoinPointExpression()")
       public Object aroundMethod(ProceedingJoinPoint pjp) {
             
              Object result = null;
              String methodName = pjp.getSignature().getName();
              //执行目标方法
              try {
                     //前置通知
                     System.out.println(" around:The method "+methodName+" begins with "+Arrays.asList(pjp.getArgs()));
                     result = pjp.proceed();
                     //返回通知
                     System.out.println(" around:The method "+methodName+" ends with"+result);
              } catch (Throwable e) {
                     //异常通知
                     System.out.println(" around: The method "+methodName+" occurs "+e);
              }
              //后置通知
              System.out.println(" around: The method "+methodName+" ends");
              return result;
       }
      
} 
  

ValidaAspect

package com.spring.aop.impl;
 
import java.util.Arrays;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 可以使用@Order注解指定切面的优先级,值越小优先级越高
 */
@Order(1)
@Aspect
@Component
public class ValidaAspect {
       /**
        * 定义一个方法,用于声明切入点表达式。一般的,该方法中不需要添入其他的代码。
        * 使用@Pointcut来声明切入点表达式
        * 后面的其他通知直接使用方法名来引用当前的切入点表达式
        */
       @Pointcut("execution(public int com.spring.aop.impl.ArithmeticCalculcator.*(..))")
       public void declareJoinPointExpression() {}
      
       @Before("declareJoinPointExpression()")
       public void validationAspect(JoinPoint joinPoint) {
              System.out.println("validate:"+Arrays.asList(joinPoint.getArgs()));
       }
 
}

 正常情况下, 基于注解的声明要优先于基于 XML 的声明.
2.3基于 XML 的声明切面

2.3.1声明切面

1)导入 aop Schema

2)在 Bean 配置文件中, 所有的 Spring AOP 配置都必须定义在 元素内部. 对于每个切面而言, 都要创建一个 元素来为具体的切面实现引用后端 Bean 实例.

3)切面 Bean 必须有一个标示符, 供 元素引用

 

2.3.2声明切入点

1)切入点使用 元素声明

2)切入点必须定义在 元素下, 或者直接定义在 元素下.

定义在 元素下: 只对当前切面有效

定义在 元素下: 对所有切面都有效

 

2.3.3声明通知

通知元素需要使用 来引用切入点, 或用 直接嵌入切入点表达式.  method 属性指定切面类中通知方法的名称

Main

package com.spring.aop.xml;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class Main {
       public static void main(String[] args) {
              //1.创建spring IOC 容器
              ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-xml.xml");
              //2.从IOC容器中获取bean的实例
              ArithmeticCalculcator a = (ArithmeticCalculcator)ac.getBean("arithmeticCalculcatorImpl");
              //3.使用bean
              int result = a.add(3, 1);
              System.out.println(result);
              System.out.println(a.div(9, 3));
       }
}
/*
validate:[3, 1]
 around:The method add begins with [3, 1]
before:The method add begins with[3, 1]
 around:The method add ends with4
 around: The method add ends
after:The method add ends
afterReturn:The method add ends with4
4
validate:[9, 3]
 around:The method div begins with [9, 3]
before:The method div begins with[9, 3]
 around:The method div ends with3
 around: The method div ends
after:The method div ends
afterReturn:The method div ends with3
3
*/

applicationContext-xml.xml



      
       
       
       
      
       
       
       
      
       
       
              
              
              
              
                     
                     
                     
                     
                     
              
              
                     
              
       
      

ArithmeticCalculcator

package com.spring.aop.xml;
 
public interface ArithmeticCalculcator {
       int add(int i,int j);
       int sub(int i,int j);
      
       int mul(int i,int j);
       int div(int i,int j);
 
}

ArithmeticCalculcatorImpl

package com.spring.aop.xml;
 
import org.springframework.stereotype.Component;
 
 
public class ArithmeticCalculcatorImpl implements ArithmeticCalculcator{
 
       @Override
       public int add(int i, int j) {
              int result = i + j;
              return result;
       }
 
       @Override
       public int sub(int i, int j) {
              int result = i - j;
              return result;
       }
 
       @Override
       public int mul(int i, int j) {
              int result = i * j;
              return result;
       }
 
       @Override
       public int div(int i, int j) {
              int result = i / j;
              return result;
       }
 
}

LogginAspect

package com.spring.aop.xml;
 
import java.util.Arrays;
import java.util.List;
 
import org.aopalliance.intercept.Joinpoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
public class LogginAspect {
      
       //声明目标方法是一个前置通知:在目标方法开始之前通知
       public void beforeMethod(JoinPoint joinPoint) {
              String methodName = joinPoint.getSignature().getName();
              List args = Arrays.asList(joinPoint.getArgs());
              System.out.println("before:The method "+methodName+" begins with"+args);
       }
       //后置通知:目标方法执行后执行的通知,无论方法是否出现异常
       //还不能对目标方法的返回值进行访问
       public void afterMethod(JoinPoint joinPoint) {
              String methodName = joinPoint.getSignature().getName();
              System.out.println("after:The method "+methodName+" ends");
       }
      
       //返回通知:目标方法正常返回后执行的通知
       //可以访问目标方法的返回值
       public void afterReturningMethod(JoinPoint joinPoint,Object result) {
              String methodName = joinPoint.getSignature().getName();
              System.out.println("afterReturn:The method "+methodName+" ends with"+result);
       }
       //异常通知:目标方法出现异常时执行的通知
       //可以访问到异常对象,可以指定出现特定异常时在执行代码
       public void afterThrowingMethod(JoinPoint joinPoint,Exception ex) {
              String methodName = joinPoint.getSignature().getName();
              System.out.println("afterThrow:The method "+methodName+" occurs "+ex);
       }
       //环绕通知需要携带ProceedingJoinPoint类型的参数
       //环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
       //环绕通知必须有返回值,返回值是目标方法的返回值
       public Object aroundMethod(ProceedingJoinPoint pjp) {
             
              Object result = null;
              String methodName = pjp.getSignature().getName();
              //执行目标方法
              try {
                     //前置通知
                     System.out.println(" around:The method "+methodName+" begins with "+Arrays.asList(pjp.getArgs()));
                     result = pjp.proceed();
                     //返回通知
                     System.out.println(" around:The method "+methodName+" ends with"+result);
              } catch (Throwable e) {
                     //异常通知
                     System.out.println(" around: The method "+methodName+" occurs "+e);
              }
              //后置通知
              System.out.println(" around: The method "+methodName+" ends");
              return result;
       }
      
} 
  

ValidaAspect

package com.spring.aop.xml;
 
import java.util.Arrays;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * 可以使用@Order注解指定切面的优先级,值越小优先级越高
 */
 
public class ValidaAspect {
      
       public void validationAspect(JoinPoint joinPoint) {
              System.out.println("validate:"+Arrays.asList(joinPoint.getArgs()));
       }
 
}

 

 

你可能感兴趣的:(Spring)