越来越多的非业务需求(日志和验证等)加入后, 原有的业务方法急剧膨胀. 每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
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 添加到 <beans> 根元素中.
3)要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要在Bean 配置文件中定义一个空的 XML 元素<aop:aspectj-autoproxy>
当 Spring IOC 容器侦测到Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时, 会自动为与 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
<?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/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.spring.aop.impl">
</context:component-scan>
<!-- 使AspectJ注解起作用,自动为装配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
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在配置文件中加入如下配置:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
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<Object> 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<Object> 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 配置都必须定义在 <aop:config> 元素内部. 对于每个切面而言, 都要创建一个 <aop:aspect> 元素来为具体的切面实现引用后端 Bean 实例.
3)切面 Bean 必须有一个标示符, 供 <aop:aspect> 元素引用
2.3.2声明切入点
1)切入点使用<aop:pointcut> 元素声明
2)切入点必须定义在<aop:aspect> 元素下, 或者直接定义在<aop:config> 元素下.
定义在 <aop:aspect> 元素下: 只对当前切面有效
定义在 <aop:config> 元素下: 对所有切面都有效
2.3.3声明通知
通知元素需要使用 <pointcut-ref> 来引用切入点, 或用 <pointcut> 直接嵌入切入点表达式. 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
<?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="arithmeticCalculcatorImpl" class="com.spring.aop.xml.ArithmeticCalculcatorImpl">
</bean>
<!-- 配置切面的bean -->
<bean id="logginAspect" class="com.spring.aop.xml.LogginAspect"></bean>
<bean id="validaAspect" class="com.spring.aop.xml.ValidaAspect"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.spring.aop.xml.ArithmeticCalculcator.*(int, int))"
id="pointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="logginAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"/>
<aop:around method="aroundMethod" pointcut-ref="pointcut" />
</aop:aspect>
<aop:aspect ref="validaAspect" order="1">
<aop:before method="validationAspect" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
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<Object> 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()));
}
}