Spring靜態代理與動態代理


代理模式

1、什么是代理模式?

  真實生活中有一種房屋中介是這樣的,租客根本就不知道房東是誰,一切簽合同、交租金、交鑰匙等操作都直接和中介公司發生。我們把這種模式稱之為代理模式。

  代理模式:客戶端直接使用的都是代理對象,不知道目標對象是誰,此時代理對象可以在客戶端和目標對象之間起到中介的作用。

2、特點

  代理對象完全包含目標對象,客戶端使用的都是代理對象的方法,和目標對象沒有直接關系

3、職責

  把不是目標對象該做的事情從目標對象上撇開——職責清晰。

4、分類

  靜態代理:在程序運行前就已經存在代理類的字節碼文件,代理對象和目標對象的關系在運行前就確定了。

  動態代理:動態代理類是在程序運行期間由JVM通過反射等機制動態的生成的,所以不存在代理類的字節碼文件。代理對象和真實對象的關系是在程序運行事情才確定的。

靜態代理

1、在程序運行前就存在代理類的字節碼文件,代理對象和真實對象的關系在運行之前就確定了。

2、優點:

  1.被代理的業務類只需要做好自己的業務,實現了責任分離,保證了業務類的重用性

  2.將業務類隱藏起來,起到了保護作用

3、 缺點:

  1.代理對象的某個接口只服務於某一個業務對象,每個真實對象都得創建一個代理對象

  2.如果需要代理的方法很多,則要為每一種方法都進行處理

  3.如果接口增加一個方法,除了所有實現類需要實現這個方法外,代理類也需要實現,增加了代碼的復雜度和成本

4、代碼示例

結構:

    

 

 

 代碼:

public class TransactionManager {

