代理模式-指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,属于结构型设计模式。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
简而言之代理模式就是对目标对象做个封装,以起到隐藏目标对象(保护目标对象),和在目标对象进行的逻辑处理之外,增加一些逻辑处理的功能(增强目标对象)。
主要分为静态代理和动态代理!其中,动态代理又有JDKProxy和CGLib两种主要的动态代理方式
静态代理实例--
显式声明被代理对象
当今社会,年轻人一般都没有时间去找对象,那么心急的父母就会作为孩子的代理,去帮孩子找对象,在这里,孩子就是被代理对象。
首先创建一个Person接口,里面定义了一个findLove方法:
public interface Person {
public void findLove();
}
知识兔再创建一个Son类,它去 实现Person接口:
public class Son implements Person{
@Override
public void findLove(){
System.out.println("儿子要求:肤白、貌美大长腿");
}
}
知识兔再创建一个Father类,在它里面显式声明了被代理对象(即Son类的对象),并显式的定义了一个带参构造函数,然后也定义一个findLove方法,并在里面调用Son的findLove方法:
public class Father{
//显示声明被代理对象
private Person person;
public Father(Person person){
this.person = person;
}
public void findLove(){
System.out.println("父亲物色对象");
this.person.findLove();
System.out.println("双方父母同意,确立关系");
}
}
知识兔来看测试类FatherProxyTest:
public class FatherProxyTest {
public static void main(String[] args) {
//只能帮儿子找对象,这就是静态代理
Father father = new Father(new Son());
father.findLove();
}
}
知识兔在测试类里面,通过Father类的构造函数,得到了一个Son类的对象,并且执行了Son类里的findLove方法,这就是静态代理模式的一个简单例子。
再看一个实际的业务场景,在分布式业务场景中,我们通常会对数据库进行分库分表,分库分表之后使用Java操作时,就可能需要配置多个数据源,我们可以实现根据订单创建时间来动态切换数据源(不是动态代理)。
首先来创建Order订单实体:
//创建order订单实体
public class Order {
private Object orderinfo;
//订单创建时间按年分库
private long createTime;
private String id;
public Object getOrderinfo() {
return orderinfo;
}
public void setOrderinfo(Object orderinfo) {
this.orderinfo = orderinfo;
}
public long getCreateTime() {
return createTime;
}
public void setCreateTime(long createTime) {
this.createTime = createTime;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
知识兔创建OrderDao持久层操作,里面就是一些逻辑操作:
//创建OrderDao持久层操作
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao创建order成功");
return 1;
}
}
知识兔创建IOrderService接口:
//创建IOrderService接口
public interface IOrderService {
int createOrder(Order order);
}
知识兔创建OrderService实现类:
public class OrderService implements IOrderService {
//静态代理的标志,显式声明被代理对象
private OrderDao orderDao;
public OrderService(){
//如果使用spring应该是自动注入的
//我们为了使用方便,在构造方法中直接将OrderDao初始化了
orderDao = new OrderDao();
}
@Override
public int createOrder(Order order) {
System.out.println("OrderService调用orderDao创建订单");
// TODO Auto-generated method stub
return orderDao.insert(order);
}
}
知识兔然后使用静态代理,根据订单创建时间自动按年进行分库。
先创建数据源路由对象,使用ThreadLocal单例实现,DynamicDataSourceEntry类:
//动态切换数据源
public class DynamicDataSourceEntity {
//默认数据源
public final static String DEFAULE_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
//构造方法私有化,即单例模式
private DynamicDataSourceEntity(){}
//清空数据源
public static void clear(){
local.remove();
}
//获取当前正在使用的数据源名字
public static String get(){
return local.get();
}
//还原当前切面的数据源
public static void restore(){
local.set(DEFAULE_SOURCE);
}
//DB_2018
//DB_2019
//设置已知名字的数据源
public static void set(String source){
local.set(source);
}
//根据名字动态设置数据源
public static void set(int year){
local.set("DB_" + year);
}
}
知识兔创建切换数据源的代理OrderServiceStaicProxy类,这里的被代理类就是IOrderService接口:
//代理数据源切换的逻辑
public class OrderServiceStaticProxy implements IOrderService {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
//因为只能代理OrderService的对象,所以是静态代理。
//静态代理要求你的对象已经创建好了
private IOrderService orderService;
public OrderServiceStaticProxy(IOrderService orderService){
this.orderService = orderService;
}
public int createOrder(Order order){
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
DynamicDataSourceEntity.set(dbRouter);
this.orderService.createOrder(order);
DynamicDataSourceEntity.restore();
return 0;
}
}
知识兔测试类,这里OrderServiceStaticProxy代理类的参数是OrderService类的对象,因为接口的对象只能通过其实现类实现!:
public class DbRouteProxyTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2017/02/01");
order.setCreateTime(date.getTime());
//接口的对象是只能通过其实现类实现,其对象调用的方法也是其实现类的方法。
//通过静态代理分配数据源。
IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
orderService.createOrder(order);
}catch(Exception e){
e.printStackTrace();
}
}
}
知识兔动态代理示例--
动态代理和静态代理基本思路一致,但是动态代理功能更强大,随着业务的扩展适应性更强。即动态代理把代理对象的限制取消,可以代理任何对象!
还以找对象为例,静态代理是父母给儿子找对象,而且父母只会给自己的儿子找对象,但是动态代理把找对象发展成一个产业,相当于媒婆、婚介所之类的,可以给任何有需求的人找对象。
动态配置和替换被代理对象。
动态代理主要有两种流行的实现方式:JDK动态代理和CGLib动态代理。
先看动态代理的例子
首先,创建一个Girl类,也让它去实现Person接口:
//对于被代理对象来说,它也必须要有一个接口!
public class Girl implements Person {
@Override
public void findLove() {
// TODO Auto-generated method stub
System.out.println("高富帅");
System.out.println("身高180厘米");
System.out.println("有6块腹肌");
}
}
知识兔为什么要实现这个接口?其实静态代理是不必实现这个接口的,但是如果使用JDK动态代理的话,就必须要实现一个接口!原因会在下面讲。
再创建一个JDKMeipo类,它作为代理类去代理Girl对象:
//必须实现InvocationHandler接口
public class JDKMeipo implements InvocationHandler {
//要持有被代理对象的引用
private Object target;
//提供一个注入的方法
public Object getInstance(Object target) throws Exception{
this.target = target;
//把类保存下来
Class<?> clazz = target.getClass();
//jdk的写法
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
before();
//用到了java反射
Object obj = method.invoke(this.target,args);
after();
return obj;
}
private void before(){
System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求");
System.out.println("开始物色");
}
private void after(){
System.out.println("OK的话,准备办事");
}
}
知识兔可以看出,要使用JDK动态代理的话,代理类必须实现InvocationHandler接口!并且要提供一个注入的方法getInstance方法,getInstance是不通过构造函数获取对象的一种手段。这个方法里面的代码基本上是一样的,里面通过泛型调用了getInterfaces方法,这里和为什么被代理类一定要实现一个接口有关系。
还重写了Invoke方法,可以看出JDK动态代理是通过Java反射实现的,基本上也是一样的写法。
还实现了before方法和after方法,这两个方法是用来实现目标对象增强的。就是在调用目标对象的逻辑前后执行的逻辑。
调用方法:
public class JDKProxyTest {
public static void main(String[] args) {
try{
//在动态代理中,媒婆可以为任何人找对象
//这个obj已经是JDK自动生成的类的对象了
Object obj = new JDKMeipo().getInstance(new Girl());
Method method = obj.getClass().getMethod("findLove", null);
method.invoke(obj);
//这段代码是输出$Proxy0.class文件
byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
os.write(bytes);
os.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
知识兔在这段代码中,可以看到不再需要显式声明被代理对象了,直接通过JDKMeipo的getInstance方法,传入被代理Girl类的对象,通过反射去执行findLove方法。但是这里有个疑问,如果仅是如此的话,那么之前的before和after方法就不会被执行,但是事实上,这两个方法的逻辑都被执行了!
可以通过debug啥的看出调用JDKMeipo得到的obj对象并不是某个已知类的对象,反而是一个叫做$Proxy0的类的对象,我们可以通过上面的一段代码去把这个类的class文件输出出来,反编译后可以看到这个类里面也有个findLove方法,事实上,obj其实是这个 类的对象,这就引出了JDKProxy的实现原理——
JDK动态代理,是利用动态生成字节码的原理,在代理中生成它($Proxy0)的源代码,然后编译成class文件,再通过classloader加载它,然后执行,也就是说,Java反射调用的类完全是一个新的类,里面调用了JDKMeipo类里重写的invoke方法,所以before和after方法才会被执行到!
这也是为什么被代理类要实现一个接口,因为需要根据这个接口找到这个接口里 方法,实现并覆盖到被代理类的方法里,并且新生成的方法是不可再次被重写的(final类型的),被代理的对象也不能被二次代理了,所以要把需要代理的方法写到接口里,才能被扫描到!
现在可以用动态代理去实现动态分配数据源了。
创建动态代理的类OrderServiceDynamicProxy:
public class OrderServiceDynamicProxy implements InvocationHandler{
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
Object proxyobj;
//动态代理的对象可以是任何对象了
public Object getInstance(Object proxyobj){
this.proxyobj = proxyobj;
Class<?> clazz = proxyobj.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
//args是参数列表,可以获取被代理对象的参数
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
before(args[0]);
Object object = method.invoke(proxyobj, args);
after();
return null;
}
private void before(Object target){
try{
//进行数据源的切换
System.out.println("Proxy before method");
//约定优于配置
//也就是说接口和实现类必须要有"getCreateTime"方法
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
DynamicDataSourceEntity.set(dbRouter);
}catch(Exception e){
e.printStackTrace();
}
}
private void after(){
System.out.println("Proxy after method");
//还原成默认的数据源
DynamicDataSourceEntity.restore();
}
}
知识兔测试代码:
public class DbRouteDynamicProxyTest {
public static void main(String[] args) {
try{
Order order = new Order();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/mm/dd");
Date date = sdf.parse("2017/02/01");
order.setCreateTime(date.getTime());
IOrderService orderService = (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService());
orderService.createOrder(order);
}catch(Exception e){
e.printStackTrace();
}
}
}
知识兔然后再看看CGLib动态代理的使用--
新建一个Customer类:
public class Customer {
public void findLove(){
System.out.println("儿子要求:肤白貌美大长腿");
}
}
知识兔创建CglibMeipo类:
public class CglibMeipo implements MethodInterceptor {
//把代理的引用传进来
//为什么要传Class?是因为要通过动态生成源代码去继承传来的这个Class
public Object getInstance(Class<?> clazz) throws Exception{
//相当于Proxy,代理的工具类
Enhancer enhancer = new Enhancer();
//设置父类就是传来的这个clazz
enhancer.setSuperclass(clazz);
//回调函数,回调下面的intercept方法
enhancer.setCallback(this);
return enhancer.create();
}
@Override
//在这个方法里实现代理的增强
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// TODO Auto-generated method stub
before();
//生成的类是子类,目的是实现父类的的一些方法
//在子类中实现父类的增强,调用父类的逻辑
Object obj = methodProxy.invokeSuper(o, objects);
after();
return obj;
}
private void before(){
System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求");
System.out.println("开始物色");
}
private void after(){
System.out.println("OK的话,准备办事");
}
}
知识兔测试类:
public class CglibTest {
public static void main(String[] args) {
try{
//JDK是采用读取接口的信息
//CGlib是覆盖父类方法
//目的:都是生成一个新的类,去实现增强代码逻辑的功能
//JDK Proxy对于用户而言,必须要有一个接口实现,目标类相对来说复杂
//CGlib可以代理任意一个普通的类,没有任何的要求
//CGlib生成代理逻辑更复杂,调用效率更高,生成一个包含了所有逻辑的FastClass,不再需要反射调用
//JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用
//CGlib有个坑,CGlib不能代理final的方法
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
Customer obj = (Customer) new CglibMeipo().getInstance(Customer.class);
obj.findLove();
}catch(Exception e){
e.printStackTrace();
}
}
}
知识兔可以看出,CGLib代理的对象不需要实现任何接口,它是通过动态继承目标对象实现的动态代理,也是自动生成新的类,不过是会生成三个。
通过上面的System.setProperty方法,可以把新生成的类输出到磁盘上。
但是CGLib不能代理final修饰的方法!因为final类型的方法不能被重写了!
动态代理总结--
可以利用动态代理做日志监听和AOP切面编程。
JDK动态代理的实现原理:
1.拿到被代理类的引用,并且获取它所有的接口(反射获取)。
2.JDK Proxy重新生成一个新的类,实现了被代理类所有接口的方法。
3.动态生成Java代码,把增强逻辑加入到新生代码中。
4.编译生成新的Java代码的class文件。
5.加载并重新运行新的class,得到的类就是全新类。
CGLib和JDK动态代理对比:
1.JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。
2.JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib是使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK效率低,
3.JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。
代理模式的优点:
代理模式能将代理对象和真实被调用的对象分离,一定程度上降低了系统的耦合性,易于扩展。可以起到保护目标对象的作用。可以增强目标对象的职责。
代理模式的缺点:
会造成系统中类的数目增多。在客户端和目标对象之间增加了一个代理对象,会造成请求处理速度变慢。增加了系统的复杂度。
Spring中的代理选择原则:
1.当bean有实现接口时,Spring就会用JDK的动态代理。
2.当bean没有实现接口时,Spring选择CGLib。
3.Spring可以通过配置强制使用CGLib,只需要在Spring的配置文件中加入如下代码——<aop:aspectj-autoproxy proxy-target-class="true"/>