- 一、静态代理设计模式
- 1.为什么需要代理设计模式
- 1.1回顾MVC和三层架构
- 1.2为什么需要代理
- 2.代理设计模式
- 2.1概念
- 2.2开发的核心要素
- 2.3实战
- 2.4静态代理存在的问题
- 二、动态代理
- 1.Spring动态代理的概念
- 2.开发环境
- 3.Spring动态代理的开发步骤
- 4.动态代理细节分析
- 三、Spring动态代理详解
- 1.MethodBeforeAdvice详解
- 2.MethodInterceptor(方法拦截器)
- 3切入点详解
- 3.1切入点表达式
- 3.1.1方法切入点表达式
- 3.2类切入点
- 3.3包切入点
- 4.切入点函数
- 4.1execution,args,within切入点函数
- 4.2@annotation
- 4.3切入点函数的逻辑运算
- 四、AOP
- 1.AOP概念
- 2.AOP编程的开发步骤
- 3.切面的名词解释
- 4.AOP底层实现原理
- 1.核心问题
- 2.JDK动态代理
- 3.CGlib动态代理
我们先来回顾一下MVC
MVC:Model 模型 比如实体类 ,View 视图 比如JSP页面,Controller 控制器 比如Servlet
早期的三层架构
用户直接访问控制层,控制层就可以直接操作数据库
servlet–对数据进行增删改查
缺点:程序臃肿,不利于维护
Servlet的代码中:处理请求,响应,视图跳转,处理JDBC,处理业务代码,处理逻辑代码
因为上面的架构,分工不够明确,所以就演变成下面这样的架构
Model:
View
Controller
然后来简单回顾一个开发中的分层
DAO—>Service—>Controler
DAO:数据访问层,主要用来对数据库的数据进行操作
Service:业务层,用来处理业务逻辑
Controler:控制器
这里我推荐两篇关于MVC和三层架构的文章,我个人觉得讲解得很详细
浅谈 MVC与三层架构
MVC与三层架构理解
Service层中主要是用来处理业务的,也就是核心功能,像那些业务逻辑,访问数据库的操作等。像那些事务,日志,性能等操作,属于附加功能,可有可无,代码量也比较少
那么额外功能写在Service层中好不好呢
接下来举一个现实生活中的例子来说明一下生活中也会有类似场景
比如说有人想要租房子,那么我们可以把房东看成一个类,代表业务类,包含核心功能也就是和房客签订出租房屋的合同,但是它肯定不能只有核心功能,别人为什么要找你租房,而不是找其他人呢?可能是因为你张贴广告,并且房客来看过房子了,所以还需要有额外功能,贴广告和看房。但是我们来想一下,贴广告这些其实挺累的,房东肯定不想做这些事情,他们希望的是房客直接来找他们签合同租房,也就是不希望额外功能。但是房客又不允许房东没有额外功能,毕竟没有看到房子怎么样,房客怎么可能直接签订合同
我们肯定是尽量满足所有人的要求的,所以我们以后就跟房东说,你只要实现核心功能就可以了,其他的不用你管。但是房客就很迷糊了,我都不知道谁有房子出租,而且没有看房怎么签合同。这个时候,中介(Proxy)出现了,中介说,大家放心,我负责提供房子信息,房客以后就只需要找中介就可以了,房客看到房子后觉得很满意,想要签订合同,但是这个时候,这个功能就不属于中介管了,这就需要房东了,毕竟房产证又不在你中介手里,房客说,我怎么知道这个房子是你的,万一不是你的,我的钱不就打水漂了,所以到签合同的这个时候,中介就会说,房东你来吧,这样子房客的诉求也满足了,皆大欢喜了。
如果说,房客对中介提供的房源不满意,那么他可以找其他的中介,这样子,我们发现,我们并不需要修改代码,这样就提高了代码的维护性
代理类=目标类+额外功能+目标类和代理类要实现相同的接口(为了让代理类和目标类的方法保持同步)
我们以租房子为例,来进行演示,下面这个演示是静态代理
静态代理:为每一个目标类,都提供一个代理类
提供代理类和目标类的公共接口
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;
}
}
}
动态代理和静态代理在概念上没有什么区别,主要是开发步骤和底层实现的不同
<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.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>spring-01artifactId>
<version>1.0-SNAPSHOTversion>
<name>spring-01name>
<url>http://www.example.comurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.7maven.compiler.source>
<maven.compiler.target>1.7maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.18version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.18version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.21version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.18version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.6version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.9.8version>
<scope>runtimescope>
dependency>
dependencies>
project>
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>
③定义切入点
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
aop:config>
④组装
把第二步和第三步进行整合
为了看清楚一点,我把上面的配置都在这里体现
<bean id="userServiceImpl" class="com.zyh.proxy.UserServiceImpl">bean>
<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
Spring创建的动态代理类在哪里?
什么又叫动态字节码技术
总结一下
动态代理编程简化代理的开发
动态代理额外功能的维护性大大增强
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("----method before advice log---");
}
MethodBeforeAdvice接口作用:额外功能运行在原始方法执行前进行额外功能操作的话,就得实现这个接口
我们要写一个类实现这个接口,并且实现这个接口的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>
<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里面写额外功能
切入点决定了额外功能添加的位置
<aop:config>
<aop:pointcut id="pc" expression="execution(* *(..))"/>
<aop:advisor advice-ref="arround" pointcut-ref="pc">aop:advisor>
aop:config>
execution(* *(..))匹配了所有方法
execution是切入点函数
* *(..)是切入点表达式
* *(..)是切入点表达式 匹配了所有方法
<aop:config>
<aop:pointcut id="pc" expression="execution(* login(..))"/>
<aop:advisor advice-ref="arround" pointcut-ref="pc">aop:advisor>
aop:config>
<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包中的类型,在切入点中必须写全限定名称
上面讲解的方法切入点表达式不精准,因为不同的包可能有同名同参的方法,我们本来只是想要匹配其中的一个方法,但是现在就会导致匹配多个方法。
为了解决这个方法,我们就得通过包,类,方法名,参数来限定我们要选择的方法
切入点
<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>
如果一个类的所有方法都要加入额外功能的话,我们就可以把整个类作为切入点,这样就避免一个方法一个方法来配置了
<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.(…)注意:这里有两个.
两个.代表一级包乃至多级包
包切入点就是说把指定包作为额外功能加入的位置,那么,包的所有类以及方法都会加入额外功能
<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包及其子包
切入点函数:用于执行切入点表达式
接下来看看有哪些主要的切入点函数
①execution
execution函数是最重要的切入点函数,功能最全面
它可以执行方法切入点表达式,类切入点表达式,包切入点表达式
缺点:execution执行切入点表达式书写麻烦
为了书写简单一点,因此就有其他切入点函数,它们的功能是一样的,只是为简化execution的复杂度
②args
主要用来方法参数的匹配
比如说方法是什么我们现在并不关心,只关心参数类型,这个时候就可以使用这个
execution(* (String ,String))
args(String,String)
③within
主要用于类,包切入点表达式的匹配
比如说我们选择切入点是UserServiceImpl,不关心它是哪一个包的
execution(* *. .UserServiceImpl.*(String ,String))
within(*. .UserServiceImpl)
如果是要把某一包及其子包下面的类和方法作为切入点
within(com.zyh.proxy. .*)
作用:为具有特殊注解的方法加入额外功能
我们以后可能为某一个方法来加入注解,那么这个时候,@annotaion就会进行扫描,如果扫描到某个注解,就可以为它进行切入
所以第一步我们要先自定义一个注解
切入点函数的逻辑运算指的是:整合多个切入点函数一起配合工作,进而完成更为复杂的需求
①and与操作
案例:login方法 同时参数是两个字符串
execution(* login(String,String))
execution(* login(..)) and args(String,String)
注意:与操作不能用于同种类型的切入点函数
②or或操作
execution(* login(..)) or execution(* register(..))
AOP(Aspect Oriented Programing):面向切面编程
面向切面编程就是以切面为基本单位的程序开发,通过切面间的彼此协同相互调用,完成程序的构建
切面=切入点+额外功能
①原始对象
②额外功能
③切入点
私组装切面(额外功能+切入点)
①AOP怎么创建动态代理类(动态字节码技术)
②Spring工厂怎么加工创建代理对象
通过原始对象的id值,获得的是代理对象
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
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 通过继承父类创建的代理类