Spring-04-代理模式和AOP
八、AOP和代理模式
8.1 AOP介绍
-
什么是AOP?关键词:解耦
- 可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP可以看做是OOP的衍生
-
详细阐述
-
如图所示,一个常见的Web业务执行流程中
- 一个WEB业务,被分成了DAO层、Service层等。
- 这么做的好处就是明确了每一层的职责, 对于这一层的来说,我只需要做好关于我自己的事情就好了,然后把对象交给下一层。至于下一层会干什么和我没有关系。(高内聚,低耦合)
-
web业务的分层开发,可以看做是将一个业务流程,用刀切开,每一层之间就是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间也都是是一个个切面。
-
假设我们在某一层中,需要做这么一些处理:记录日志,安全性检验,权限验证等。那么对于这一层的的业务来说,也相当于被分成了这么几层
-
从这个流程图中可以看到,真正的业务处理中,执行业务的功能代码只占了一小部分,取余的都是一些附加功能代码。当这个这个业务多次重复执行的时候,这部分附加功能可能完全都是一样的,没有必要在重复写一遍代码。
-
我们可以将这些附加的功能全部抽取出来,让这个业务只专注于自己的业务处理。
-
就像下图展示的一样,我们将附加功能抽取出来,通过Spring将这些附加功能在注入到需要调用的地方,这样,业务就可以专心的处理自己的业务了,不需要关注其他功能的处理。这样,就可以算作一次简单的AOP
-
上面操作的流程,就叫做AOP。 附加功能的抽取就是切面,调用附加功能的地方就是切入点
-
-
8.2 代理模式
-
提到了AOP,就不得不提代理模式。这是因为Spring中AOP的底层实现就是基于代理模式实现的。所以掌握好代理模式的设计思想,是掌握AOP的基础
-
那么什么是代理模式?
- 代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色:
- Subject:抽象主题角色。可以是抽象类也可以是接口,是一个最普通的业务类型定义。
- RealSubject:具体主题角色,也就是被代理的对象,是业务逻辑的具体执行者。
- Proxy:代理主题角色。负责读具体主题角色的引用,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作。
- 现实生活中的演员就是一个很好的例子
- 一般来说,演员主要的工作就是演戏,其他的事情就交给了经纪人去做。让经纪人来负责安排演员演什么戏,拿多少钱,安排档期等等除了演戏之外的琐事
- 对于这个例子。演员的主要业务就是演戏,那么演戏就是Subject;处理各种大事小事的经纪人就是Proxy;负责演戏的演员就是RealSubject
- 代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色:
-
代理模式的优缺点
- 优点:分工明确,低耦合,扩展性强
- 缺点:加入代理会导致性能的降低,实现代理需要额外的工作
-
代理模式的应用场景
- 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
-
如何实现代理模式?
- 增加一个中间层,实现与被代理类的组合
8.3 静态代理
-
定义一个最普通的业务类型,Subject
public interface Actor { public void act(); }
-
定义一个实现了业务的具体执行者,也就是RealSubject
public class Star implements Actor { private String name; public Star(String name) { this.name = name; } @Override public void act() { System.out.println(name + "来演戏"); } }
-
定义代理类,也就是Proxy,代理类负责其他和演戏无关的事情的处理
public class Agent implements Actor { private Star star; public void setStar(Star star) { this.star = star; } private void arrange() { System.out.println("安排档期"); } private void signContact() { System.out.println("签约"); } private void paid() { System.out.println("获取片酬"); } @Override public void act() { arrange(); signContact(); star.act(); paid(); } }
-
运行测试
@Test public void test01() { Agent agent = new Agent(); agent.setStar(new Star("刘德华")); agent.act(); }
-
小结:
-
代理类Agent中的一些处理方法通常会设置成对外不可见的。
-
当外界需要找演员来演戏的时候,就只需要把演员丢给Agent,然后Agent会来安排其他的事情。
-
虽然看上去是调用了Agent的act方法,但是最后关于act的实现还是是用了Star类中的act的实现。
-
所以对于剧组(使用者)看不出来是Agent,所以说Agent是Star的代理类
-
8.4 动态代理
-
既然有静态代理,那么就势必会有动态代理
-
为什么会出现动态代理?
- 对于静态代理来说,代理的是一个RealSubject。我们继续沿用静态代理的情景。
- 我们先横向扩展业务。以前只有Star去演戏,这个时候我们有了Dancer可以去跳舞,有了Joker去表演喜剧,等一系列的扩展。
- 这个时候我们可以看出,由于我们的Agent代理的角色是在代码中写死的Star,那么我们想要代理Dancer和Joker就势必要重新编写他们的代理类,
- 这意味着什么?这意味着每有一个新的实现类被创建,静态代理类就要相应的增加一个。这样就大大增加了工作量
- 那么能不能将代理类设计成代理的是一个
Interface
,这样对于相同接口的不同实现类,就不要重新在编写一个代理类了。 - 在这样的需求下,动态代理出现了
- 对于静态代理来说,代理的是一个RealSubject。我们继续沿用静态代理的情景。
-
动态代理和静态代理的区别
-
静态代理,代理的是一个实现了具体业务的类;而动态代理,代理的是一个接口,所有实现了这个接口的类都可以丢给动态代理
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
-
基于接口的动态代理----JDK动态代理
-
基于类的动态代理--cglib
-
现在用的比较多的是 javasist 来生成动态代理
-
实现基于接口的动态代理类
-
必要的知识
-
接口
InvocationHandler
是由代理实例的调用处理程序实现的接口。- 每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,对该方法调用进行编码,并将其分派到其调用处理程序的invoke方法。
-
类
Proxy
提供静态方法来创建对象,这些对象的行为类似于接口实例,但允许自定义方法调用。- Proxy类是在运行时创建的类,该类实现指定的接口列表(称为代理接口)。
- 代理实例是Proxy类的实例。每个代理实例都有一个关联的调用处理程序对象,该对象实现接口InvocationHandler。调用处理程序将适当地处理编码的方法调用,并且返回的结果将作为代理实例上方法调用的结果返回。
-
-
定义一个普通的业务,这里沿用静态代理的Subject
public interface Actor { public void act(); }
-
实现这个业务的具体实现类,Star,Dancer,Joker
public class Star implements Actor { private String name; public Star(String name) { this.name = name; } @Override public void act() { System.out.println(name + "来演戏"); } }
public class Dancer implements Actor { private String name; public Dancer(String name) { this.name = name; } @Override public void act() { System.out.println(name + "跳舞"); } }
public class Joker implements Actor { private String name; public Joker(String name) { this.name = name; } @Override public void act() { System.out.println(name + "表演喜剧"); } }
-
实现动态代理
package com.pbx.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author BruceXu * @date 2020/11/22 */ public class DynamicAgent implements InvocationHandler { private Actor actor; public void setActor(Actor actor) { this.actor = actor; } public Actor getActor() { return (Actor) Proxy.newProxyInstance(this.getClass().getClassLoader(), actor.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { arrange(); signContact(); Object res = method.invoke(actor, args); paid(); return res; } private void arrange() { System.out.println("安排档期"); } private void signContact() { System.out.println("签约"); } private void paid() { System.out.println("获取片酬"); } }
- 对于这个动态代理类,我们并没有想创建静态代理一样去创建一个动态代理的类。我们实际编写的是一个动态处理器,真正的代理对象,在运行时由JDK通过反射去创建出来
-
测试
@Test public void test01() { DynamicAgent agent = new DynamicAgent(); agent.setActor(new Dancer("芭蕾舞演员")); Actor proxiedActor = agent.getProxy(); proxiedActor.act(); System.out.println("==========================="); agent.setActor(new Star("芭蕾舞演员")); proxiedActor = agent.getProxy(); proxiedActor.act(); System.out.println("==========================="); agent.setActor(new Joker("芭蕾舞演员")); proxiedActor = agent.getProxy(); proxiedActor.act(); }
8.5 AOP和代理模式之间的关系
-
AOP和代理模式都是为了解决同一个问题
- 在不破坏原始代码的情况下,对已有代码进扩展,增加新功能
- 或者对原有代码进行解耦,降低业务处理时各部分的关联度
-
AOP的底层通过代理模式实现,了解代理模式的工作方式,对于学习AOP很有帮助
九、AOP
9.1 AOP概述
-
什么是AOP?关键词:解耦
- 可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP可以看做是OOP的衍生
-
详细阐述
-
如图所示,一个常见的Web业务执行流程中
- 一个WEB业务,被分成了DAO层、Service层等。
- 这么做的好处就是明确了每一层的职责, 对于这一层的来说,我只需要做好关于我自己的事情就好了,然后把对象交给下一层。至于下一层会干什么和我没有关系。(高内聚,低耦合)
-
web业务的分层开发,可以看做是将一个业务流程,用刀切开,每一层之间就是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间也都是是一个个切面。
-
假设我们在某一层中,需要做这么一些处理:记录日志,安全性检验,权限验证等。那么对于这一层的的业务来说,也相当于被分成了这么几层
-
从这个流程图中可以看到,真正的业务处理中,执行业务的功能代码只占了一小部分,取余的都是一些附加功能代码。当这个这个业务多次重复执行的时候,这部分附加功能可能完全都是一样的,没有必要在重复写一遍代码。
-
我们可以将这些附加的功能全部抽取出来,让这个业务只专注于自己的业务处理。
-
就像下图展示的一样,我们将附加功能抽取出来,通过Spring将这些附加功能在注入到需要调用的地方,这样,业务就可以专心的处理自己的业务了,不需要关注其他功能的处理。这样,就可以算作一次简单的AOP
-
上面操作的流程,就叫做AOP。 附加功能的抽取就是切面,调用附加功能的地方就是切入点
-
-
9.2 Spring中的AOP
-
Spring提供了声明式事务,同时支持用户自定义切面
-
Spring中AOP的一些概念,(了解就好了,开发的时候用不到)
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ....
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
-
下面这张图很好的说明了各种概念到底是什么玩意儿
-
SpringAOP中支持的5中Advice类型
9.3 在Spring中实现AOP
- 在Spring中,有3种不同实现AOP的方式
- 利用Spring中自带的Advice API去实现advice
- 自定义切面类,在切面类中实现各种advice
- 使用注解开发
方式一:使用Spring API
-
定义我们的业务接口和我们的业务实现类
-
业务接口
public interface UserService { void add(); void delete(); void update(); void select(); }
-
业务实现类
public class UserServiceImpl implements UserService { @Override public void add() { System.out.println("UserServiceImpl::add()"); } @Override public void delete() { System.out.println("UserServiceImpl::delete()"); } @Override public void update() { System.out.println("UserServiceImpl::update()"); } @Override public void select() { System.out.println("UserServiceImpl::select()"); } }
-
-
根据需求实现advice
-
实现一个前置通知
package com.pbx.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * @author BruceXu * @date 2020/11/24 */ public class beforeLog implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("在" + o.getClass().getName() + "::" + method.getName() + "执行前"); } }
-
实现一个后置通知
package com.pbx.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; /** * @author BruceXu * @date 2020/11/24 */ public class afterLog implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("在" + target.getClass().getName() + "::" + method.getName() + "执行后,结果为:" + returnValue); } }
-
-
在Spring配置文件中将advice和aspect相关联
<!--记得提前导入aop的约束--> <!--定义bean--> <bean id="userService" class="com.pbx.service.UserServiceImpl"/> <bean id="beforeAdvice" class="com.pbx.advice.beforeLog"/> <bean id="afterAdvice" class="com.pbx.advice.afterLog"/> <!--配置aop--> <aop:config> <aop:pointcut id="point1" expression="execution(* com.pbx.service.UserServiceImpl.*(..))"/> <aop:advisor advice-ref="beforeAdvice" pointcut-ref="point1"/> <aop:advisor advice-ref="afterAdvice" pointcut-ref="point1"/> </aop:config>
-
测试
@Test public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("aop01.xml"); UserService userService = context.getBean("userService", UserService.class); userService.add(); }
方式二:自定义切面类
-
沿用之前的业务接口和实现类
-
自定义切面类
public class MyAspect { public void before() { System.out.println("---- before advice ----"); } public void after() { System.out.println("---- after advice ----"); } }
-
配置AOP
<!--定义bean--> <bean id="MyAspect" class="com.pbx.aspect.MyAspect"/> <bean id="userService" class="com.pbx.service.UserServiceImpl"/> <!--配置AOP--> <aop:config> <aop:aspect ref="MyAspect"> <aop:pointcut id="point1" expression="execution(* com.pbx.service.UserServiceImpl.*(..))"/> <aop:before method="before" pointcut-ref="point1"/> <aop:after method="after" pointcut-ref="point1"/> </aop:aspect> </aop:config>
-
测试
@Test public void test02() { ApplicationContext context = new ClassPathXmlApplicationContext("aop02.xml"); UserService userService = context.getBean("userService", UserService.class); userService.add(); }
方式三:注解实现aop
-
实现一个使用注解的增强类
package com.pbx.aspect; 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; /** * @author BruceXu * @date 2020/11/24 */ @Aspect public class MyAspect2 { @Before("execution(* com.pbx.service.UserServiceImpl.*(..))") public void before() { System.out.println("---- before advice ----"); } @After("execution(* com.pbx.service.UserServiceImpl.*(..))") public void after() { System.out.println("---- after advice ----"); } @Around("execution(* com.pbx.service.UserServiceImpl.*(..))") public Object surround(ProceedingJoinPoint point) throws Throwable { System.out.println("---- before around ----"); System.out.println("方法签名为:"+point.getSignature()); Object proceed = point.proceed(); System.out.println("---- after around ----"); return proceed; } }
-
配置Spring
<!--注册bean--> <bean id="userService3" class="com.pbx.service.UserServiceImpl"/> <bean id="Aspect2" class="com.pbx.aspect.MyAspect2"/> <!--自动代理--> <aop:aspectj-autoproxy/>
-
测试
@Test public void test03() { ApplicationContext context = new ClassPathXmlApplicationContext("aop03.xml"); UserService userService = context.getBean("userService3", UserService.class); userService.add(); }