马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
ok了家人们,继续学习spring ,这段知识点有点绕,建议搭配b站的视频去学,passion!!!
八.AOP-面向切面编程
8.1 动态署理
8.1.1 概述
什么是署理?在现实生活中,署理很常见。比如,我们必要去 某个地方,必要自己开车已往,但是公交车师傅的出现替代了 我们自己开车的这个步骤,我们只必要乘坐公交车就可以到达 我们必要到达的目标地。这就是一种署理方式。 用自己的话总结一下:署理就是原本必要自己去完成的工作可 以由一个新的替代者来完成,这就是署理的一种表现。 在 Java 中就是在不侵入原来代码的情况下完成功能的增强。 Java 中常见署理分类:静态署理,基于 JDK 的动态署理,基于 CGlib 的动态署理 8.1.2 静态署理
举个例子:现实中明星都是有经纪人的,经纪人的作用就是帮 明星来处置惩罚业务,节目组要找明星来表演,那么肯定不是和明 星直接接洽的,而是明星的经纪人出头和节目组洽谈,而且只 有当业务条件满足时,明星才会出头表演,那么这个经纪人就 是明星的署理人,他可以帮助明星来筛选演出业务,条件达不 到要求的表演就过滤了,只有达到要求了,才会被经纪人接收 然后明星就会出场表演,也就是拦截了外界对明星的直接访 } 问。这也是 JAVA 中署理对象的作用,产生一个署理对象用来 拦截外界对真实业务的访问。 可以看出,经纪人会将外界给明星的信息进行拦截、处置惩罚,这 就是我们常说的署理模式。 静态署理的实现方式
- 经纪人和明星在一些行为上有共同点,共同的目标,以是 定义一个共有接口: start 接口 ( 参加节目 ) 。
- 明星实现接口的行为,因为真正出力的是明星。
- 经纪人要代表明星,就必要和明星有同样的行为,同时持 有明星的引用 ( 经纪人能找到明星 ) 。
- 调用方要找明星之前,先找到经纪人。由经纪人出头进行 谈判,比如参加节目前片酬,比如参加节目后发的微博。
静态署理的代码实现
- public interface FindWife {
- public void findWife(String message);
- }
复制代码
- public class BuyHouseProxy implements BuyHouse{
- //定义一个 有买房需求的
- private BuyHouse buyHouse;
- //在创建代理对象的时候 得有委托人
- public BuyHouseProxy(BuyHouse buyHouse) {
- this.buyHouse = buyHouse;
- }
- @Override
- public void buyHouse() {
- //代理人 帮助 委托人
- System.out.println("我是中介,帮您买房,先付款...");
- buyHouse.buyHouse();//最终 委托人买方法
- System.out.println("将信息偷偷滴卖给装修的人...");
- }
- }
复制代码
- package com.cjx.service;
- //买房的中介 代理
- public class BuyHouseProxy implements BuyHouse{
- //定义一个 有买房需求的
- private BuyHouse buyHouse;
- public BuyHouseProxy(BuyHouse buyHouse) {
- this.buyHouse = buyHouse;
- }
- @Override
- public void buyHouse() {
- //代理人 帮助 委托人
- System.out.println("我是中介,帮您买房,先付款...");
- buyHouse.buyHouse();//最终 委托人买方法
- System.out.println("将信息偷偷滴卖给装修的人...");
- }
- }
复制代码- package com.cjx.service;
- //婚介中心
- public class FindWifeProxy implements FindWife{
- //定义一个 有找媳妇需求的人
- private FindWife findWife;
- public FindWifeProxy(FindWife findWife) {
- this.findWife = findWife;
- }
- @Override
- public void findWife(String message) {
- System.out.println("我们这里是百合网,交会员费,提供优质服务,按照您的条件找");
- findWife.findWife(message);
- System.out.println("祝你幸福...");
- System.out.println("又把信息卖给了婚纱摄影");
- }
- }
复制代码
- package com.cjx.service;
- //消费者 委托人
- public class Customer implements BuyHouse,FindWife{
- @Override
- public void buyHouse() {
- System.out.println("看房源...");
- System.out.println("谈价格...");
- System.out.println("过户...");
- }
- @Override
- public void findWife(String message) {
- System.out.println("要求是:"+message);
- System.out.println("主要是暖被窝...");
- }
- }
复制代码
- package com.cjx.test;
- import com.cjx.service.*;
- import org.junit.Test;
- public class DemoTest {
- @Test
- public void test01(){
- //没有代理的时候 自己买方房
- Customer customer = new Customer();
- customer.buyHouse();
- customer.findWife("xx");
- System.out.println("-------------------");
- //找中介 代理人
- BuyHouseProxy buyHouseProxy = new BuyHouseProxy(customer);
- buyHouseProxy.buyHouse();
- System.out.println("-------------------");
- //找媳妇 代理人
- FindWifeProxy findWifeProxy = new FindWifeProxy(customer);
- findWifeProxy.findWife("迪丽热巴..。");
- System.out.println("-------------------");
- // 动态代理
- BuyHouse proxy = (BuyHouse) JDKProxy.getProxy(customer);
- proxy.buyHouse();
- }
- }
复制代码 从实现上可以看到 署理 的主要作用是 方法增强,它可以在不 “ 惊动 ” 被署理类的情况下修改被署理类的行为。这有助于体系 解耦。我们这里署理类和被署理类都是自己亲身敲好的,即在 步伐运行前署理类的 .class 文件就已经存在了的形式叫做静态 署理。值得留意的是,署理类和被署理类应该共同实现一个接 口,或者是共同继承某个类。 静态署理的缺点
- 我们必要在运行前手动创建署理类,这意味着如果有很多 署理的话会很繁琐,一个被署理就必要一个署理;
- 其次署理类 和 被署理类 必须实现同样的接口,万一接口 有变更,署理、被署理类都得修改,容易出题目;
8.1.3 动态署理
动态署理 与 静态署理 最大的区别就是不消我们创建那么多 类,敲那么多代码。在步伐运行时,运用反射机制动态创建而 成。 JDK 中为我们提供了 Proxy 类来实现动态署理,其中最紧张的 方法是 Proxy.newProxyInstance :只必要我们传入相干信 息,它就可以返回我们必要的署理对象。 Object obj = Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) 上面的方法可以实现动态的拦截被署理类的方法,也就是说我 们无需定义署理类,由该方法自动产生可以实当署理的对象。 - package com.cjx.service;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- public class JDKProxy {
- //获取代理对象的方法 参数:被代理对象 委托人
- //传入被代理人 返回一个代理对象
- public static Object getProxy(Object obj) {
- Object proxy = null;
- proxy = Proxy.newProxyInstance(
- obj.getClass().getClassLoader(),
- //类加载器,将生成的代理类加载到内存中
- obj.getClass().getInterfaces(),
- // 生成的代理类需要实现的接口的字节码
- //invoke:此invoke方法并非是反射的invoke方法,此方法属于动态代理,
- //调用代理类的哪个方法,invoke就表示那个方法
- new InvocationHandler() {
- /*
- proxy:代理对象
- method:委托人需要增强的方法
- args:要实现功能的参数
- Object:被增强方法的返回值
- */
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws
- Throwable {
- Object result = null;
- //这个方法来自哪个接口定义的方法
- Class<?> clazz =
- method.getDeclaringClass();
- if (clazz ==
- BuyHouse.class) {
- //我们应该根据委托人不同的需求,执行不同的功能增强
- String methodName =
- method.getName();
- if
- (methodName.equalsIgnoreCase("buyHouse")) {
- //对买房的操作进行增强
- System.out.println("我是中介,帮您买房,先付款...");
- result =
- method.invoke(obj, args);
- System.out.println("将信息偷偷滴卖给装修的人...");
- }
- }
- if (clazz == FindWife.class) {
- //我们应该根据委托人不同的需求,执行不同的功能增强
- String methodName = method.getName();
- if (methodName.equalsIgnoreCase("findWife")) {
- System.out.println("我们这里是百合网,交会员费,提供优质服务,按照您的条件找");
- result = method.invoke(obj, args);
- System.out.println("又把信息卖给了婚纱摄影");
- }
- }
- return result;
- }
- }
- );
- return proxy;
- }
- }
复制代码 明星与经纪人的关系先容了静态署理,欠好之处在于一个经纪 人只能署理一个明星,一旦明星有变更,或者想要署理其他明 星时,必要修改、创建经纪人,大量使用这种静态署理,会使 我们体系内的类的规模增大,并且不易维护; 而动态署理模式,大大减少类的创建、修改本钱。此外动态代 理还符合 AOP ( 面向切面编程 ) 头脑,在很多场所都有使用。 通过讲授可以得出结论 : 署理模式主要用于扩展原功能又不侵 入(修改)源代码。 ( 增强功能 ) 8.1.4 cglib署理
JDK 的动态署理已经很好的帮我们完成了署理工作,那么, CGlib 动态署理又有什么好用的特征呢? 我们可以看出,静态署理和基于 JDK 的动态署理的被署理类都 必要实现接口,那么,如果我的类没有实现接口,但是,我想 给它设置署理,那么,应该怎么实现呢? 这时候,就轮到 CGlib 出场了, CGlib 可以署理没有接口的类。 我们必要在 pom 文件中引入 CGlib 的 jar 包。 - <!--https://mvnrepository.com/artifact/cglib/cglib -->
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>3.2.9</version>
- </dependency>
复制代码 cglib 动态署理其实就是把原有对象传进去进行方法拦截,拦 截到之后进行逻辑增强。 8.1.5 署理总结
使用署理技能就是为了帮我们在不入侵原有代码的情况下增强 业务逻辑! 你完全可以使用静态署理一个一个去定义署理类,但是如许的 话太过于繁琐,而且有些情况下你不知道未来会有什么接口 (比如咱们的 Mybatis ,你现在有个 UserMapper.java ,以后 还可能有更多其他的 Mapper 接口,这些都是不确定的),所 以就是用动态署理去给他们天生署理对象吧 有接口就用 JDK 动态署理,没有接口就用 CGLIB 动态署理 8.2 AOP概述
OP 为 Aspect Oriented Programming 的缩写,意为:面向切 面编程,通过 运行期动态 实现步伐功能的统一维护的一种技 术。 AOP 是 OOP 的延续,是软件开发中的一个热门,也是 Spring 框架中的一个紧张内容。 AOP 弥补了 OOP 的不足,基于 OOP 底子上进行横向开发; OOP 步伐开发以类为主题模子,一切围绕对象进行,通过构 建模子来完成任务; AOP 步伐的开发主要关注 OOP 开发中的 共性功能,一切围绕共性功能进行。 AOP 通过接纳横向抽取机制,已经完全代替了传统纵向继承体 系重复性代码的编写方式 AOP 的底层封装了动态署理,我们可以理解为 AOP 是动态署理 的一种简单写法,进行功能的增强! 简单理解: AOP 就是实现业务代码和逻辑代码的解耦,但在运 行时又可以织入在一起。 场景 名称 | 场景描述 | 变乱控制 | 批量的为业务对象,增加变乱处置惩罚的功能,不消 每个对象都去办变乱处置惩罚 | 记录跟踪 | 批量的为业务对象,增加日记记录的功能,不消 在每个方法执行进行日记记录 | 权限控制 | 批量的为业务对象,增加权限查抄,不消每个业 务对象都做安全查抄 | 异常处置惩罚 | 批量的为业务对象,增加异常情况的处置惩罚功能, 不消在每个方法执行进行异常处置惩罚 | 参数校验 | 批量的为业务对象,增加各种入参的查抄,不消 在方法内部进行检 | 等等 | ..... | 8.3 AOP 相干概念
- Joinpoint( 连接点 ) : 所谓连接点是指那些被拦截到的点。 在 spring 中 , 这些点指的是方法 , 因为 spring 只支持方法范例 的连接点。
- Pointcut( 切入点 ) : 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。比方:我们设置 save 方法就是切入点
- Advice( 关照 / 增强 ) : 所谓关照是指拦截到 Joinpoint 之后所 要做的事情就是关照。 关照的范例:前置关照 , 后置关照 , 异 常关照 , 最终关照 , 环绕关照。 比方: @Before 前置关照 : 在 切入点运行前执行。
- Target( 目标对象 ) : 署理的目标对象。举例: UserServiceImpl ,他就是目标对象,因为它里面的功能需 要被增强。
- Weaving( 织入 ) : 就是代码运行的时候 业务代码和逻辑代码 织入到一起执行。比方:把 before 方法在 save 方法执行之 前执行了这就是织入。
- Aspect( 切面 ) : 是切入点和关照的结合。切面 = 切入点 + 方位 信息 + 增强逻辑,比方: AopAdvice 类 是一个切面类
- Proxy(署理): 一个类被AOP织入增强后,就产生一个结果 署理类。比方: AccountServiceImpl 它的功能在增强的时 候,现实是天生了署理对象增强的。
8.4 AOP案例
8.4.1 需求分析
需求:任意业务层接口执行前后获取当前体系时间 分析:
- 原始步伐中将共性功能抽取出来独立制作成方法放在特定类中
- 定义带有共性功能的方法名
- 将抽取出来的共性功能与对应的方法名之间绑定关系
8.4.2 情况准备
- package com.cjx.pojo;
- import org.springframework.stereotype.Component;
- @Component
- public class User {
- private int id;
- private String name;
- //省略get set方法
- }
复制代码
- package com.cjx.config;
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.EnableAspectJAutoProxy;
- //Spring配置文件
- @Configuration
- @ComponentScan("com.cjx")
- @EnableAspectJAutoProxy
- public class SpringConfig {
- }
复制代码
- public class AopAdvice {
- public void before(){
- System.out.println("当前方法执行前初始时间毫
- 秒值是:"+System.currentTimeMillis());
- }
- public void after(){
- System.out.println("当前方法执行后初始时间毫
- 秒值是:"+System.currentTimeMillis());
- }
- }
复制代码
- package com.cjx.service;
- import com.cjx.pojo.User;
- import org.springframework.stereotype.Service;
- import java.util.List;
- @Service
- public class UserServiceImpl implements UserService{
- @Override
- public void save(User user) {
- //System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());
- System.out.println("保存了用户数据~");
- //System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());
- }
- @Override
- public void update(User user) {
- //System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());
- System.out.println("更新了用户数据~");
- //System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());
- }
- @Override
- public void delete(String id) {
- System.out.println("删除了用户数据~");
- }
- @Override
- public List<User> findAll() {
- System.out.println("查询了用户数据~");
- return null;
- }
- }
复制代码 - @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(classes =
- {SpringConfig.class})
- public class DemoTest {
- @Autowired
- private UserService userService;
- @Test
- public void test01(){
- userService.save(new User());
- }
- }
复制代码 8.4.3 AOP实现
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>springaspects</artifactId>
- <version>5.2.10.RELEASE</version>
- </dependency>
复制代码
- 将共性功能抽取出来制作成独立的关照方法写入关照类 中。创建一个新的类,将共性功能提取出来制作成独立的 方法
- /*
- 通知类
- */
- public class AopAdvice {
- public void before(){
- System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());
- }
- public void after(){
- System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());
- }
- }
复制代码
- /*
- 通知类
- 切入点:@Pointcut
- 你想增强哪个方法,那个方法就当做切入点,切入点在通知
- 类中这样配置
- void
- com.lzw.service.UserServiceImpl.save(com.lzw.poj
- o.User)
- */
- public class AopAdvice {
- //配置切入点 是用来找到需要被增强的方法的
- @Pointcut("execution(void
- com.lzw.service.UserServiceImpl.save(com.lzw.poj
- o.User))")
- public void pt(){
- }
- }
复制代码
- package com.cjx.aop;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.stereotype.Component;
- import java.util.Date;
- /*
- 通知类
- 切入点:@Pointcut
- 你想增强哪个方法,那个方法就当做切入点,切入点在通知
- 类中这样配置
- void
- com.lzw.service.UserServiceImpl.
- save(com.lzw.pojo.User)
- @Component 我这个通知类交给spring管理
- @Aspect 当前类为切面类 里面包含 切入点+方位信息
- +增强逻辑 目的是spring底层自动完成织入
- */
- @Component
- @Aspect
- public class AopAdvice {
- /*
- before after 使我们抽出来的 逻辑代码
- 在aop的术语中 这两个方法 叫做通知方法
- 在 pt 切入点 方法执行之前 执行 before功能
- */
- //配置切入点 是用来找到需要被增强的方法的
- @Pointcut("execution(void com.cjx.service.UserServiceImpl.save(com.cjx.pojo.User))")
- public void pt(){
- }
- @Pointcut("execution(void com.cjx.service.UserServiceImpl.update(com.cjx.pojo.User))")
- public void update(){
- }
- @Before("pt()")
- public void before(){
- System.out.println("当前方法执行前初始时间毫秒值是:"+System.currentTimeMillis());
- }
- @After("pt()")
- public void after(){
- System.out.println("当前方法执行后初始时间毫秒值是:"+System.currentTimeMillis());
- }
- @Before("update()")
- public void before01(){
- System.out.println("当前方法执行的时间是:"+new Date());
- }
- @After("update()")
- public void after01(){
- System.out.println("当前方法执行的时间是:"+new Date());
- }
- }
复制代码
- package com.cjx.config;
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.EnableAspectJAutoProxy;
- //Spring配置文件
- @Configuration
- @ComponentScan("com.cjx")
- @EnableAspectJAutoProxy
- public class SpringConfig {
- }
复制代码 8.4.4 工作流程分析
- Spring容器启动,加载bean
- 启动AOP,加载所有的切入点
- bean 工作的过程中, AOP 内部监听机制不停监听设置的切 入点是否运行
- 当切入点对应的对象(目标对象)执行对应的切入点方法 时,创建目标对象的署理对象
- 动态将关照内容织入到署理对象对应的方法中
- 最终由署理对象完成最终工作
- package com.cjx.test;
- import com.cjx.config.SpringConfig;
- import com.cjx.pojo.User;
- import com.cjx.service.UserService;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
- import java.sql.PreparedStatement;
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(classes = SpringConfig.class)
- public class UserTest {
- @Autowired
- private User user;
- @Autowired
- private UserService userService;
- @Test
- public void test01(){
- userService.save(user);
- userService.update(user);
- userService.delete("1");
- userService.findAll();
- }
- }
复制代码 ok了家人们,see you later
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |