spring-aop
为什么会有面向切面编程(aop)?
我们知道java是一个面向对象(oop)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为
例如日志、权限验证、事务等功能时,只能在每个对象里引用公共行为,这样做不便于维护,而且有大量重复代码,aop的出现弥补了oop的这点不足
代理模式
为其他对象提供一种代理以控制对这个对象的访问
比如a对象要做一件事,在没有代理前,自己来做,在对a代理后,由a的代理b来做
代理其实是在原实例前后加了一层处理
静态代理模式
静态代理说白了就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定
静态代理虽然保证了业务类只需要关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理
再者,如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本
那么要如何解决呢?答案是使用动态代理
实例
接口
public interface UserDao {
void save();
void fina();
}
实现
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存用户");
}
@Override
public void fina() {
System.out.println("查询用户");
}
}
代理
/**
* 静态代理,需要维护一个目标对象
* 特点:
* 1. 目标对象必须要实现接口
* 2. 代理对象,要实现与目标对象一样的接口
*/
public class UserDaoProxy implements UserDao {
//代理对象,需要维护一个目标对象
private UserDao userDao = new UserDaoImpl();
@Override
public void save() {
System.out.println("执行目标对象前");
userDao.save();
System.out.println("执行目标对象后");
}
@Override
public void fina() {
System.out.println("执行目标对象前");
userDao.fina();
System.out.println("执行目标对象后");
}
}
测试
@Test
void test1(){
//代理对象
UserDao userDao = new UserDaoProxy();
//执行代理方法
userDao.save();
}
动态代理模式
动态代理类的源码是在程序运行期间通过jvm反射等机制动态生成,代理类和委托类的关系是运行时才确定的
使用jdk生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用jdk动态代理,所以cglib代理就是解决这个问题的
cglib是以动态生成的子类继承目标的方式实现,在运行时动态的在内存中构建一个子类
cglib使用的前提是目标类不能为final修饰。因为final修饰的类不能被继承
实例
接口和实现与上面相同
代理
/**
* 动态代理
* 代理工厂,给多个目标对象生成代理对象
*/
public class ProxyFactory {
//接收一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
//返回目标对象(target)代理后的对象(proxy)
public Object getProxyInstance(){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),//目标对象使用的类加载器
target.getClass().getInterfaces(), //目标对象实现的所有接口
new InvocationHandler() { //执行代理对象方法时触发
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法返回值
Object result = null;
//执行方法
System.out.println("方法开始前");
result = method.invoke(target,args);
System.out.println("方法结束");
return result;
}
});
return proxy;
}
}
测试
@Test
void test2(){
//创建目标对象
UserDao userDao = new UserDaoImpl();
//代理对象:其实是jdk动态生成了一个类去实现接口,隐藏了这个过程
UserDao proxy = (UserDao) new ProxyFactory(userDao).getProxyInstance();
//执行方法
proxy.save();
}
aop定义
面向切面编程,核心原理是使用动态代理模式在方法执行前后或异常时兼容相关逻辑
- aop是基于动态代理模式
- aop是方法级别的
- aop可以分离业务代码和关注度代码(重复代码),在执行业务代码时,动态的注入关注点代码,切面就是关注点代码形成的类
aop原理
- 创建容器对象的时候,根据接入点表达式拦截的类,生成代理对象
- 如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用cglib代理。然后从容器获取代理后的对象,在运行期植入“切面”类的方法
通过查看spring源码,我们在 DefaultAopProxyFactory 类中,找到这样一段话
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!IN_NATIVE_IMAGE &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);//jdk
}
return new ObjenesisCglibAopProxy(config);//cglib
}
else {
return new JdkDynamicAopProxy(config);//jdk
}
}
简单的从字面意思看出,如果有接口,则使用jdk代理,反之使用cglib,这刚好印证了前文所阐述的内容。spring aop综合两种代理方式的使用前提会有如下结论:如果模板没有实现接口,且class为final修饰的,则不能进行springaop编程