Spring框架学习05:代理模式和AOP

    技术2024-11-12  8

    八、静态/动态代理模式

    1.代理模式

    为什么要学习代理模式,因为AOP的底层机制就是动态代理!

    代理模式:

    静态代理动态代理

    学习aop之前 , 我们要先了解一下代理模式!

    2.静态代理

    静态代理角色分析

    抽象角色 : 一般使用接口或者抽象类来实现真实角色 : 被代理的角色代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .客户 : 使用代理角色来进行一些操作

    代码实现 :

    Rent.java 即抽象角色

    //抽象角色:租房 public interface Rent { public void rent(); }

    Host.java 即真实角色

    //真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }

    Proxy.java 即代理角色

    //代理角色:中介 public class Proxy implements Rent { private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } //租房 public void rent(){ seeHouse(); host.rent(); fare(); } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }

    Client.java 即客户

    //客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //房东要租房 Host host = new Host(); //中介帮助房东 Proxy proxy = new Proxy(host); //你去找中介! proxy.rent(); } }

    分析:

    ​ 在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情

    静态代理的优点和缺点:

    优点:

    可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .公共的业务由代理来完成 . 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .

    缺点 :

    类多了 , 多了代理类 , 工作量变大了 . 开发效率降低

    我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

    3.动态代理

    动态代理的角色和静态代理的一样 .

    动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

    动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    基于接口的动态代理----JDK动态代理基于类的动态代理–cglib现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist我们这里使用JDK的原生代码来实现,其余的道理都是一样的

    JDK的动态代理需要了解两个类

    ​ InvocationHandler 和 Proxy ,打开JDK帮助文档看看 。

    InvocationHandler:调用处理程序

    Object invoke(Object proxy, 方法 method, Object[] args)//参数 //proxy - 调用该方法的代理实例 //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。 //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

    Proxy : 代理

    //生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }

    代码实现:

    //抽象角色:租房 public interface Rent { public void rent(); } //真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } } public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); } // proxy : 代理类 method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } } //租客 public class Client { public static void main(String[] args) { //真实角色 Host host = new Host(); //代理实例的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setRent(host); //将真实角色放置进去! Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类! proxy.rent(); } }

    一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

    深化理解

    我们来使用动态代理实现代理我们后面写的UserService!

    我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

    public class ProxyInvocationHandler implements InvocationHandler { private Object target; public void setTarget(Object target) { this.target = target; } //生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } public void log(String methodName){ System.out.println("执行了"+methodName+"方法"); } }

    测试:

    public class Test { public static void main(String[] args) { //真实对象 UserServiceImpl userService = new UserServiceImpl(); //代理对象的调用处理程序 ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(userService); //设置要代理的对象 UserService proxy = (UserService)pih.getProxy(); //动态生成代理类! proxy.delete(); } }

    动态代理的好处:

    可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .公共的业务由代理来完成 . 实现了业务的分工 ,公共业务发生扩展时变得更加集中和方便 .一个动态代理 , 一般代理某一类业务一个动态代理可以代理多个类,代理的是接口

    九、AOP( 面向切面编程 )

    1.什么是AOP

    ​ AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率 。

    ​ 通俗讲, AOP思想就是把中间的非主流程的代码(比如:验证、事务管理、缓存、日志记录等), 横向抽取出来放在一个公共的类中, 也就是切面.可以通过Spring框架来配置该切面

    2. Aop在Spring中的作用

    提供声明式事务;允许用户自定义切面

    以下名词需要了解下:

    横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。目标(Target):被通知对象。代理(Proxy):向目标对象应用通知之后创建的对象。切入点(PointCut):切面通知 执行的 “地点”的定义。连接点(JointPoint):与切入点匹配的执行点。

    SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

    即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

    3. 使用Spring实现Aop

    使用AOP注入,需要导入一个依赖包

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>

    第一种方式 : 通过 Spring API 实现

    1、 首先编写我们的业务接口和实现类

    public interface UserService { public void add(); public void delete(); public void update(); public void search(); } public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }

    2、 写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

    public class Log implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } } public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }

    3、 去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.kuang.service.UserServiceImpl"/> <bean id="log" class="com.kuang.log.Log"/> <bean id="afterLog" class="com.kuang.log.AfterLog"/> <!--aop的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>

    4、 测试

    public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.search(); } }

    Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

    Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

    第二种方式: 自定义类来实现Aop

    1、目标业务类不变依旧是userServiceImpl

    2、 写我们自己的一个切入类

    public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }

    3、 spring中配置

    <!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.kuang.config.DiyPointcut"/> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <aop:aspect ref="diy"> <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <aop:before pointcut-ref="diyPonitcut" method="before"/> <aop:after pointcut-ref="diyPonitcut" method="after"/> </aop:aspect> </aop:config>

    4、测试

    public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) context.getBean("userService"); userService.add(); } }

    第三种方式:使用注解实现

    1、 编写一个注解实现的增强类

    package com.kuang.config; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class AnnotationPointcut { @Before("execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.kuang.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("签名:"+jp.getSignature()); //执行目标方法proceed Object proceed = jp.proceed(); System.out.println("环绕后"); System.out.println(proceed); } }

    2、 在Spring配置文件中,注册bean,并增加支持注解的配置

    <!--第三种方式:注解实现--> <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/> <aop:aspectj-autoproxy/>

    aop:aspectj-autoproxy说明 :

    ​ 通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了。

    ​ <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

    Processed: 0.044, SQL: 9