Java 反射、代理


 

反射(reflect)

反射:通過類的class對象來獲取類的信息,動態操作類中的字段、調用類中的方法。
 

獲取class對象的三種方式

// Class.forName("全限定類名")
Class<?> class1 = Class.forName("com.chy.mall.model.User");  //必須寫成全類名。類名是String形式,編譯時並不知道Class對象的類型,所以是?
Class<User> class2 = (Class<User>)class1;  //強轉


//類名.class
Class<User> class3=User.class;


//對象名.getClass()
User user=new User();
Class<? extends User> class4=user.getClass();

 

使用反射創建對象

Class<User> userClass = User.class;  //獲取class對象
Constructor<User> constructor = userClass.getConstructor(int.class, String.class, int.class);  //通過class對象獲取指定的構造器,形參表要寫成class對象的形式
User user = constructor.newInstance(1, "chy", 20);  //傳入實參創建實例

 

使用反射操作字段

Class<User> userClass = User.class;
Field nameField = userClass.getDeclaredField("name");  //獲取私有字段
nameField.setAccessible(true);   //取消權限檢查。private不允許在類外操作,需要取消權限檢查才能操作private成員

User user = new User(1, "zhangsan", 20);
String name = (String) nameField.get(user);  //傳入對象,獲取該字段的值,返回Object類型
nameField.set(user, "lisi");  //設置對象該字段的值

 

使用反射調用方法

Class<User> userClass = User.class;

Method getNameMethod = userClass.getMethod("getName");  //獲取無參的公有方法
Method getNameMethod = userClass.getMethod("setName", String.class);  //獲取帶參的公有方法。後面是形參表的class對象,參數個數可變

User user = new User(1, "zhangsan", 20);
String name = (String) getNameMethod.invoke(user);  //調用指定對象的該方法(無參),返回值是Object類型
setNameMethod.invoke(user, "lisi");  //調用指定對象的該方法(帶參),後面是實參表,參數個數可變

 

使用反射動態創建、操作數組

//動態創建數組
Object obj = Array.newInstance(String.class, 10); //數組元素類型、元素個數,返回值是Object類型
String[] arr= (String[]) obj;  //有需要的話可以強轉

//動態操作數組。傳入數組的元素類型是無法預測的,所以參數是Object類型
Object first = Array.get(obj, 0);  //獲取數組指定位置(index)上的元素,返回Object
Array.set(obj, 0, "zhangsan");  //設置數組指定位置上的元素

 
反射的特點是動態操作,傳入該類的對象,根據傳入的對象動態進行操作。

 

代理(proxy)

代理是在原類的基礎上進行擴展、包裝,使之變成更加強大的類,代替原來的類來使用。aop就是使用代理實現的。
 
代理分爲2種

  • 靜態代理
  • 動態代理,常見的有jdk動態代理、cglib代理

 

靜態代理

//實現目標接口或繼承目標類
public class UserDaoProxy implements UserDao{
    private UserDao userDao;  //把要代理的接口或類寫作成員變量。聲明爲接口,則可以代理這個接口所有的實現類;聲明爲實現類,則只代理這個實現類

    //注入目標對象
    public UserDaoProxy(UserDao userDao) {
        this.userDao = userDao;
    }

	@Override
    public void addUser() {
        //.....   //前增強
        userDao.addUser();  //調用目標類的方法,如果是繼承目標類,通過super來調用
        //.....  //後增強
    }

	@Override
    public void deleteUser() {
        userDao.deleteUser();  //如果不需要增強,直接調用目標類的方法即可
    }

}
UserDao userDao = new UserDaoImpl();
UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);
userDaoProxy.addUser();    

 
靜態代理的特點

  • 需要實現目標接口或者繼承目標類
  • 可以代理接口、類
  • 可以設置每個方法的增強,很靈活;但要手動設置每個方法的增強,又很繁瑣
     

不管實不實現目標接口、繼不繼承目標類,都可以做到對目標接口、目標類的增強,爲什麼要實現目標接口、繼承目標類,沒必要?

不管是參數還是返回值,都是用目標接口、目標類來聲明,不會用代理類來聲明,實現、繼承的意義在於成爲子類,可以作爲目標類使用,在目標類出現的地方都可以使用,代理要做的是取代,並非只是增強。

 

jdk動態代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


//不需要繼承、實現目標接口、目標類
public class UserDaoProxyFactory {
    private UserDao userDao;  //jdk動態代理代理的是接口,要聲明爲接口
    
    //注入目標對象
    public UserDaoProxyFactory(UserDao userDao) {
        this.userDao = userDao;
    }

    
    //獲取代理,代理是目標接口的實例
    public UserDao getProxyInstance(){
        ClassLoader classLoader = userDao.getClass().getClassLoader();  //獲取目標類的類加載器。
        Class<?>[] interfaces = userDao.getClass().getInterfaces();  //獲取目標類實現的所有接口的class對象,這也是爲什麼要聲明爲接口的原因
        
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  //後2個參數是目標方法、實參表
                //.....   //前增強
                Object returnValue=method.invoke(userDao,args);  //調用目標方法,傳入目標對象、實參表
                //....  //後增強
                return returnValue;  //返回目標方法的返回值,Object類型
            }
        };

        //創建代理,返回值是Object類型,需要強轉
        Object userDaoProxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        //強轉爲目標接口類型
        return (UserDao)userDaoProxy;
    }
    
}
UserDaoImpl userDao = new UserDaoImpl();

//傳入目標對象,創建代理,代理是目標接口類型
UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance();

userDaoProxy.addUser();    

看invoke()的參數,在編譯時不能確定要增強的方法,運行時根據傳入的參數動態進行增強,所以叫做動態代理。靜態代理在編譯時就確定了要增強的方法。

是使用jdk自帶的反射實現的動態代理,所以叫做jdk動態代理。
 

jdk動態代理的特點

  • 不需要實現接口、繼承目標類
  • 代理的是接口
  • 通過代理調用的目標類的方法都會被增強,且所使用的增強完全一樣,偏死板

 

cglib動態代理

需要添加cglib的依賴

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
//需要實現MethodInterceptor接口
public class UserDaoProxyFactory implements MethodInterceptor {
    private UserDao userDao;    //可以是接口,也可以是類

    //傳入目標對象
    public UserDaoProxyFactory(UserDao target) {
        this.userDao = target;
    }

    //攔截目標方法,進行增強
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //.....  //前增強
        Object returnValue = method.invoke(userDao, objects);  //調用目標對象的方法
       //.....  //後增強
        return returnValue;
    }

    //創建代理,代理是目標接口類型
    public UserDao getProxyInstance(){
        Enhancer en = new Enhancer();  //創建工具類對象
        en.setSuperclass(userDao.getClass());  //設置基類(父類),即繼承目標類
        en.setCallback(this);  //設置回調函數,此句代碼是調用intercept()攔截目標方法,進行增強
        return (UserDao) en.create();  //創建並返回代理對象。創建的對象是Object型,需要強轉
    }
    
}
UserDao userDao = new UserDaoImpl();

//傳入目標對象,創建代理,代理是目標類型
UserDao userDaoProxy = new UserDaoProxyFactory(userDao).getProxyInstance();

userDaoProxy.addUser();

 
cglib動態代理的特點

  • 需要實現MethodInterceptor接口
  • 可以代理接口、類
  • 通過代理調用的目標類的方法都會被增強,且所使用的增強完全一樣,偏死板

 

總結

如果要對不同的方法做不同的增強,用靜態代理;如果對每個方法的增強都一樣,用動態代理。

如果要代理接口,使用三種都行;如果要代理類,用靜態代理、cglib動態代理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章