动态代理
动态代理可以在不修改被代理对象源码的情况下,为代理对象进行功能增强
注:
*Proxy是JDK提供的工具类,使用该类创建代理类时,要求被代理的类必须实现接口!
若要给没有实现接口的类创建代理类,需要使用cglib
下面是使用Proxy*
ProxyFactory (工具类util中)
不加事务
package utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
//创建代理类的工厂类
public class ProxyFactory {
public static <T> T getProxy(Class<T> clazz) {
//创建实例
try {
final T tt = clazz.newInstance();
//返回代理对象
return //创建代理实例
(T) Proxy.newProxyInstance(
//类加载器 原因:由于需要在程序运行时创建类的对象,并加载到JVM中运行,运行之前需要类加载
ProxyFactory.class.getClassLoader(),
//被代理类实现的所有接口(为了实现它的方法)代理类内部会根据接口进行实现
clazz.getInterfaces(),
//相当于在代理类中写的增强 当调用代理类中的任何方法时,都由该接口中的invoke方法来处理
new InvocationHandler() {
/*
Object proxy: 代理类,一般不使用该对象
Method method:被代理类中的方法对象
Object[] args 调用方法时传递的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method的方法调用可能有返回值
Object result = null;
result = method.invoke(tt, args); //业务核心
return result ;
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
加事务
package utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
//创建代理类的工厂类
public class ProxyFactory {
public static <T> T getProxy(Class<T> clazz) {
//创建实例
try {
final T tt = clazz.newInstance();
//返回代理对象
return //创建代理实例
(T) Proxy.newProxyInstance(
//类加载器 原因:由于需要在程序运行时创建类的对象,并加载到JVM中运行,运行之前需要类加载
ProxyFactory.class.getClassLoader(),
//被代理类实现的所有接口(为了实现它的方法)代理类内部会根据接口进行实现
clazz.getInterfaces(),
//相当于在代理类中写的增强 当调用代理类中的任何方法时,都由该接口中的invoke方法来处理
new InvocationHandler() {
/*
Object proxy: 代理类,一般不使用该对象
Method method:被代理类中的方法对象
Object[] args 调用方法时传递的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method的方法调用可能有返回值
Object result = null;
Connection connection = null;
try {
connection = JdbcUtil.getConnection();
connection.setAutoCommit(false); //开启事务
//用反射调用原有业务
result = method.invoke(tt, args); //业务核心
} catch (Exception e) {
e.printStackTrace();
try {
connection.rollback(); //事务回滚
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
connection.commit(); //提交事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
进一步完善
package utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
//创建代理类的工厂类
public class ProxyFactory {
public static <T> T getProxy(Class<T> clazz) {
//创建实例
try {
final T tt = clazz.newInstance();
//返回代理对象
return //创建代理实例
(T) Proxy.newProxyInstance(
//类加载器 原因:由于需要在程序运行时创建类的对象,并加载到JVM中运行,运行之前需要类加载
ProxyFactory.class.getClassLoader(),
//被代理类实现的所有接口(为了实现它的方法)代理类内部会根据接口进行实现
clazz.getInterfaces(),
//相当于在代理类中写的增强 当调用代理类中的任何方法时,都由该接口中的invoke方法来处理
new InvocationHandler() {
/*
Object proxy: 代理类,一般不使用该对象
Method method:被代理类中的方法对象
Object[] args 调用方法时传递的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// method的方法调用可能有返回值
Object result = null;
//拿到方法名
String methodName = method.getName();
/*
根据方法名进行不同业务的判断,这就要求在开发的过程中,方法名不可以随便起名(开发规范)
例如,在系统开发前,公司规定,查询的方法必须以get、query、select、find开头,
其它方法不得以这些开头,这样就可以针对查询和非查询进行不同的处理
通常情况下,对于查询,需要进行性能统计;
而对于非查询要进行事务处理
*/
// 判断方法名以什么开头
if(methodName.startsWith("get")
||methodName.startsWith("query")
||methodName.startsWith("select")
||methodName.startsWith("find开头") ){ //查询
long start = System.currentTimeMillis();// 当前时间
result = method.invoke(tt, args); //业务核心 假设是查询业务
long end = System.currentTimeMillis();// 业务核心运行后的当前时间
long time = start-end; //耗时
System.out.println(time); //输出耗时
/*
可以采用数据库进行统计,通过反射来获取 :
clazz.getName //获取类名
clazz.getName+"."+methodName//获取方法名
后台提供查询页面
*/
}else { //非查询,加入事务
Connection connection = null;
try {
connection = JdbcUtil.getConnection();
connection.setAutoCommit(false); //开启事务
//用反射调用原有业务
result = method.invoke(tt, args); //业务核心
return result;
} catch (Exception e) {
e.printStackTrace();
try {
connection.rollback(); //事务回滚
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
connection.commit(); //提交事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return null;
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
业务逻辑(service)
接口
package service;
public interface Accountservice {
void transfer(String from,String to,int money);
}
接口实现类
package service.Impl;
import Dao.AccountDao;
import Dao.Impl.AccountDaoImpl;
import service.Accountservice;
//被代理类
public class AccountserviceImpl implements Accountservice {
public void transfer(String from, String to, int money) {
AccountDao accountDao = new AccountDaoImpl();
accountDao.update(from, -money);
// System.out.println(1/0);// 异常
accountDao.update(to, money);
}
}
Dao
接口
package Dao;
public interface AccountDao {
void update(String from, int i);
}
接口实现类
package Dao.Impl;
import Dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import utils.JdbcUtil;
import java.sql.SQLException;
public class AccountDaoImpl implements AccountDao {
public void update(String account, int money) {
QueryRunner queryRunner = new QueryRunner();
String sql = "update account set money = money+? where account = ?";
try {
queryRunner.update( JdbcUtil.getConnection(),sql,money,account);
System.out.println("成功");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
测试类:
import org.junit.Test;
import service.Accountservice;
import service.Impl.AccountserviceImpl;
import utils.ProxyFactory;
public class Tester {
@Test
public void test(){
Accountservice accountservice = ProxyFactory.getProxy(AccountserviceImpl.class);
accountservice.transfer("tom","jack",100);
}
}
总结:
动态代理和静态代理
- 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
使用代理模式的优缺点
优点:
- 代理模式在客户端和目标对象之间产生了一个中介和保护作用
- 代理模式可以增强目标对象的功能。
- 代理模式使得客户端和目标对象产生了分离,降低了系统的耦合度。
缺点:
- 编码复杂。