    public void begin(){
        System.out.println("開啟事務###");
    }
    public void commit(){
        System.out.println("提交事務++++++");
    }
    public void rollback(){
        System.out.println("回滾事務....");
    }
}
txManager
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>spring03</artifactId>
    <version>1.0.0</version>
    <properties>
        <!--
            定義全局變量:變量名為project.spring.version
        -->
        <project.spring.version>5.0.0.RELEASE</project.spring.version>

    </properties>
    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${project.spring.version}</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${project.spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${project.spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--事務管理器-->
    <bean id="txManager" class="com.test.class01_static.tx.TransactionManager"/>

    <bean id="userDao" class="com.test.class01_static.dao.impl.UserDaoImpl"/>

    <!--代理對象,屬性中的userService和Service為同一個實現類,使用id區分-->
    <bean id="proxy" class="com.test.class01_static.proxy.UserServiceImplProxy">
        <property name="userService" ref="userService"/>
        <property name="txManager" ref="txManager"/>
    </bean>
    <!--被代理對象-->
    <bean  id="userService" class="com.test.class01_static.service.impl.UserServiceImpl">
         <property name="userDao" ref="userDao"/>
    </bean>



</beans>
App-Context.xml
public class UserDaoImpl implements IUserDao {
    
    public void save() {
        System.out.println("保存成功!!!!!!!!!!");
    }

    
    public void update() {
        System.out.println("更新成功~~~~~~~~~");
    }
}
UserDaoImpl
public class UserServiceImplProxy implements IUserService {
    /**
     * 靜態代理的特點:
     * 指在程序運行之前就存在代理對象的字節碼文件(本文件)
     * 一個代理類只能代理一種類型
     * 實現了責任分離的目標(事務的開啟、提交、回滾與業務分離)
     * 靜態代理因為多了代理層,從而提升了維護成本
     */

    @Setter
    private IUserService userService;
    @Setter
    private TransactionManager txManager;


    public void save() {
        try{
            //開啟事務
            txManager.begin();
            //處理事務
            userService.save();
            //提交事務
            txManager.commit();
        }catch (Exception e){
            //回滾事務
            txManager.rollback();
        }
    }


    public void update() {
        try{
            //開啟事務
            txManager.begin();
            //處理事務
            userService.update();
            //提交事務
            txManager.commit();
        }catch (Exception e){
            //回滾事務
            txManager.rollback();
        }
    }
}
UserServiceImplProxy
public class UserServiceImpl implements IUserService {
    @Setter
    private IUserDao userDao;

    public void save() {
        userDao.save();
    }


    public void update() {
        userDao.update();
    }
}
UserServiceImpl
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
    @Autowired
    @Qualifier("proxy")
    //因為bean中有兩個服務層對象,所以利用別名的方式區分代理和服務層對象
    //也可直接命名IUserService proxy 效果相同
    private IUserService userService;

    @Test
    public void testSave() throws Exception {
        //因為使用的是proxy代理對象,所以使用的是代理類的方法
        userService.save();
    }

    @Test
    public void testUpdate() throws Exception {
        userService.update();
    }
}
App

JDK動態代理

1、在程序運行之前是沒有字節碼文件的,在程序運行時由JVM通過反射機制動態的創建出代理對象的字節碼。代理對象和真實對象的關系是在程序運行時才確定的。

2、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    :當前調用方法的實參

返回: 真實方法的返回結果
------------------------------------------------------------------------------------
jdk動態代理操作步驟 
① 實現InvocationHandler接口,創建自己增強代碼的處理器。
② 給Proxy類提供ClassLoader對象和代理接口類型數組,創建動態代理對象。
③ 在處理器中實現增強操作。

3.步驟

  1、配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--事務管理器-->
    <bean id="manager" class="com.test.class02_JDKProxy.tx.TransactionManager"/>

    <!--JDK動態代理-->
    <bean id="proxy" class="com.test.class02_JDKProxy.proxy.JDKProxy">
        <property name="txManager" ref="manager"/>
        <property name="target" ref="userService"/>
    </bean>

    <!--CGlib動態代理-->
    <bean id="cglibProxy" class="com.test.class02_JDKProxy.cglib.CglibProxy">
        <property name="target" ref="userService"/>
        <property name="txManager" ref="manager"/>
    </bean>


    <bean id="userService" class="com.test.class02_JDKProxy.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

    <bean id="userDao" class="com.test.class02_JDKProxy.dao.impl.UserDaoImpl"/>

</beans>
App-Context.xml

  其余的代碼和上面一樣

  2、獲取代理對象,實現接口成為代理類,實現接口的方法

public class JDKProxy implements InvocationHandler {
    @Setter
    private Object target;
    @Setter
    private TransactionManager txManager;

    //java.lang.reflect.Proxy是java所有動態代理類的父類
    public Object getProxy(){
        /**Proxy.newProxyInstance提供了一組靜態方法為一組接口動態的生成代理類及對象
         * 第一個參數:類加載器
         * 第二個參數:目標對象實現的接口的字節碼數據對象
         * 第三個參數:實現了InvocationHandler接口的類的對象(代理類)
         *          (本類可實現此接口,成為代理類)
         */
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         * proxy:代理對象
         * method:當前調用的真實方法,利用反射使用方法
         *        方法的使用,方法.invoke(對象名,參數)
         * args:當前調用方法的實參
         */
        try {
            //開啟事務
            txManager.begin();
            //處理事務
            method.invoke(target,args);
            //提交事務
            txManager.commit();

        }catch (Exception e){
            //回滾事務
            txManager.rollback();
        }finally {
            //釋放資源
            txManager.destroy();
        }
        return null;
    }
}

  3、測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class App {
    @Autowired
    private JDKProxy proxy;
    @Autowired
    private CglibProxy cglibProxy;

    @Test
    public void testSave() throws Exception {
        IUserService service = (IUserService)this.proxy.getProxy();
        System.out.println(service.getClass());
        service.save();
    }


    @Test
    public void testUpdate() throws Exception {
        IUserService service = (IUserService)this.proxy.getProxy();
        service.update();
    }

    @Test
    public void testSave2() throws Exception {
        UserServiceImpl service = (UserServiceImpl)cglibProxy.getProxy();
        service.save();
    }

    @Test
    public void testUpdate2() throws Exception {
        UserServiceImpl service = (UserServiceImpl)cglibProxy.getProxy();
        service.update();
    }
}
App

4、原理

 

 

 5、JDK動態代理:

  1.代理的對象必須要實現接口

  2.需要為每個對象創建代理對象;

  3.動態代理的最小單位是類(類中所有的方法都會被代理);

6、JDK動態代理總結:

  1.JAVA動態代理是使用java.lang.reflect包中的Proxy類與InvocationHandler接口這兩個來完成的。

  2.要使用JDK動態代理,必須要定義接口。

  3.JDK動態代理將會攔截所有public的方法(因為只能調用接口中定義的方法),這樣即使在接口中增加了新的方法,不用修改代碼也會被攔截。

  4.如果只想攔截一部分方法,可以在invoke方法中對要執行的方法名進行判斷

7、CGLib針對沒有接口的類的代理,和動態代理的區別是獲取代理對象的方法不一樣,其余一樣

public class CglibProxy implements InvocationHandler {
    @Setter
    private Object target;
    @Setter
    private TransactionManager txManager;

    public Object getProxy(){
        Enhancer enhancer = new Enhancer();//增強類
        enhancer.setSuperclass(UserServiceImpl.class);//對哪個父類增強
        //設置如何增強(寫實現了InvocationHandler的接口的類,也就是代理類)
        //這里本類實現了此接口,為代理類
        enhancer.setCallback(this);
        return enhancer.create();//創建並返回代理對象
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //開啟事務
            txManager.begin();
            //處理事務
            method.invoke(target,args);
            //提交事務
            txManager.commit();

        }catch (Exception e){
            //回滾事務
            txManager.rollback();
        }finally {
            //釋放資源
            txManager.destroy();
        }
        return null;
    }
}
CGLib

8、CGLib代理總結

  1.CGLIB可以生成目標類的子類,並重寫父類非final修飾符的方法。

  2.要求類不能是final的,要攔截的方法要是非final、非static、非private的。

  3.動態代理的最小單位是類(所有類中的方法都會被處理);

9、在Spring中:

 

  1.若目標對象實現了若干接口,Spring就會使用JDK動態代理。

 

  2.若目標對象沒有實現任何接口,Spring就使用CGLIB庫生成目標對象的子類。

 

  3.對接口創建代理優於對類創建代理,因為會產生更加松耦合的系統,也更符合面向接口編程規范。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM