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());
}
執行結果:
上面的代碼和執行結果,可以看出靜态代理的方式
動态代理
@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");
}
執行結果:
以上,java的動态代理,就不需要再出現UserServiceImplProxy這種代理類了,而是試用Proxy和InvocationHandler動态生成了UserServiceImpl的代理類,擴充了原始類中的功能。
spring的動态代理
Spring中,有一套自己的動态代理的開發方式,編寫起來會更加的優雅,使用aop的方式來實作,其中最主要的接口是MethodBeforeAdvise和MethodInterceptor
- MethodBeforeAdvise:顧名思義,實作了這個接口,會在原始類的原始方法執行之前,進行功能的擴充
- MethodInterceptor:将原始類的原始方法進行攔截,可以在原始方法的前後、報錯、傳回等位置,進行攔截,甚至可以改變傳回值。
實作spring的動态代理,主要分為四個步驟:
- 原始類的編寫
- 擴充類的編寫,實作上述兩個接口
- pointcut-切點的聲明
- 将擴充類和切點進行組裝
首先,需要引入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");
}
執行結果:
我們可以看到,實作類中的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的動态代理,有兩種類型,一種是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>
- 注解模式配置