天天看點

spring學習記錄(九)Spring的動态代理開發

Spring的動态代理開發

說起代理設計模式,大家應該都不陌生:一個原始類,一個代理類,都實作相同的接口,擴充類依賴原始類,并在接口的實作中,調用原始類的方法,并且在調用原始類中方法的前後,進行擴充功能的注入,這就是靜态代理模式。

而java中的動态代理,則是試用Proxy類和InvocationHandler接口,來實作了代理的功能,并且不用寫很多的代理類。

下面我們來依次看一下

靜态代理:

//接口
public interface UserService {

    public void register(Person person) ;

    public void login(String username, String password) ;
}

//實作類
public class UserServiceImpl implements UserService {
    @Override
    public void register(Person person) {
        System.out.println("UserServiceImpl.register");
    }

    @Override
    public void login(String username, String password) {
        System.out.println("UserServiceImpl.login");
    }
}

//代理類
public class UserServiceImplProxy implements UserService {

    private UserService userService = new UserServiceImpl();

    @Override
    public void register(Person person) {
        System.out.println("UserServiceImplProxy.register before");
        userService.register(person);
        System.out.println("UserServiceImplProxy.register after");
    }

    @Override
    public void login(String username, String password) {
        System.out.println("UserServiceImplProxy.login before");
        userService.login(username, password);
        System.out.println("UserServiceImplProxy.login after");
    }
}

           

測試方法:

@Test
public void test3(){
    UserService userService = new UserServiceImplProxy();
    userService.login("huwenchao", "password");
    userService.register(new Person());
}
           

執行結果:

spring學習記錄(九)Spring的動态代理開發

上面的代碼和執行結果,可以看出靜态代理的方式

動态代理

@Test
public void test5(){
    UserService userService = new UserServiceImpl();
    UserService serviceProxy = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(method.getName() + ": before");
            Object ret = method.invoke(userService, args);
            System.out.println(method.getName() + ": after");
            return ret;
        }
    });
    serviceProxy.register(new Person());
    serviceProxy.login("huwenchao", "password");
}
           

執行結果:

spring學習記錄(九)Spring的動态代理開發

以上,java的動态代理,就不需要再出現UserServiceImplProxy這種代理類了,而是試用Proxy和InvocationHandler動态生成了UserServiceImpl的代理類,擴充了原始類中的功能。

spring的動态代理

Spring中,有一套自己的動态代理的開發方式,編寫起來會更加的優雅,使用aop的方式來實作,其中最主要的接口是MethodBeforeAdvise和MethodInterceptor

  1. MethodBeforeAdvise:顧名思義,實作了這個接口,會在原始類的原始方法執行之前,進行功能的擴充
  2. MethodInterceptor:将原始類的原始方法進行攔截,可以在原始方法的前後、報錯、傳回等位置,進行攔截,甚至可以改變傳回值。

實作spring的動态代理,主要分為四個步驟:

  1. 原始類的編寫
  2. 擴充類的編寫,實作上述兩個接口
  3. pointcut-切點的聲明
  4. 将擴充類和切點進行組裝

首先,需要引入spring-aop相關的jar包:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.15</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>
           
  • 編寫原始類

    我們就使用上述的UserService接口和UserServiceImpl實作類

  • 編寫擴充類,我們使用MethodInterceptor來說明
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("MyMethodInterceptor.invoke  before");
        Object ret = invocation.proceed();
        System.out.println("MyMethodInterceptor.invoke  after");
        return ret ;
    }
}
           
  • 配置檔案中進行聲名,包括切點和切點的組裝
<bean id="arround" class="com.huwc.dynamic.MyMethodInterceptor"></bean>
<bean id="userService" class="com.huwc.dynamic.UserServiceImpl"></bean>
<aop:config>
   <aop:pointcut id="pc" expression="execution(* login(String, ..))"/>
   <aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
           

其中,涉及到了切點表達式的編寫,我們就不展開了

測試方法:

@Test
public void test2(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    UserService service = (UserService) ctx.getBean("userService");
    System.out.println(service.getClass().getName());
    service.register(new Person());
    service.login("huwenchao", "password");
}
           

執行結果:

spring學習記錄(九)Spring的動态代理開發

我們可以看到,實作類中的register方法,并沒有被攔截,而login方法則被攔截了,這個是和我們指定的切點表達式相關。從上面的執行結果中,我們可以看到從IOC容器中拿到的UserService的bean,其實是一個代理對象,并且成功攔截了login方法,在前後進行了功能的擴充。

其中切點(pointcut)的表達式主要又分為:

  • execution的表達式
  • args表達式
  • within表達式
  • @annotation表達式

    每個表達式,都有自己的含義和作用,并且多個表達之間可以進行and和or的邏輯運作,具體的細節,感興趣的可以自己查找和學習。

基于注解的spring的動态代理

spring的動态代理,也可以通過注解的方式來編寫,會更加的簡單和友善,步驟如下

  • xml配置:
<bean id="personService" class="com.huwc.proxy.PersonServiceImpl"></bean>
<bean id="myAspect" class="com.huwc.aspect.MyAspect"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
           
  • Aspect–切面類的編寫
@Aspect
public class MyAspect {

    @Pointcut("execution(* login(..))")
    public void myPointCut(){}

    @Around(value = "myPointCut()")
    public Object arround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("------aspect log --------------");
        Object ret = joinPoint.proceed();

        return ret ;
    }

    @Around(value = "myPointCut()")
    public Object arround2(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("------aspect tx --------------");
        Object ret = joinPoint.proceed();

        return ret ;
    }
}
           

測試代碼:

@Test
public void testAspect(){
    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
    PersonService service = (PersonService) ctx.getBean("personService");
    service.register(new com.huwc.bean.Person());
    service.login("huwenchao", "password");
}
           

執行結果:

spring學習記錄(九)Spring的動态代理開發

注意:

spring的動态代理,有兩種類型,一種是java動态代理,基于接口實作,另一種是cglib代理,基于類的繼承來實作,spring中預設的代理類型為java動态代理,如果需要修改為cglib代理,則需要修改spring中的配置檔案

  • 傳統配置
<aop:config proxy-target-class="true">
    <!--   定義切入點:所有的方法都進行攔截     -->
    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    <!--   組裝:把切入點和額外的功能,進行組裝     -->
    <aop:advisor advice-ref="myMethodInterceptor" pointcut-ref="pc"></aop:advisor>
</aop:config>
           
  • 注解模式配置