Spring(四)AOP
当业务不断发展的时候,原来的代码可能不能满足现在的需求了,可能就需要为现在的代码再增添一些功能。还有有时候代码可能需要一些执行情况的统计,比如说,老板说,你给每个方法的执行都加上日志输出吧,或者是,你给每个方法都加上运行时间统计吧。
这本身是很简单的需求,用代码实现对你来说也是小easy,但是那么多的类都需要一个个的去修改的话,咱违反了OCP原则(对扩展开发,对修改源代码封闭)不说,改也会改到哭的吧。。。
Spring又帮你想到了这个问题,Spring中的两大核心之一:AOP,Aspect Oriented Programming的缩写,意为面向切面编程。与我们的OOP很像有木有,AOP也是一种编程的思想,AOP是通过预编译方式和运行期间动态代理实现程序功能维护的一种技术。
即当我们需要对一些原来的程序增加一些功能,但是又不能违反OCP原则的时候,就可以使用AOP。
我们可以将日志统计、权限控制、安全控制、事务处理、异常处理等重复性的代码从业务逻辑代码中脱离出来,一脱离出来不就可以降低代码之间的耦合度了吗。
1.AOP相关术语
以下介绍十分简洁,可查看其它解释。
-
连接点:可被扩展的地方;
-
切点:被扩展的地方;
-
通知:为可扩展的地方添加的东西;
-
目标:要被扩展的类;
-
切面:切点和通知的结合;
-
织入:将通知应用到切点的过程;
-
代理:增强后的对象。
(AOP的整个过程就是对目标将通知应用然后产生一个代理对象的过程,然后我们使用的都是这个代理对象)。
举个栗子:
有一个类A,里面有两个方法method1和method2,然后要对method1添加日志功能,那么:
A就是目标,method1和method2就是连接点,method1就是切点,日志相关代码就是通知,method1+日志代码就是切面,整个过程为织入,使用的思想就是代理。
2.AOP的传统实现
(1)官方动态代理
要求目标实现接口,并且只能动态代理接口中的方法。
public interface UserDao {
public void save();
public void delete();
}
public class UserDaoImpl implements UserDao {
public void save(){
System.out.println("save run");
}
public void delete(){
System.out.println("delete run");
}
}
public class MyProxy implements InvocationHandler {
private Object target;
public MyProxy(Object target) {
this.target = target;
}
//创建代理对象 本质是动态的产⽣⼀个target对象的接⼝实现类
public Object createProxy(){
Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
return o;
}
//⽅法处理 动态代理核⼼⽅法,在调⽤代理对象⽅法时都会⾃动调⽤该⽅法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
//获取类信息和⽅法名当我们要对某些⽅法进⾏权限控制时也⾮常简单,只需要判断⽅法名称,然后增加权限控制逻辑即可;
String className = target.getClass().getName();
String methodName = method.getName();
//记录开始时间
long startTime = new Date().getTime();
//调⽤原始⽅法
Object result = method.invoke(target,args);
//计算耗时
long runtime = new Date().getTime() - startTime;
System.out.printf("info [class:%s method:%s
runtime:%s]\n",className,methodName,runtime);
//返回原始⽅法执⾏结果
return result;
}
public static void main(String[] args) {
//⽬标对象
UserDao userDao = new UserDaoImpl();
//代理对象
UserDao proxyDao = (UserDao) new MyProxy(userDao).createProxy();
proxyDao.save();
proxyDao.delete();
}
}
(2)民间CGLib
解决了官方动态代理的问题,可以不需要实现接口。
需要先导入jar包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
public class UserDaoImpl implements UserDao {
public void save(){
System.out.println("save run");
}
public void delete(){
System.out.println("delete run");
}
}
public class MyProxy implements MethodInterceptor {
private Object target;
public MyProxy(Object target) {
this.target = target;
}
//创建代理对象 本质是动态的产⽣⼀个target对象的接⼝实现类
public Object createProxy(){
//CGLib核⼼类
Enhancer enhancer = new Enhancer();
//指定要代理的对象类型
enhancer.setSuperclass(target.getClass());
//设置⽅法回调 即代理调⽤代理对象的⽅法时会执⾏的⽅法
enhancer.setCallback(this);
//创建代理对象
Object o = enhancer.create();
return o;
}
/***
* @param o 代理对象
* @param method 客户要执⾏的⽅法
* @param objects ⽅法参数
* @param methodProxy ⽅法代理对象 ⽤于执⾏⽗类(⽬标)⽅法
* @return 原始⽅法的返回值
* @throws Throwable*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy
methodProxy) throws Throwable {
//注意不要对第⼀个参数(代理对象) 执⾏任何⽅法,会造成死循环
//获取类信息和⽅法名
String className = target.getClass().getName();
String methodName = method.getName();
//记录开始时间
long startTime = new Date().getTime();
Object result = method.invoke(target,objects);
//调⽤原始⽅法2 传⼊的是⽅法代理对象
Object result = methodProxy.invokeSuper(o,objects);
//计算耗时
long runtime = new Date().getTime() - startTime;
System.out.printf("info [class:%s method:%s
runtime:%s]\n",className,methodName,runtime);
//返回原始⽅法执⾏结果
return result;
}
public static void main(String[] args) {
//⽬标对象
UserDaoImpl userDao = new UserDaoImpl();
//代理对象
UserDaoImpl proxyDao = (UserDaoImpl) new MyProxy(userDao).createProxy();
proxyDao.save();
proxyDao.delete();
}
}
CGLib采用的是产生⼀个继承目标类的代理类方式产生代理对象,所以如果类被final修饰将无法使用 CGLib。
所以官方的动态代理是兄弟关系,而民间的CGLib是父子关系。