- 一、案例分析
- 二、静态代理
- 1、静态代理概述
- 2、静态代理的实现
- 3、静态代理的优缺点
- 三、动态代理
- 1、字节码动态加载
- 2、JDK动态代理
- 3、JDK动态代理原理
- 4、CGLIB动态代理
- 5、拦截器思想
- 四、 代理总结
Spring系列
- Spring — Spring简介、入门、配置 , IoC和DI思想
- Spring — IoC核心(基于XML)、DI核心(基于XML)
- Spring — 使用IoC和DI模拟注册案例、注解配置IoC和DI
- Spring — 静态代理、动态代理、拦截器思想
- Spring — AOP思想、AOP开发、Pointcut语法、注解配置AOP
- Spring — DAO层、Spring JDBC、Spring事务控制
- Spring — XML配置事务、注解+XML、纯注解的配置方式
- Spring整合MyBatis
- Spring Java Config — 组件注册相关注解
- Spring Java Config — 常用注解
跳转到目录
1、引出问题之前我们在Service中写的业务逻辑方法, 对有些方法都要做事务管理,比如save和update操作距离,没有加上事务控制的代码如下:
public class EmployeeServiceImpl implement EmployeeService{
public void save(){
// 保存操作
}
}
加上事务控制之后
public class EmployeeServiceImpl implements EmployeeService{
public void save(){
// 打开资源
// 开启事务
try{
// 保存操作
// 提交事务
} catch (Exception e) {
// 回滚事务
} finally {
// 释放资源
}
}
}
上述问题: 我们在业务层的方法都得处理事务(繁琐的try-catch) 设计上存在的问题:
- 责任不分离,业务方法应该只需要关系如何完成业务功能;不需要去关心事务管理/日志管理/权限管理等
- 代码结构重复,维护成本大
跳转到目录
代理设计模式客户端直接使用的都是代理对象,不知道真实对象是谁, 此时代理对象可以在客户端和真实对象之间起到中介的作用;
- 代理对象完全包含真实对象, 客户端使用的都是代理对象的方法, 和真实对象没有直接关系;
- 代理模式的职责: 把不是真实对象做的事情从真实对象上撇开----责任清晰;
跳转到目录 在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了. 我们成为静态代理
跳转到目录 代码
// domain
public class Employee {
}
// ---------------------------------------------
// dao层
public interface EmployeeDao {
void save(Employee emp);
void update(Employee emp);
}
public class EmployeeDaoImpl implements EmployeeDao {
public void save(Employee emp) {
System.out.println("保存员工");
}
public void update(Employee emp) {
System.out.println("修改员工信息");;
}
}
// ---------------------------------------------
// service层
public interface EmployeeService {
void save(Employee emp);
void update(Employee emp);
}
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeDao dao;
public void setDao(EmployeeDao dao) {
this.dao = dao;
}
public void save(Employee emp) {
dao.save(emp);
System.out.println("保存成功");
}
public void update(Employee emp) {
dao.update(emp);
throw new RuntimeException("故意出错");
}
}
// ---------------------------------------------
// tx包,模拟事务管理器
public class TransactionManager {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
// ---------------------------------------------
// proxy类
public class EmployeeServiceProxy implements EmployeeService {
private TransactionManager tx; // 事务管理器
private EmployeeService target; // 真实对象/委托对象
public void setTarget(EmployeeService target) {
this.target = target;
}
public void setTx(TransactionManager tx) {
this.tx = tx;
}
// 被增强的方法
public void save(Employee emp) {
// 开启事务_对save方法的增强
tx.begin();
try {
target.save(emp);
tx.commit();
} catch (Exception e){
e.printStackTrace();
tx.rollback();
}
}
// 被增强的方法
public void update(Employee emp) {
tx.begin();
try {
target.update(emp);
tx.commit();
} catch (Exception e){
e.printStackTrace();
tx.rollback();
}
}
}
staticProxy.xml
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class StaticProxyTest {
// class com.sunny._01_static_proxy.proxy.EmployeeServiceProxy
@Autowired // 注入代理对象
private EmployeeServiceProxy service;
@Test
public void testSave(){
System.out.println(service.getClass());
service.save(new Employee());
}
@Test
public void testUpdate1(){
service.update(new Employee());
}
}
跳转到目录 静态代理:在程序运行前就已经存在代理类的字节码文件
,代理对象和真实对象的关系在运行前就确定了。
优点:
- 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
- 把真实对象隐藏起来了保护真实对象
缺点:
- 代理对象的某个接口只服务于某一种类型的对象,也就是说每一个真实对象都得创建一 个代理对象。
- 如果需要代理的方法很多,则要为每一种方法都进行代理处理。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。
这样就需要使用动态代理了
三、动态代理跳转到目录
- 静态代理:在程序运行前就
已经存在代理类的字节码文件
,代理对象和真实对象的关系在运行前就确定了。 - 动态代理:动态代理类是在程序
运行期间由JVM通过反射等机制动态的生成
的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的。
- 针对
有接口
:使用JDK动态代理 - 针对
无接口
:使用CGLIB或Javassist组件
跳转到目录 静态代理步骤 :
动态代理步骤 :
如何动态的加载一份字节码:
- 由于JVM通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循Java编译系统组织
.class
文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此就完成了动态创建一个类的能力;
跳转到目录 代码
// dao包同上
// service包
public interface EmployeeService1 {
void save(Employee1 emp);
void update(Employee1 emp);
// 测试增加功能不需要对增强类做操作
void delete(Long l);
// JDK动态反射,最小单位是类,类中的方法都会被拦截
void queryAll();
}
public class EmployeeServiceImpl1 implements EmployeeService1 {
private EmployeeDao1 dao1;
public void setDao1(EmployeeDao1 dao1) {
this.dao1 = dao1;
}
public void save(Employee1 emp) {
dao1.save(emp);
System.out.println("保存成功");
}
public void update(Employee1 emp) {
dao1.update(emp);
throw new RuntimeException("故意出错");
}
public void delete(Long l) {
System.out.println("删除成功");
}
public void queryAll() {
System.out.println("查询全部");
}
}
// ---------------------------------------------
// tx包,模拟事务管理器
public class TransactionManager1 {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
public void rollback(){
System.out.println("回滚事务");
}
}
// ---------------------------------------------
// tx包, 事务管理器增强
// 事务的增强操作
@SuppressWarnings("all")
public class TransactionManagerAdvice implements java.lang.reflect.InvocationHandler{
// 代理对象中包含的真实对象(对谁做增强)
private Object target; // 这里是对EmployeeService作增强
private TransactionManager1 tx;
public void setTarget(Object target) {
this.target = target;
}
public void setTx(TransactionManager1 tx) {
this.tx = tx;
}
// 创建一个代理对象
public T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器,一般跟上真实对象的类加载器
target.getClass().getInterfaces(),// 真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
this); // 如何做事务增强的对象
}
// 如何为真实对象的方法做增强的具体操作
/*
proxy: 代理对象
method: 原始方法(需要被增强的方法)
args: 原始方法的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 过滤某些方法(对某些方法不增强)
if (method.getName().equals("queryAll")){
return method.invoke(target, args);
}
Object ret = null;
tx.begin(); // 开启事务_ 增强操作
try {
//------------------------
ret = method.invoke(target, args); //调用真实对象的方法
//------------------------
tx.commit(); // 增强操作
} catch (Exception e){
e.printStackTrace();
tx.rollback(); // 增强操作
}
return ret;
}
/*class Xx implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}*/
}
dynamicProxy.xml
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdkDynamicProxyTest {
// com.sun.proxy.$Proxy13
@Autowired
private TransactionManagerAdvice advice;
@Test
public void testSave(){
// 获取到代理对象
EmployeeService1 proxy = advice.getProxyObject();
//System.out.println(proxy.getClass());
proxy.save(new Employee1());
}
@Test
public void testUpdate(){
// 获取到代理对象
EmployeeService1 proxy = advice.getProxyObject();
proxy.update(new Employee1());
}
@Test
public void testDelete(){
EmployeeService1 proxy = advice.getProxyObject();
proxy.delete(1L);
}
@Test
public void testQueryAll1(){
EmployeeService1 proxy = advice.getProxyObject();
proxy.queryAll(); // 没有增强该方法,作了判断过滤
}
}
3、JDK动态代理原理
跳转到目录
注意: 在接口方法Invoke中打印代理对象会造成死循环,实际上打印代理对象就是打印其对象的toString方法,通过上面反编译的代码,toString方法中仍会调用Invoke方法,造成了死循环.
动态代理代码解释:
public class TestJDKProxy {
/**
1. 借⽤类加载器 TestJDKProxy 或 UserServiceImpl 都可以
2. JDK8.x 前必须加 final
final UserService userService = new UserServiceImpl();
*/
public static void main(String[] args) {
// 1. 创建原始对象
UserService userService = new UserServiceImpl();
// 2. JDK 动态代理
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---- proxy log ----");
// 原始方法运行
Object ret = method.invoke(userService, args);
return ret;
}
};
UserService userServiceProxy = (UserService) Proxy.
newProxyInstance(TestJDKProxy.class.getClassLoader(),
userService.getClass().getInterfaces(),
handler);
/*
1、首先userServiceProxy是一个代理对象, 只是该对象是通过动态字节码技术,
在运行时根据字节码+反射机制, 动态生成一个实现UserService接口的代理类;
2、该代理对象调用增强的login方法, 首先就会调用到InvocationHandler接口实现类
的invoke方法, 上面代码是采用匿名内部类的方式来创建该接口实现类对象handler,
invoke方法就是对目标对象的原始方法做增强的。
3、method.invoke(), 其中method就是原始方法对象, args就是原始方法的参数,
当传入method.invoke(目标对象, 原始方法参数), 此时根据多态的动态绑定机制,
调用的额就是UserServiceImpl中的login方法
//... register方法同理
*/
userServiceProxy.login("zhenyu", "123456");
userServiceProxy.register(new User());
}
}
4、CGLIB动态代理
跳转到目录 使用JDK的动态代理,只能针对于目标对象存在接口的情况,如果目标对象没有接口
, 此时可以考虑使用CGLIB的动态代理方式;
代码:
public class TransactionManagerAdvice2 implements org.springframework.cglib.proxy.InvocationHandler {
// 代理对象中包含的真实对象(对谁做增强)
private Object target; // 这里是对EmployeeService作增强
private TransactionManager2 tx;
public void setTarget(Object target) {
this.target = target;
}
public void setTx(TransactionManager2 tx) {
this.tx = tx;
}
// 创建一个代理对象
public T getProxyObject() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
enhancer.setCallback(this); // 设置增强的对象
return (T) enhancer.create(); // 创建代理对象
}
// 如何为真实对象的方法做增强的具体操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = null;
tx.begin(); // 开启事务_ 增强操作
try {
//------------------------
ret = method.invoke(target, args); //调用真实对象的方法
//------------------------
tx.commit(); // 增强操作
} catch (Exception e){
e.printStackTrace();
tx.rollback(); // 增强操作
}
return ret;
}
}
// CGLIB代理对象
com.sunny._03cglib_dynamic_proxy.service.impl.EmployeeServiceImpl2$$EnhancerByCGLIB$$676c99e7
CGLIB原理: 通过生成代理类,然后继承目标类,再对目标类中可以继承的方法做覆盖,并在该方法中做功能增强,实则调用的是子类中的方法; 两种动态代理底层:
跳转到目录
- Java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。
模拟日志记录操作
// 在service方法调用之前,做日志记录
public class LogUtil {
public void writeLog(String methodClass,String methodName){
System.out.println(new Date().toLocaleString() + "调用了"+methodClass+"类中的" +methodName + "方法");
}
}
// 日志增强
public class LogAdvice implements org.springframework.cglib.proxy.MethodInterceptor{
private Object target; // 真实对象
private LogUtil logUtil; // 日志工具类
public void setTarget(Object target) {
this.target = target;
}
public void setLogUtil(LogUtil logUtil) {
this.logUtil = logUtil;
}
// 创建代理对象
public T getProxyObject() {
/*Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
enhancer.setCallback(this); // 设置增强的对象
return (T) enhancer.create(); // 创建代理对象*/
return (T) Enhancer.create(target.getClass(), this);
}
/**
* 如何对方法作增强
* @param proxy 代理对象
* @param method 要作增强的方法
* @param objects 要作增强方法的参数
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
logUtil.writeLog(method.getDeclaringClass().getName(),method.getName());
Object ret = method.invoke(target, objects); // 调用真实对象的方法
return ret;
}
}
cglibMethodInterceptor.xml
测试类
// com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3$$EnhancerByCGLIB$$b1d8ba01
@Autowired
private LogAdvice advice;
@Test
public void testSave(){
EmployeeServiceImpl3 proxy = advice.getProxyObject();
// System.out.println(proxy.getClass());
proxy.save(new Employee3());
}
@Test
public void testUpdate(){
EmployeeServiceImpl3 proxy = advice.getProxyObject();
proxy.update(new Employee3());
}
跳转到目录 JDK动态代理总结:
-
JAVA动态代理是使用
java.lang.reflect包中的Proxy类
与InvocationHandler接口
这两个来完成的。 -
要使用JDK动态代理,代理类必须要实现接口。
-
JDK动态代理将会拦截所有pubic的方法(因为只能调用接口中定义的方法) ,这样即使在接口中增加了新的方法,不用修改代码也会被拦截。
-
动态代理的最小单位是类(所有类中的方法都会被处理) ,如果只想拦截一部分方法 ,可以
在invoke方法中对要执行的方法名进行判断
。
CGLIB代理总结:
-
CGLIB可以生成委托类的子类,并重写父类非final修饰符的方法。
-
要求类不能是final 的,要拦截的方法要是非final、非static、非private的。
-
动态代理的最小单位是类(所有类中的方法都会被处理);
性能和选择:
-
JDK动态代理是基于实现接口的, CGUB和Javassit是基于继承委托类的。
-
从性能上考虑: Javassit > CGLIB > JDK
-
Struts2的拦截器和Hibernate延迟加载对象,采用的是Javassit的方式
-
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。若委托对象实现了干接口,优先选用JDK动态代理。
-
若委托对象没有实现任何接口, 使用Javassit和CGLIB动态代理。