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>
- 注解模式配置
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>