编写一个简单的需求:要求在程序执行期间追踪正在发生的活动
代码语言:javascript复制// 接口
public interface AtithmeticCalculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
代码语言:javascript复制// 实现
public class AtithmeticCalculatorImpl implements AtithmeticCalculator {
@Override
public int add(int i, int j) {
System.out.println("The method add begins with [" i "," j "]");
int result = i j;
System.out.println("The method add ends with " result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("The method add begins with [" i "," j "]");
int result = i - j;
System.out.println("The method add ends with " result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("The method add begins with [" i "," j "]");
int result = i * j;
System.out.println("The method add ends with " result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("The method add begins with [" i "," j "]");
int result = i / j;
System.out.println("The method add ends with " result);
return result;
}
}
代码语言:javascript复制// Main
public class Main {
public static void main(String[] args) {
AtithmeticCalculator atithmeticCalculator = new AtithmeticCalculatorImpl();
int result = atithmeticCalculator.add(1,2);
System.out.println(result);
result = atithmeticCalculator.div(4,2);
System.out.println(result);
}
}
// Output
The method add begins with [1,2]
The method add ends with 3
3
The method add begins with [4,2]
The method add ends with 2
2
上面例子中的实现代码存在的问题:
- 代码混乱:非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
- 代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块(方法)里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有的模块
使用动态代理解决上述问题 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及合适将方法调用转到原始对象上
代码语言:javascript复制// 接口
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
代码语言:javascript复制// 实现类
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@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;
}
}
代码语言:javascript复制// 代理
public class ArithmeticCalculatorLoggingProxy {
// 要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
this.target = target;
}
public ArithmeticCalculator getLoggingProxy(){
ArithmeticCalculator proxy = null;
// 代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
// 代理对象的类型,即其中有哪些方法
Class [] interfaces = new Class[]{ArithmeticCalculator.class};
// 当调用代理对象其中的方法时,该执行的代码
InvocationHandler h = new InvocationHandler() {
/**
*
* @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
* @param method 正在被调用的方法
* @param args 调用方法时,传入的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 日志
System.out.println("The method" methodName "begins with " Arrays.asList(args));
// 执行方法
Object result = method.invoke(target,args);
// 日志
System.out.println("The method" methodName "ends with " result);
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
return proxy;
}
}
代码语言:javascript复制// Main
public class Main {
public static void main(String[] args) {
ArithmeticCalculator target = new ArithmeticCalculatorImpl();
ArithmeticCalculator proty = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
int result = proty.add(1,2);
System.out.println(result);
result = proty.div(8,2);
System.out.println(result);
}
}
使用AOP
AOP面向切面编程,是一种新的方法论,并对传统OOP(面向对象编程)的补充
在应用AOP编程时,仍然需求定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样依赖横切关注点就被模块化到特殊的对象(切面)里
AOP术语
切面(Aspect):横切关注点 (跨越应用程序多个模块的功能)被模块化的特殊对象 通知(Advice):切面必须要完成的工作 目标(Target):被通知的对象 代理(Proxy):向目标对象应用通知之后创建的对象 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前,调用后,方法抛出异常后等。 切点(pointcut):每个类都拥有多个连接点。即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点
Spring中启用AspectJ注解支持
AspectJ:Java社区里最完整最流行的AOP框架
通知是标注有注解的简单的Java方法:
@Before
前置通知,在方法执行之前执行
@After
后置通知,在方法执行之后执行
@AfterRunning
返回通知,在方法返回结果之后执行
@AfterThrowing
异常通知,在方法抛出异常之后
@Around
环绕通知,环绕着方法执行
// maven注入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
代码语言:javascript复制// ArithmeticCalculator
public interface ArithmeticCalculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
代码语言:javascript复制// ArithmeticCalculatorImpl
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@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;
}
}
代码语言:javascript复制// ArithmeticCalculatorLoggingProxy
public class ArithmeticCalculatorLoggingProxy {
// 要代理的对象
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
this.target = target;
}
public ArithmeticCalculator getLoggingProxy(){
ArithmeticCalculator proxy = null;
// 代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
// 代理对象的类型,即其中有哪些方法
Class [] interfaces = new Class[]{ArithmeticCalculator.class};
// 当调用代理对象其中的方法时,该执行的代码
InvocationHandler h = new InvocationHandler() {
/**
* @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
* @param method 正在被调用的方法
* @param args 调用方法时,传入的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 日志
System.out.println("The method" methodName "begins with " Arrays.asList(args));
// 执行方法
Object result = method.invoke(target,args);
// 日志
System.out.println("The method" methodName "ends with " result);
return result;
}
};
proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
return proxy;
}
}
代码语言:javascript复制// LoggingAspect
@Component
@Aspect
public class LoggingAspect {
// 声明该方法是一个前置通知:在目标方法开始之前执行
// @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法
@Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub
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);
}
}
代码语言:javascript复制// Main
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
int result = arithmeticCalculator.add(3,6);
System.out.println("result: " result);
result = arithmeticCalculator.sub(2,1);
System.out.println("result: " result);
result = arithmeticCalculator.mul(2,1);
System.out.println("result: " result);
result = arithmeticCalculator.div(6,3);
System.out.println("result: " result);
}
}
前置通知
在方法执行之前执行的通知,使用@Before注解,并将切入点表达式的值作为注解值
编写AspectJ切入点表达式
通过方法的签名来匹配各种方法:
execution * com.sangyu.test10.ArithmeticCalculator.*(...)
匹配ArithmeticCalculator中声明的所有方法,第一个代表任意修饰符及任意返回值,第二个 代表任意方法,...表示匹配任意数量的参数(若目标类于接口与该切面在同一个包中,可以省略包名)
execution public * com.sangyu.test10.ArithmeticCalculator.*(...)
匹配ArithmeticCalculator接口的所有共有方法
execution public double com.sangyu.test10.ArithmeticCalculator.*(...)
匹配ArithmeticCalculator中返回double类型数值的方法
execution public double com.sangyu.test10.ArithmeticCalculator.*(double,...)
匹配第一个参数为double类型的方法 ...匹配任意数量任意类型的参数
execution public double com.sangyu.test10.ArithmeticCalculator.*(double,double)
匹配参数类型为double,double类型的方法
execution * *,*(...)
执行任意类的任意方法
JoinPoint
通知方法中beforeMethod()中声明一个类型为JoinPoint的参数,可以访问链接细节,如方法名称和参数值
后置通知
在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止,一个切面可以包括一个或者多个通知
代码语言:javascript复制@Component
@Aspect
public class LoggingAspect {
// 声明该方法是一个前置通知:在目标方法开始之前执行
@Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法
// @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub
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);
}
// 后置通知:在目标方法执行后(无论是否发生异常),执行的通知
@Before("execution(public int com.sangyu.test10.ArithmeticCalculatorImpl.*(int, int))") // 所有方法:add、mul、div、sub
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " methodName " end");
}
}