本文主要讲解内容如下:
- Spring的核心之一 - AOP思想
(1) 代理模式- 动态代理
① JDK的动态代理 (Java官方)
② CGLIB 第三方代理
AOP概述
什么是AOP(面向切面编程)
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行时动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,进而提高开发效率。
传统Web开发模型: 纵向的编程
面向切面编程: 纵横配合的编程.
AOP的作用及优势
作用:在程序运行期间,不修改任何相关源码对已有方法进行增强。
优势:减少重复代码、提高开发效率、方便后期维护。
AOP的实现方式
使用动态代理模式来实现
可能通过上面的介绍,我们还是没有对AOP有一个清晰的认识。没关系,我们看看下面的具体应用。
案例中问题
通过下面的代码,我们能看出什么问题吗?
模拟事务管理器
public class TransactionMangerHandler {
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
public void closeSession() {
System.out.println("关闭session");
}
}
Service层代码
public class UserServiceImpl implements UserService {
// 引入事务管理器
private TransactionManagerHandler txManager = new TransactionManagerHandler();
@Override
public void insert() {
try {
txManager.begin();
System.out.println("调用dao层insert方法");
txManager.commit();
} catch (Exception e) {
txManager.rollback();
} finally {
txManager.close();
}
}
@Override
public void update() {
try {
txManager.begin();
System.out.println("调用dao层update方法");
txManager.commit();
} catch (Exception e) {
txManager.rollback();
} finally {
txManager.close();
}
}
}
存在的问题
上面代码的问题就是:我们的事务控制的代码是重复性的。这还只是一个业务类,如果有多个业务类,每个业务类中都会有这些重复性的代码。是不是重复代码太多了?
通过传统方式来控制业务层的事务,那么会出现大量的冗余代码,例如开启事务、提交事务、事务回滚、关闭资源。这些冗余的代码,降低了开发效率,并且不利于代码后期的维护。我们可以借助AOP思想来集中管理冗余的代码。AOP可以理解为在业务方法的执行前后植入一些增强(事务开始、事务提交、事务回滚、资源关闭),到这里,AOP总的来说就是解决方法内部代码冗余的问题,并且在一定程度上使得方法内部的非业务逻辑代码和业务逻辑代码解耦,提高开发效率、质量的同时方便代码的后期维护!
解决上述问题的方案
- JDK的动态代理 - Java官方。
- CGLIB动态代理 -第三方组织开源。
- Spring的AOP技术(底层就是JDK动态代理和CGLIB代理技术)。
什么是动态代理技术?
Java中的动态代理,就是使用者使用的不是真实的对象,而是使用一个代理对象,而这个代理对象中包含的是真实的对象,代理对象的作用就是不改变原有对象的功能方法的基础之上封装新的功能。可以认为是真实对象的方法在代理对象的某一个制定好规则的方法中执行。如事务管理。
动态代理技术,可以使得业务逻辑代码和非业务逻辑代码相分离,提高了开发效率,控制事务的代码都已经在代理对象的方法中搭建好了,当需要调用真实对象的方法时,那么就会调用代理对象中已经搭建好框架的方法,只需将真实对象的方法植入到框架的指定位置,即可完成真实对象方法的访问。
JDK动态代理
JDK动态代理是Java官方的动态代理技术。
使用JDK官方的Proxy类创建代理对象:
- 需要通过Proxy类创建代理对象。
- 创建代理对象必须要有一个代理处理类(实现接口InvocationHandler的类)。
JDK动态代理API分析
1、java.lang.reflect.Proxy
类:
Java动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
主要方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler hanlder)
方法职责:为指定类加载器、一组接口及调用处理器生成动态代理类实例
参数:
- loader: 类加载器
- interfaces : 模拟的接口
- hanlder: 代理执行处理器
返回:动态生成的代理对象
2、java.lang.reflect.InvocationHandler
接口:
public Object invoke(Object proxy, Method method, Object[] args)
方法职责:负责集中处理动态代理类上的所有方法调用
参数:
- proxy:生成的代理对象
- method:当前调用的真实方法对象
- args:当前调用方法的实参
返回: 真实方法的返回结果
3、JDK动态代理操作步骤:
① 实现InvocationHandler接口,创建增强代码处理器。
② 给Proxy类提供ClassLoader对象和代理接口类型数组,创建动态代理对象。
③ 在处理器中实现增强操作。
案例代码
在我们的UserServiceImpl实现类中,使用JDK动态代理,进行方法的增强,增强事物的功能。
—UserService&UserServiceImpl
public interface UserService {
void insert();
void update();
}
public class UserServiceImpl implements UserService {
@Override
public void insert() {
System.out.println("执行了service层的insert方法");
}
@Override
public void update() {
System.out.println("执行了service层的update方法");
}
}
—JDK动态代理工具类
/**
* 获取代理对象的工具类
* 增强功能:事务管理
*/
@Component
public class DynamicProxyUtil {
// 模拟的事务管理器
@Autowired
private TransactionMangerHandler tx;
/**
* 根据类的字节码获取代理对象
* @param cls 被代理类的字节码
* @param <T> 被代理类的类型
* @return 代理对象
*/
public <T> T getProxyObject(Class<T> cls) {
/*
Proxy类是JDK动态代理类
静态方法newProxyInstance用于创建类的代理对象dd
*/
Object proxyObj = Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; // 代理方法执行结果
try {
tx.begin(); // 开启事务
result = method.invoke(cls.newInstance(), args); // 执行被代理方法
tx.commit(); // 提交事务
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); // 回滚事务
} finally{
tx.closeSession(); // 关闭资源
}
return result; // 返回被代理方法的执行结果
}
});
return (T) proxyObj; // 返回代理对象
}
}
—配置类
@Configuration
public class SpringConfig {
@Bean(name = "tx")
public TransactionMangerHandler getTransactionMangerHandler() {
return new TransactionMangerHandler();
}
@Bean(name = "dynamicProxyUtil")
public DynamicProxyUtil getDynamicProxyUtil() {
DynamicProxyUtil proxyUtil = new DynamicProxyUtil();
return proxyUtil;
}
}
—测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DynamicProxyTest {
@Resource(name = "dynamicProxyUtil")
private DynamicProxyUtil proxyUtil;
@Test
public void testInsert() {
// 通过ProxyUtil工具类获取某个类的代理对象
UserService userService = proxyUtil.getProxyObject(UserServiceImpl.class);
userService.insert();
}
@Test
public void testUpdate() {
UserService userService = proxyUtil.getProxyObject(UserServiceImpl.class);
userService.update();
}
—测试结果
JDK动态代理的不足
- JDK动态代理的对象必须要实现接口,因为JDK动态代理是基于接口代理的。
- 需要为每个对象创建代理对象。
- 代理粒度大,动态代理的最小单位是类(所有类中的方法都会被处理),但是查询方法不需要事务,因此不需要被代理。
CGLIB动态代理(第三方代理)
CGLIB(Code Generation Library)是一个开源项目。
CGLIB和JDK动态代理一样都是动态代理,但是CGLIB可以代理没有实现接口的类。
CGLIB代理没有实现接口的类时,程序在JVM运行过程中动态的为这个类创建一个子类,并重写父类方法,在调用父类方法时,在方法执行之前、之后、异常、最终做增强。
Spring默认已经集成CGLIB代理,直接可以使用即可,不用拷贝任何jar包。
案例代码
在我们的UserServiceImpl的实现类中,使用CGLIB的动态代理,进行方法的增强,增强事物的功能。
—UserServiceImpl2
public class UserServiceImpl2 {
public void insert() {
System.out.println("执行了service层的insert方法");
}
public void update() {
System.out.println("执行了service层的update方法");
}
}
—CGLIB代理工具类
@Component("cglibProxyUtil")
public class CglibProxyUtil {
@Autowired
@Qualifier("tx")
private TransactionMangerHandler tx;
/**
* 返回一个代理对象,代理对象就做了方法的增强,(事物管理,日志控制,权限管理等等)
* @param cls 被代理类的字节码
* @return
*/
public <T> T getProxyObject(Class<T> cls) {
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(cls.getClassLoader());
// 设置被代理类的字节码
enhancer.setSuperclass(cls);
// 设置功能增强
/**
* 代理类的增强方法,正常增强的地方
* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 被代理对象方法的参数
* @return 被代理对象执行的结果
* @throws Throwable
*/
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
Object result = null;
try {
tx.begin();
result = method.invoke(cls.newInstance(), args);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
tx.closeSession();
}
return result;
}
});
// 创建代理对象
Object proxyObj = enhancer.create();
return (T) proxyObj;
}
}
—配置类
@Configuration
public class SpringConfig {
@Bean(name = "tx")
public TransactionMangerHandler getTransactionMangerHandler() {
return new TransactionMangerHandler();
}
@Bean(name = "cglibProxyUtil")
public CglibProxyUtil getDynamicProxyUtil() {
CglibProxyUtil proxyUtil = new CglibProxyUtil();
return proxyUtil;
}
}
—测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class CglibProxyTest {
@Resource(name = "cglibProxyUtil")
private CglibProxyUtil proxyUtil;
@Test
public void testInsert() {
// 通过ProxyUtil工具类获取某个类的代理对象
UserServiceImpl2 proxyObj = proxyUtil.getProxyObject(UserServiceImpl2.class);
proxyObj.insert();
}
@Test
public void testUpdate() {
UserServiceImpl2 proxyObj = proxyUtil.getProxyObject(UserServiceImpl2.class);
proxyObj.update();
}
}
—测试结果
CGLIB代理的不足
- CGLIB可以标类的子类,并重写父类非final修饰符的方法。
- 要求类不能是final的,要代理的方法要是非final、非static、非private的。
- 动态代理的最小单位是类(所有类中的方法都会被处理)。
小结
解决代码重复的方案
在Spring中:
- 若目标对象实现了若干接口,Spring就会使用JDK动态代理。
- 若目标对象没有实现任何接口,Spring就使用CGLIB动态代理。
直接使用代理的缺陷
如果直接使用动态代理技术(JDK代理、CGLIB代理)解决代码重复问题,我们会发现,我们每一个类都要配置代理类,非常的麻烦。
动态代理模式的缺陷是什么
动态代理模式的缺陷是:
- 实现类必须要实现接口 -JDK动态代理
- 无法通过规则制定拦截无需功能增强的方法。
如何解决这个问题?
使用Spring提供了AOP的实现。