先用代码讲一下什么是传统的AOP(面向切面编程)编程
需求:实现一个简单的计算器,在每一步的运算前添加日志。最传统的方式如下:
Calculator.java
package cn.limbo.spring.aop.calculator;
/**
* Created by Limbo on 16/7/14.
*/
public interface Calculator {
int add(int i , int j);
int sub(int i , int j);
int mul(int i , int j);
int div(int i , int j);
}
CalculatorImpl.java
package cn.limbo.spring.aop.calculator;
/**
* Created by Limbo on 16/7/14.
*/
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("The method add begin with [ "+ i +"," + j+" ]");
System.out.println("The method add end with [ "+ i +"," + j+"]");
return i + j;
}
@Override
public int sub(int i, int j) {
System.out.println("The method sub begin with [ "+ i +"," + j+" ]");
System.out.println("The method sub end with [ "+ i +"," + j+" ]");
return i - j;
}
@Override
public int mul(int i, int j) {
System.out.println("The method mul begin with [ "+ i +"," + j+" ]");
System.out.println("The method mul end with [ "+ i +"," + j+" ]");
return i * j;
}
@Override
public int div(int i, int j) {
System.out.println("The method div begin with [ "+ i +"," + j+" ]");
System.out.println("The method div end with [ "+ i +"," + j+" ]");
return i / j;
}
}
这样就完成了需求,但是我们发现,倘若是要修改日志的信息,那么就需要在具体方法里面改,这样做很麻烦,而且把原本清爽的方法改的十分混乱,方法应该表现的是核心功能,而不是这些无关紧要的关注点,下面用原生的java的方式实现在执行方法时,动态添加输出日志方法
CalculatorImpl.java
package cn.limbo.spring.aop.calculator;
/**
* Created by Limbo on 16/7/14.
*/
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
package cn.limbo.spring.aop.calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Objects;
/**
* Created by Limbo on 16/7/14.
*/
public class CalculatorLoggingProxy {
//要代理的对象
private Calculator target;
public CalculatorLoggingProxy(Calculator target) {
this.target = target;
}
public Calculator getLoggingProxy(){
//代理对象由哪一个类加载器负责加载
ClassLoader loader = target.getClass().getClassLoader();
//代理对象的类型,即其中有哪些方法
Class[] interfaces = new Class[]{Calculator.class};
// 当调用代理对象其中的方法时,执行改代码
InvocationHandler handler = new InvocationHandler() {
@Override
/**
* proxy:正在返回的那个对象的代理,一般情况下,在invoke方法中不使用该对象
* method:正在被调用的方法
* args:调用方法时,传入的参数
*/
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;
}
};
Calculator proxy = (Calculator) Proxy.newProxyInstance(loader,interfaces,handler);
return proxy;
}
}
package cn.limbo.spring.aop.calculator;
/**
* Created by Limbo on 16/7/14.
*/
public class Main {
public static void main(String[] args) {
Calculator calculator = new CalculatorImpl();
Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy();
int result = proxy.add(1,2);
System.out.println("--->" + result);
result = proxy.sub(1,2);
System.out.println("--->" + result);
result = proxy.mul(3,2);
System.out.println("--->" + result);
result = proxy.div(14,2);
System.out.println("--->" + result);
}
}
下面我们使用spring自带的aop包实现但是要加入
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.8.5.RELEASE.jar
这两个额外的包
下面看代码,要点全部卸载代码里面了
Calculator.java
package cn.limbo.spring.aop.impl;
/**
* Created by Limbo on 16/7/14.
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
CalculatorImpl.java
package cn.limbo.spring.aop.impl;
import org.springframework.stereotype.Component;
/**
* Created by Limbo on 16/7/14.
*/
@Component("calculatorImpl")
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
LoggingAspect.java
package cn.limbo.spring.aop.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Created by Limbo on 16/7/14.
*/
//把这个类声明为切面:需要该类放入IOC容器中,再声明为一个切面
@Order(0)//指定切面优先级,只越小优先级越高
@Aspect
@Component
public class LoggingAspect {
/**
* 定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要添加其他代码
* 主要是为了重用路径,使用@Pointcut来声明切入点表达式
* 后面的其他通知直接使用方法名来引用当前的切入点表达式
*/
@Pointcut("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
public void declareJointPointExpression()
{
}
//声明该方法是一个前置通知:在目标方法之前执行
@Before("declareJointPointExpression()")
// @Before("execution(* cn.limbo.spring.aop.impl.*.*(..))") 该包下任意返回值,任意类,任意方法,任意参数类型
public void beforeMethod(JoinPoint joinPoint)
{
String methodName = joinPoint.getSignature().getName();
List
applicationContext.xml
用xml来配置aop
application-config.xml
一共有5类的通知,其中around最为强大,但是不一定最常用,aspect的底层实现都是通过代理来实现的,只能说这个轮子造的不错
最近在实际项目中配置aop发现了几个问题,实际的项目配置的的模式是ssh即Spring4+SpringMVC+Hibernate4的模式。
基于注解的方式配置方式有些坑点:
1.发现SpringMVC中aop不起任何作用
经过排查和查找网上的资料,发现问题如下:
Spring MVC启动时的配置文件,包含组件扫描、url映射以及设置freemarker参数,让spring不扫描带有@Service注解的类。为什么要这样设置?因为springmvc.xml与applicationContext.xml不是同时加载,如果不进行这样的设置,那么,spring就会将所有带@Service注解的类都扫描到容器中,等到加载applicationContext.xml的时候,会因为容器已经存在Service类,使得cglib将不对Service进行代理,直接导致的结果就是在applicationContext 中的事务配置不起作用,发生异常时,无法对数据进行回滚。以上就是原因所在。
所以改进后的applicationContext.xml如下(只是更改自动扫描包的那个配置):
这样就可以成功解决ssh配置下不能使用aop的情况。看来扫描包的时候不能暴力直接全部扫描进来啊,真是成也扫描,败也扫描,手动哭笑不得。