Spring ----AOP
文章目录前言一、静态代理设计模式1.为什么需要代理设计模式1.1回顾MVC和三层架构1.2为什么需要代理前言一、静态代理设计模式1.为什么需要代理设计模式1.1回顾MVC和三层架构我们先来回顾一下MVCMVC:Model 模型 比如实体类 ,View 视图 比如JSP页面,Controller 控制器 比如Servlet早期的三层架构用户直接访问控制层,控制层就可以直接操作数据库servlet–对
一、静态代理设计模式
1.为什么需要代理设计模式
1.1回顾MVC和三层架构
我们先来回顾一下MVC
MVC:Model 模型 比如实体类 ,View 视图 比如JSP页面,Controller 控制器 比如Servlet
早期的三层架构
用户直接访问控制层,控制层就可以直接操作数据库
servlet–对数据进行增删改查
缺点:程序臃肿,不利于维护
Servlet的代码中:处理请求,响应,视图跳转,处理JDBC,处理业务代码,处理逻辑代码
因为上面的架构,分工不够明确,所以就演变成下面这样的架构
Model:
- 业务处理:业务逻辑(Service)
- 数据持久层:CRUD(DAO)
View
- 展示数据
- 提供连接发起Servlet请求
Controller
- 接收用户请求:(req:请求参数,Session参数)
- 提供连接发起Servlet请求交给业务层处理对应的代码
- 控制视图跳转
- 登录—>接收用户登录请求—>处理用户请求(获取用户登录的参数用户名,密码)—>交给业务层处理登录业务(判断用户名密码是否正确)—>DAO层查询用户名和密码是否正确
然后来简单回顾一个开发中的分层
DAO—>Service—>Controler
-
DAO:数据访问层,主要用来对数据库的数据进行操作
-
Service:业务层,用来处理业务逻辑
-
Controler:控制器
这里我推荐两篇关于MVC和三层架构的文章,我个人觉得讲解得很详细
浅谈 MVC与三层架构
MVC与三层架构理解
1.2为什么需要代理
Service层中主要是用来处理业务的,也就是核心功能,像那些业务逻辑,访问数据库的操作等。像那些事务,日志,性能等操作,属于附加功能,可有可无,代码量也比较少
那么额外功能写在Service层中好不好呢
- Service层从调用者角度(Controller):需要在Service层写额外功能
- 从软件设计角度,不希望Service层写额外功能,因为它是可有可无的
接下来举一个现实生活中的例子来说明一下生活中也会有类似场景
比如说有人想要租房子,那么我们可以把房东看成一个类,代表业务类,包含核心功能也就是和房客签订出租房屋的合同,但是它肯定不能只有核心功能,别人为什么要找你租房,而不是找其他人呢?可能是因为你张贴广告,并且房客来看过房子了,所以还需要有额外功能,贴广告和看房。但是我们来想一下,贴广告这些其实挺累的,房东肯定不想做这些事情,他们希望的是房客直接来找他们签合同租房,也就是不希望额外功能。但是房客又不允许房东没有额外功能,毕竟没有看到房子怎么样,房客怎么可能直接签订合同
- 房东(类似于软件设计者):不想要额外功能
- 房客(调用者):想要额外功能
我们肯定是尽量满足所有人的要求的,所以我们以后就跟房东说,你只要实现核心功能就可以了,其他的不用你管。但是房客就很迷糊了,我都不知道谁有房子出租,而且没有看房怎么签合同。这个时候,中介(Proxy)出现了,中介说,大家放心,我负责提供房子信息,房客以后就只需要找中介就可以了,房客看到房子后觉得很满意,想要签订合同,但是这个时候,这个功能就不属于中介管了,这就需要房东了,毕竟房产证又不在你中介手里,房客说,我怎么知道这个房子是你的,万一不是你的,我的钱不就打水漂了,所以到签合同的这个时候,中介就会说,房东你来吧,这样子房客的诉求也满足了,皆大欢喜了。
如果说,房客对中介提供的房源不满意,那么他可以找其他的中介,这样子,我们发现,我们并不需要修改代码,这样就提高了代码的维护性
2.代理设计模式
2.1概念
- 通过代理类,为原始类(目标类)增加额外功能
- 代理类提供的方法要和目标类的方法一一对应
- 有利于目标类的维护
- 目标类(原始类):指的是业务类,里面只做核心功能,比如业务运算,DAO的调用
- 目标方法(原始方法):目标类中的方法
- 额外功能:比如事务,日志,性能
2.2开发的核心要素
代理类=目标类+额外功能+目标类和代理类要实现相同的接口(为了让代理类和目标类的方法保持同步)
2.3实战
我们以租房子为例,来进行演示,下面这个演示是静态代理
静态代理:为每一个目标类,都提供一个代理类
提供代理类和目标类的公共接口
public interface House {
/**
* 出租房屋
*/
public void rent();
}
房东
/**房东
* @author zengyihong
* @create 2022--04--18 22:13
*/
public class Landlord implements House {
@Override
public void rent() {
System.out.println("房东:签订合同,出租房屋");
}
}
房屋中介(代理类)
**房屋中介
* @author zengyihong
* @create 2022--04--18 22:12
*/
public class HouseProxy implements House {
Landlord landlord=new Landlord();
Lodger lodger=new Lodger();
/**
* 当中介带领房客看房屋后,如果房客说满意,那么就可以签订合同了
* 如果房客不满意,那么他可能就会寻找其他房屋了
*/
@Override
public void rent() {
boolean flag= lodger.satisfy();;
if (flag){
landlord.rent();
}else {
System.out.println("房客:我还想看看其他的房屋");
}
}
/**
* 带领想租房子的人看房,对方如果说满意了,就可以签订合同了
* @return
*/
public void show(){
System.out.println("中介:带领房客看房屋");
rent();
}
}
房客
/**房客
* @author zengyihong
* @create 2022--04--18 22:11
*/
public class Lodger {
Scanner scanner=new Scanner(System.in);
/**
* 看房子是否满意
*/
public boolean satisfy(){
System.out.println("请问您对房子满意吗(Y/N)");
String str=scanner.next();
if ("Y".equalsIgnoreCase(str)){
return true;
}else{
return false;
}
}
}
测试
2.4静态代理存在的问题
- 静态代理的类文件过多,一个目标类就有一个代理类,不利于项目管理
- 以上面的代码举例子,可能有不同的中介来提供房屋信息,不同的中介其实它们提供的功能基本是一样的,但是现在写了这么多的代理类,其实也没什么意义
- 额外功能维护性差,也就是代理类中,额外功能修改复杂,比较麻烦
二、动态代理
1.Spring动态代理的概念
动态代理和静态代理在概念上没有什么区别,主要是开发步骤和底层实现的不同
- 概念:通过代理类为目标类增加额外功能
- 好处:有利于目标类的维护
2.开发环境
<?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>org.example</groupId>
<artifactId>spring-01</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring-01</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!-- 日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.18</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.8</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
3.Spring动态代理的开发步骤
public interface UserService {
public void register(User user);
public boolean login(String name,String password);
}
①创建原始对象(目标对象)
/**
* 原始类
*
* @author zengyihong
* @create 2022--04--18 21:57
*/
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register 业务运算+DAO");
}
@Override
public boolean login(String name, String password) {
System.out.println("UserServiceImpl.login");
return true;
}
}
然后在Spring的配置文件进行配置来创建对象
<bean id="userServiceImpl" class="com.zyh.proxy.UserServiceImpl"></bean>
②提供额外功能
我们需要把额外功能写在MethodBeforeAdvice接口的实现中
一旦实现了这个接口,那么在运行的时候,这个接口里面的额外功能会在原始类的方法运行前执行
/**before方法的作用就是把运行在原始方法执行之前运行的额外功能写在before方法中
* @author zengyihong
* @create 2022--04--19 8:56
*/
public class Before implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("----method before advice log---");
}
}
<bean id="before" class="com.zyh.dynamic.Before"></bean>
③定义切入点
- 切入点指的是额外功能加入的位置
- 目的:由程序员根据自己的需要,觉得额外功能加给哪一个原始方法
- 简单测试:所有方法都作为切入点,都加入额外的功能
<!--代表所有方法都作为切入点 目前是login register-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
</aop:config>
④组装
把第二步和第三步进行整合
为了看清楚一点,我把上面的配置都在这里体现
<bean id="userServiceImpl" class="com.zyh.proxy.UserServiceImpl"></bean>
<!--代表所有方法都作为切入点 目前是login register-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="before" pointcut-ref="pc"></aop:advisor>
</aop:config>
⑤调用
获得Spring工厂创建的动态代理对象,并且进行调用
ApplicationContext ctx=new ClassPathXmlApplicationContext("/applicationContext.xml");
注意:
1.Spring工厂通过原始对象的id值获得的是代理对象
2.获得代理对象后,可以通过声明接口类型,进行对象的存储
UserService userService=(UserService)ctx.getBean("userServiceImpl");
userService.login("","");
userService.regist();
测试
@Test
public void test() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext3.xml");
UserService userService = (UserService) ctx.getBean("userServiceImpl");
userService.login("tom","123");
userService.register(new User());
}
运行结果
----method before advice log--- 额外功能
UserServiceImpl.login 原始功能
----method before advice log---
UserServiceImpl.register 业务运算+DAO
4.动态代理细节分析
-
Spring创建的动态代理类在哪里?
- Spring创建的动态代理类,是Spring框架在运行的时候,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失
-
什么又叫动态字节码技术
- 先来回顾一下,以前是怎么运行一个类,是JVM运行这个类的字节码文件,然后创建该对象,进而调用这个对象的方法
- 现在通过动态字节码技术的话,我们就不需要编写源文件编译成.class文件
- 动态字节码是通过第三方框架来完成,我们通过这些框架,就可以直接在JVM生成字节码(动态字节码),把动态字节码加载到JVM中后,就可以创建对象了
- 动态代理其实就是动态字节码
总结一下
- 动态字节码技术:通过第三方的动态字节码框架,在JVM中创建对应类的字节码,进而创建对象
- 动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会产生像静态代理,类文件数量过多,影响项目管理的问题
-
动态代理编程简化代理的开发
- 在额外功能不变的情况下,我们在创建目标类的代理对象时,只需要指定目标对象就可以了
-
动态代理额外功能的维护性大大增强
- 以前用静态代理的话,比如说有几个类的方法的功能是一样的,但是这个时候我们想要修改它的功能,就得对这几个类的方法都进行修改,比较麻烦
- 使用动态代理的话,我们只需要新创建一个类来实现 MethodBeforeAdvice接口,并且实现里面的before方法,然后在配置文件进行配置即可
三、Spring动态代理详解
1.MethodBeforeAdvice详解
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("----method before advice log---");
}
- Method:指的是额外功能要增加给的那个原始方法
- 比如是在login方法执行之前,我们想要执行这个额外功能,那么login就是原始方法
- 额外功能要增加给的原始方法的参数
MethodBeforeAdvice接口作用:额外功能运行在原始方法执行前进行额外功能操作的话,就得实现这个接口
2.MethodInterceptor(方法拦截器)
我们要写一个类实现这个接口,并且实现这个接口的invoke方法
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return null;
}
可以在原始方法执行之前或之后运行
所以我们应该要先知道原始方法在什么时候运行,这样才可以让额外功能在原始方法之前或之后运行
MethodInvocation invocation:这个参数代表的就是额外功能所增加给的那个原始方法
这个方法就代表原始方法的执行
invocation.proceed();
方法的返回值Object代表原始方法的返回值,如果原始方法没有返回值,就认为返回的是null
配置文件配置
<bean id="arround" class="com.zyh.dynamic.Arround"></bean>
<!--代表所有方法都作为切入点 目前是login register-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
public class Arround implements MethodInterceptor {
/**
* 我们把额外功能写在invoke方法中,这样额外功能就可以运行在原始方法执行之前或之后
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("额外功能执行");
Object obj = invocation.proceed();
return obj;
}
}
运行效果
像事务的开启,提交这样的操作,我们就可以把它写在核心功能的前面和后面,在前面写事务的开启,在后面写事务的提交
额外功能如果是运行在原始方法抛出异常的时候呢,我们可以在try-catch里面写额外功能
3切入点详解
切入点决定了额外功能添加的位置
<!--代表所有方法都作为切入点 目前是login register-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
execution(* *(..))匹配了所有方法
execution是切入点函数
* *(..)是切入点表达式
3.1切入点表达式
3.1.1方法切入点表达式
* *(..)是切入点表达式 匹配了所有方法
接下来我们来看看,怎么把一个具体的方法定义成切入点
- 定义login方法作为切入点
<!--代表所有方法都作为切入点 目前是login register-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* login(..))"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
- 定义login方法并且login方法有两个字符串类型的参数作为切入点
<aop:config>
<aop:pointcut id="pc" expression="execution(* login(String,String ))"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
如果我们说切入点的参数类型和我们的方法类型不匹配,就不会执行额外功能
注意:如果参数类型不是java.lang包中的类型,在切入点中必须写全限定名称
上面讲解的方法切入点表达式不精准,因为不同的包可能有同名同参的方法,我们本来只是想要匹配其中的一个方法,但是现在就会导致匹配多个方法。
为了解决这个方法,我们就得通过包,类,方法名,参数来限定我们要选择的方法
切入点
<!--代表所有方法都作为切入点 目前是login register-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.zyh.proxy.UserServiceImpl.login())"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
3.2类切入点
如果一个类的所有方法都要加入额外功能的话,我们就可以把整个类作为切入点,这样就避免一个方法一个方法来配置了
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.zyh.proxy.UserServiceImpl.*(..))"/>
<!-- 组装,把切入点和额外功能整合-->
<aop:advisor advice-ref="arround" pointcut-ref="pc"></aop:advisor>
</aop:config>
注意:*只能套一层包,比如说类A写在com.zyh.proxy下面,那么这个时候,如果我们写的是*.UserServiceImpl.(…)是不可以的,因为*只能处理一层包
但是如果我们这个时候就想用*的话,要怎么办
那就写* . .UserServiceImpl.(…)注意:这里有两个.
两个.代表一级包乃至多级包
3.3包切入点
包切入点就是说把指定包作为额外功能加入的位置,那么,包的所有类以及方法都会加入额外功能
<aop:pointcut id="pc" expression="execution(* com.zyh.proxy.*.*(..))"/>
注意:如果使用这种方法的话,那么切入点包中的所有类必须放在proxy包下面,而不能是proxy包下的子包
如果我们想要把proxy包及其子包下的所有类及其方法作为切入点的话,要怎么办?
<aop:pointcut id="pc" expression="execution(* com.zyh.proxy..*.*(..))"/>
注意:com.zyh.proxy. . *.*(…)
proy后面有两个.代表proxy包及其子包
4.切入点函数
4.1execution,args,within切入点函数
切入点函数:用于执行切入点表达式
接下来看看有哪些主要的切入点函数
①execution
execution函数是最重要的切入点函数,功能最全面
它可以执行方法切入点表达式,类切入点表达式,包切入点表达式
缺点:execution执行切入点表达式书写麻烦
为了书写简单一点,因此就有其他切入点函数,它们的功能是一样的,只是为简化execution的复杂度
②args
主要用来方法参数的匹配
比如说方法是什么我们现在并不关心,只关心参数类型,这个时候就可以使用这个
execution(* (String ,String))
args(String,String)
③within
主要用于类,包切入点表达式的匹配
比如说我们选择切入点是UserServiceImpl,不关心它是哪一个包的
execution(* *. .UserServiceImpl.*(String ,String))
within(*. .UserServiceImpl)
如果是要把某一包及其子包下面的类和方法作为切入点
within(com.zyh.proxy. .*)
4.2@annotation
作用:为具有特殊注解的方法加入额外功能
我们以后可能为某一个方法来加入注解,那么这个时候,@annotaion就会进行扫描,如果扫描到某个注解,就可以为它进行切入
所以第一步我们要先自定义一个注解
4.3切入点函数的逻辑运算
切入点函数的逻辑运算指的是:整合多个切入点函数一起配合工作,进而完成更为复杂的需求
①and与操作
案例:login方法 同时参数是两个字符串
execution(* login(String,String))
execution(* login(..)) and args(String,String)
注意:与操作不能用于同种类型的切入点函数
②or或操作
execution(* login(..)) or execution(* register(..))
四、AOP
1.AOP概念
AOP(Aspect Oriented Programing):面向切面编程
面向切面编程就是以切面为基本单位的程序开发,通过切面间的彼此协同相互调用,完成程序的构建
切面=切入点+额外功能
- AOP的概念
- 本质上是Spring的动态代理开发,通过代理类为原始类增加额外功能
- 好处:有利于原始类的维护
2.AOP编程的开发步骤
①原始对象
②额外功能
③切入点
私组装切面(额外功能+切入点)
3.切面的名词解释
4.AOP底层实现原理
1.核心问题
①AOP怎么创建动态代理类(动态字节码技术)
②Spring工厂怎么加工创建代理对象
通过原始对象的id值,获得的是代理对象
2.JDK动态代理
package com.zyh.jdk;
import com.zyh.proxy.User;
import com.zyh.proxy.UserService;
import com.zyh.proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author zengyihong
* @create 2022--04--20 16:34
*/
public class TestJDKProxy {
public static void main(String[] args) {
//创建原始对象
final UserService userService=new UserServiceImpl();
//JDK动态代理
/**
* InvocationHandler:提供额外功能 额外功能其实是一个接口
*/
InvocationHandler handler=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("---proxy log-----");
//原始方法运行
Object obj = method.invoke(userService, args);
return obj;
}
};
UserService userServiceProxy= (UserService)Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
userServiceProxy.login("tom","123");
userServiceProxy.register(new User());
}
}
---proxy log-----
UserServiceImpl.login
---proxy log-----
UserServiceImpl.register 业务运算+DAO
3.CGlib动态代理
CGlib创建动态代理的原理:父子继承关系创建代理对象,原始类作为辅助,代理类作为子类,既可以保证二者方法一致,同时还可以在代理类中提供新的方法实现(额外功能+原始方法)
public class UserService {
public void login(String name,String password){
System.out.println("UserService.login");
}
public void register(User user){
System.out.println("UserService.register");
}
}
public class TestCglib {
public static void main(String[] args) {
//创建原始对象
final UserService userService = new UserService();
//CGlib方式创建动态代理对象
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(TestCglib.class.getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("-------cglib log---------");
Object obj = method.invoke(userService, args);
return obj;
}
};
enhancer.setCallback(interceptor);
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.login("tom","123");
userServiceProxy.register(new User());
}
}
JDK动态代理 Proxy.newProxyInstance() 通过接口插件代理的实现类
Cglib动态代理 Enhancer 通过继承父类创建的代理类
更多推荐
所有评论(0)