从本篇文章开始我们就要开始研究spring的第二大特性AOP了,也就是面向切面编程。 在java开发过程中,我们把我们具体要实现的业务方法,可以称之为核心功能,比如注册,登陆,增删改查等,而除了额外功能以外,往往我们还需要一下额外功能。
什么是额外功能呢,他们本身不属于业务系统,可有可无,代码量小,如事务,日志和性能分析等。
那么我们在开发的时候,最好不要把这些额外功能和核心功能的代码写到一起,这样不利于维护。所以我们引入了代理模式。
一. 代理模式
代理模式是设计模式的一种,主要解决的问题就是以一种优雅的方式为上面所说的核心功能添加额外功能,并且不破坏核心功能的代码独立性。
代理设计模式: 通过代理类,为原始类(目标) 增加额外功能
名词解释: 很重要,一定要理解清楚,后面我们的动态代理和aop都会用到以下名词
1. 目标类: 又称原始类 指的是业务类(核心功能),如业务运算,dao调用等
2. 目标方法: 也称原始方法 目标类中的方法就是目标方法
3. 额外功能: 也叫附加功能 日志,事务,性能等
代理模式开发的核心要素
代理类 = 目标类 附加功能 与原始类实现相同的接口
二. 静态代理
接下来我们通过一个静态代理的代码方式演示一下上面提到的代理模式。根据核心要素,我们需要有目标类,附加功能和 实现相同接口。
代码语言:javascript复制public interface UserService {
// 注册
void register(User user);
// 登录
boolean login(String name ,String password);
}
public class UserServiceImpl implements UserService{
@Override
public void register(User user){
System.out.println("用户注册");
}
@Override
public boolean login(String name, String password){
System.out.println("用户登录");
}
}
复制代码
上面的UserServiceImpl 就是我们的目标类,里边的两个方法就是我们的目标方法(也就是我们的核心功能)。现在我们要为这两个目标方法增加一个打印日志的额外功能。我们就可以通过代理模式实现,实现的方式,就是写一个代理类,让他和目标类实现相同的接口。
代码语言:javascript复制public class UserServiceProxy implements UserService{
private UserServiceImpl userService = new UserServiceImpl();
@Override
public void register(User user){
System.out.println("-----log------");
userService.register(user);
}
@Override
public boolean login(String name, String password){
System.out.println("------log------");
userService.login(name, password);
}
}
复制代码
通过实现相同接口并持有目标类引用,我们就优雅的在不破坏原有类的基础上,为他们添加了额外功能。这就是静态代理的实现方式。但是静态代理也是存在一些弊端的
- 静态代理类文件过多,不利于项目管理(每为一个类添加额外功能就需要有一个代理类)
- 额外功能的维护性差,代理类中额外功能修改起来比较麻烦。
三. Spring动态代理
spring中也为我们提供了动态代理的实现。可以帮助我们为目标类添加额外功能。我们来看下如何实现。
3.1 引入依赖
代码语言:javascript复制<dependency>
<groupId>org.spring.framework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>asprctjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>asprctjweaver</artifactId>
<version>1.8.3</version>
</dependency>
复制代码
3.2 开发步骤
1. 创建目标对象
代码语言:javascript复制public class UserService implements UserService{
@Override
public void register(User user){
System.out.println("注册,,,,调用dao");
}
@Overrride
public boolean login(String name, String password){
System.out.println("登录。。。。调用dao");
return true;
}
}
2. 装配bean
代码语言:javascript复制<bean id="userService" class = "com.xxx.UserServiceImpl" />
复制代码
3. 提供额外功能
Spring为我们提供了一个MethodBeforeAdvice接口,额外功能写在该接口的实现方法中,会运行在原始方法之前
代码语言:javascript复制public class Before implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] objects, Object target){
System.out.println("proxy......");
}
}
复制代码
装配Bean
代码语言:javascript复制<bean id="before" class="com.xxx.Before" />
复制代码
4. 定义切入点
切入点: 额外功能加入的位置 引入的目的:由程序员根据自己的需要决定额外功能加给哪个原始方法
简单测试: 给所有方法都作为切入点,都加入额外功能。
代码语言:javascript复制<aop:confg>
<aop:pointcut id="pc" expression="execution(* *(..))" />
</aop:confg>
复制代码
5. 组装(额外功能和切入点的整合)
把切入点和额外功能进行整合。所有的方法
代码语言:javascript复制<aop:confg>
<aop:pointcut id="pc" expression="execution(* *(..))" />
<aop:advisor advice-ref="before" pointcut-ref="pc" />
</aop:confg>
复制代码
6.调用
获得spring工厂创建的动态搭理对象,并进行调用 注意:
- spring工厂通过原始对象的id值获得的是代理对象
- 获得代理对象后可以通过声明接口类型对代理对象进行接收
UserService userService = (UserService)ctx.getBean("userService");// 代理对象
userService.login();//
复制代码
打印结果:
proxy... login...
3.3 动态代理细节分析
通过上面的方式,我们已经演示了如何通过Spring完成了一个动态代理的过程。 实现了将核心功能和额外功能分离开发,动态灵活组装的案例。其本质特点其实是和上面的静态代理的一样的,那么我们接来下来分析几个问题,加深一下我们对于动态代理的理解。
1. Spring创建动态代理的类在哪里?
我们在上面调用的时候,其实还是通过获取原始对象的方式来调用的,但其实此时的原始对象已经是一个代理对象了。
Spring框架运行时,会通过动态字节码技术,在JVM中创建的动态代理对象,运行在JVM内部,等程序结束后,会和jvm一起消失。
这也是为什么叫做动态代理的原因,就是因为这个对象是动态生成出来的。不像静态代理我们必须自己创建。
2. 动态代理编程简化了代理的开发
我们上面为UserService中的方法,增加了一个额外功能(虽然只是打印了一句话),如果我们想给OrderService中也增加一个和UserService相同的额外功能,只需要把OrderService也配置到Spring的配置文件中即可,额外功能的代码都是可以复用的,无需额外配置。(主要是我们切入点的表达式是配置的所有方法都执行额外功能,如果想要指定部分方法执行,可以通过修改切入点表达式的方式实现,后面会详细讲解)
在额外功能不变的前提下,创建其他目标类的代理对象时,只需要执行目标对象即可。
3. 代理额外功能的可维护性增强了
想要修改额外功能,只需要执行新的before, 目标类无需修改。