栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

2021-10-20 SpringAOP面向切面编程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

2021-10-20 SpringAOP面向切面编程

SpringAOP 主要内容

代理模式

代理模式在Java开发中是一种比较常见的设计模式.设计目的旨在为服务类与客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用.如租房子的例子:房客,中介,房东.对应于代理模式中即: 客户类,代理类,委托类(被代理类).

为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问. 委托类和代理类有一个共同的父类或父接口. 代理类会请对请求做预处理, 过滤 , 请求分配给指定对象.

生活中常见的代理情况:

租房中介,婚庆公司等

代理模式的两个设计原则:

1.代理类与委托类具有相似的行为(共同) 2.代理类增强委托类的行为

常用的代理模式:

1.静态代理

2.动态代理

静态代理

某个对象提供一个代理,代理角色固定,以控制对这个对象的访问.代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象代替.代理类负责请求的预处理,过滤,将请求分派给委托类处理,以及委托类执行完请求后的后续处理.

代理的三要素

a.有共同的行为(结婚) - 接口

b.目标角色(新人) - 实现行为

c.代理角色(婚庆公司) - 实现行为 增强目标对象行为

静态代理的特点

1.目标角色固定

2.在应用程序执行前就得到目标角色

3.代理对象会增强目标对象的行为

4.有可能存在多个代理 引起"类爆炸"(缺点)

静态代理实现 定义行为(共同)定义接口
public interface Marry{
    public void toMarry();
}
目标对象(实现行为)
public class You implements Marry{
    //实现行为
    @override
    public void toMarry(){
        System.out.println("我要结婚了...");
    }
}
代理对象(实现行为,增强目标对象的行为)
public class MarryCompanyProxy implements Marry{
    //目标对象
    private Marry marry;
    //通过构造器将目标对象传入
    public MarryCompanyProxy(Marry marry){
        this.marry = marry;
    }
    
    //实现行为
    @override
    public void toMarry(){
        //增强行为
        before();
        
        //执行目标对象中的方法
        marry.toMarry();
        
        //增强行为
        after();
    }
    
    private void after(){
        System.out.println("新婚快乐,早生贵子!");
    }
    
    private void before(){
        System.out.println("场地布置中...");
    }
}
通过代理对象实现目标对象的功能
//目标对象
You you = new You();
//构造代理角色同时传入真实角色
MarryCompanyProxy  marryCompanyProxy  = new MarryCompanyProxy(you);
//通过代理对象调用目标对象中的方法
marryCompanyProxy.toMarry();

静态代理对于代理的角色是固定的,如dao层有20个dao类,如果要对方法的访问权限进行代理,此时需要创建20个静态代理角色,引起类爆炸,无法满足生产上的需要,于是催生了动态代理的思想.

动态代理

相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生.它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码.动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类.代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的.

动态代理的两种实现方式:

1.JDK动态代理

2.CGLIB动态代理

动态代理的特点

1.目标对象不固定

2.在应用程序执行时动态创建目标对象

3.代理对象会增强目标对象的行为

JDK动态代理 注:JDK动态代理的目标对象必须有接口实现 newProxyInstance

Proxy类:

Proxy类专门完成代理的操作类,可以通过此类为一个或多个接口动态生成实现类,此类提供了如下操作方法:

public static object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
获取代理对象
public class JdkHandler implements InvocationHandler{
    //目标对象
    private object target;//目标对象的类型不固定,创建时动态生成
    //通过构造器将目标对象赋值
    public JdkHandler(object target){
        this.target = target;
    }
    
      @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强行为
        System.out.println("==============方法前执行");
        // 调用目标对象的方法(返回Object)
        Object result = method.invoke(target,args);
        // 增强行为
        System.out.println("方法后执行==============");
        return result;
   }
    
    public Object getProxy() {
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
   }
}
通过代理对象实现目标对象的功能
// 目标对象
You you = new You();
// 获取代理对象
JdkHandler jdkHandler = new JdkHandler(you);
Marry marry = (Marry) jdkHandler.getProxy();
// 通过代理对象调用目标对象中的方法
marry.toMarry();
问:Java动态代理类中的invoke是怎么调用的?
答:在生成的动态代理类$Proxy0.class中,构造方法调用了父类Proxy.class的构造方法,给成员变量
invocationHandler赋值,$Proxy0.class的static模块中创建了被代理类的方法,调用相应方法时方法体中调用了父类中的成员变量InvocationHandler的invoke()方法.

注:JDK动态代理依靠接口实现,如果有些类并没有接口实现,则不能使用JDK代理.

CGLIB动态代理

JDK的动态代理机制只能代理实现接口的类,而不能实现接口的类就不能使用JDK的动态代理,cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理.

添加依赖

在pom.xml文件中引入cglib的相关依赖


    cglib
    cglib
    2.2.2

定义类

实现MethodInterceptor接口

public class CglibInterceptor implements MethodInterceptor {
    // 目标对象
    private Object target;
    // 通过构造器传入目标对象
    public CglibInterceptor(Object target) {
        this.target = target;
   }
    
    public Object getProxy() {
        // 通过Enhancer对象的create()方法可以生成一个类,用于生成代理对象
        Enhancer enhancer = new Enhancer();
        // 设置父类 (将目标类作为其父类)
        enhancer.setSuperclass(target.getClass());
        // 设置拦截器 回调对象为本身对象
        enhancer.setCallback(this);
        // 生成一个代理类对象,并返回
        return enhancer.create();
   }
    
    @Override
    public Object intercept(Object object, Method method, Object[] objects, 
                            MethodProxy methodProxy) throws Throwable {
        // 增强行为
        System.out.println("==============方法前执行");
        // 调用目标对象的方法(返回Object)
        Object result = methodProxy.invoke(target,objects);
        // 增强行为
        System.out.println("方法后执行==============");
        return result;
   }
}
调用方法
// 目标对象
You you = new You();
CglibInterceptor cglibInterceptor = new CglibInterceptor(you);
Marry marry = (Marry) cglibInterceptor.getProxy();
marry.toMarry();
User user = new User();
CglibInterceptor cglibInterceptor = new CglibInterceptor(user);
User u = (User) cglibInterceptor.getProxy();
u.test();
JDK代理与CGLIB代理的区别

JDK动态代理实现接口,Cglib动态代理继承思想

JDK动态地理(目标对象存在接口时)执行效率高与Cglib

如果目标对象有接口的实现,选择JDK代理,如果没有接口的实现选择Cglib代理

Spring AOP 日志处理带来的问题

我们有一个Pay(接口)然后两个实现类DollarPay和RmbPay,都需要重写pay()方法,这时我们需要对pay方法进行性能监控,日志的添加等等怎么做?

最容易想到的方法

对每个字符方法做日志代码的编写处理,如下面方式

缺点**:代码重复太多,添加的日志代码耦合度太高**(如果需要更改日志记录代码功能需求,类中方法需要全部改动,工程量浩大)

使用装饰器模式代理/代理模式改进解决方案

装饰器模式:动态地给一个对象添加一些额外的职责

代理模式:以上刚讲过.于是得出以下结构:

仔细考虑过后发现虽然对原有内部代码没有进行改动,对于每个类做日志处理,并引用目标类,但是如果待添加日志的业务类的数量很多,此时手动为每个业务类实现一个装饰器或创建对应的代理类,同时代码的耦合度也加大,需求一旦改变,改动工程量也是可想而知的.

有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方能够实现代码的复用,达到松耦合的同时,又能够完美完成功能.

答案是肯定的,存在这样的技术,aop一斤对其提供了完美的实现!

什么是AOP?

Aspect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来说,Aop关注的不再是程序代码中某个 类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。联想大家吃 的汉堡(中间夹肉)。那么aop是怎么做到拦截整个面的功能呢?考虑前面学到的servlet filter @Component // 将对象交给IOC容器去实例化 @Aspect // 声明当前类是一个切面 public class LogCut { @Pointcut("execution (* com.xxxx.service..*.*(..) )") public void cut(){} @Before(value = "cut()") public void before() { System.out.println("前置通知....."); } @AfterReturning(value = "cut()") public void afterReturn() { System.out.println("返回通知....."); } @After(value = "cut()") public void after() { System.out.println("最终通知....."); } @AfterThrowing(value="cut()",throwing = "e") public void afterThrow(Exception e) { System.out.println("异常通知....." + " 异常原因:" + e.getCause()); } @Around(value = "cut()") public Object around(ProceedingJoinPoint pjp) { System.out.println("前置通知..."); Object object = null; try { object = pjp.proceed(); System.out.println(pjp.getTarget() + "======" + pjp.getSignature()); // System.out.println("返回通知..."); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("异常通知..."); } System.out.println("最终通知..."); return object; } } 配置文件(spring.xml)


XML实现 定义切面
**
* 切面
*  切入点和通知的抽象   (与面向对象中的 类 相似)
* 定义 切入点和通知 (切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么)
*/
@Component // 将对象交给IOC容器去实例化
public class LogCut02 {
    public void cut(){}
    
    public void before() {
        System.out.println("前置通知.....");
   }
    
    public void afterReturn() {
        System.out.println("返回通知.....");
   }
    
    public void after() {
        System.out.println("最终通知.....");
   }
    
        public void afterThrow(Exception e) {
        System.out.println("异常通知....." + " 异常原因:" + e.getCause());
   }
    
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("前置通知...");
        Object object = null;
        try {
            object = pjp.proceed();
            System.out.println(pjp.getTarget() + "======" + pjp.getSignature());
            // System.out.println("返回通知...");
       } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("异常通知...");
       }
        System.out.println("最终通知...");
        return object;
   }
}
配置文件(spring.xml)

    
    
        
        
        
        
        
        
        
        
        
        
        
        
    

Spring AOP总结 代理模式实现三要素

​ 1.接口定义

​ 2.目标对象与代理对象必须实现统一接口

​ 3.代理对象持有目标对象的引用增强目标对象的行为

代理模式实现分类以及对应区别

​ 1.静态代理: 手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建

​ 2.动态代理: 在程序运行期动态创建目标对象对应代理对象.

​ 3.jdk动态代理: 被代理目标对象必须实现某一类或某一组接口实现方式通过回调创建代理对象.

​ 4.cglib动态代理: 被代理目标对象可以不必实现接口,继承的方式实现.

动态代理相比较静态代理,提高开发效率,可以批量化创建代理,提高代码复用率. AOP理解

​ 1.面向切面,相比oop关注代码中的层或面

​ 2.解耦,提高系统扩展性

​ 3.提高代码复用

AOP关键词

​ 1.连接点: 每一个方法

​ 2.切入点: 匹配的方法集合

​ 3.切面: 连接点与切入点的集合决定了切面,横关注点的抽象

​ 4.通知: 几种通知

​ 5.目标对象:被代理对象

​ 6.织入: 程序运行期将切面应用到目标对象并生成代理对象的过程

​ 7.引入: 在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程

Spring Task 定时任务 主要内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v1mchEM5-1634692565446)(C:UsersAchuanAppDataRoamingTyporatypora-user-imagesimage-20211014171855460.png)]

定时任务概述

在项目中开发定时任务应该是一种比较常见的需求,在Java中开发定时任务主要有三种解决方案:一是使用JDK自带的Timer,二是使用第三方组件Quartz功能强大,但是使用起来相对笨重.而Spring Task则具备前两者的有点(功能强大且简单易用),使用起来很简单,除Spring相关的包外不需要额外的包.而且支持注解和配置文件两种形式.

使用Spring Task实现定时任务

Spring Task 开发定时任务有两种任务配置方式:

1.XML配置

2.注解配置

使用XML配置实现定时任务 创建项目,添加依赖

创建Maven项目,在pom.xml配置文件中,修改项目环境,添加Spring依赖坐标


	  org.springframework
    spring-context
    5.2.4.RELEASE

添加配置文件spring.xml

在src/main/resource目录下新建配置文件spring.xml,并设置Spring扫描器


    
    

定义定时任务方法

新建类,添加自动注入的注解,定义定时任务的方法

package com.xxxx.task;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
public class TaskJob {
    public void job1(){
        System.out.println("任务 1:" +
                new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
   }
    public void job2(){
        System.out.println("任务 2:" +
                new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
   }
}
定时任务命名空间的添加

在spring.xml配置文件中,添加定时任务的命名空间

xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd

修改配置文件内容如下:


定时任务配置

 
 
 
 
 
测试定时任务

public static void main( String[] args ) {
        
        System.out.println("定义任务测试...");
        // 获取Spring上下文环境
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        // 获取指定Bean对象
        TaskJob taskJob = (TaskJob) ac.getBean("taskJob");
}
使用注解配置实现定时任务 定义定时 任务方法

@Scheduled注解使用

package com.xxxx.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
@Component
public class TaskJob02 {
    @Scheduled(cron="0/2 * * * * ?")
    public void job1(){
        System.out.println("任务 1:" +
                new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
   }
    @Scheduled(cron="0/5 * * * * ?")
    public void job2(){
        System.out.println("任务 2:" +
                new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
   }
}
定时任务命名空间的添加

在spring.xml配置文件中,添加定时任务的命名空间

xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
配置定时任务驱动

配置文件内容如下:


    
    
    
    

测试定时任务
public static void main( String[] args ) {
        
        System.out.println("定义任务测试...");
        // 获取Spring上下文环境
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        // 获取指定Bean对象
        TaskJob02 taskJob02 = (TaskJob02) ac.getBean("taskJob02");
}
Cron表达式简介

关于cronException表达式有至少6个(也可能是7个)由空格分隔时间元素.从左至右,这些元素的定义如下:

1.秒(0–59)

2.分钟(0–59)

3.小时(0–23)

4.月份中的日期(1–31)

5.月份(1–12 或 JAN–DEC)

6.星期中的日期(1–7 或 SUN–SAT)

7.年份(1970–2099)

0 0 10,14,16 * * ?
    
每天上午 10 点,下午 2 点和下午 4 点
0 0,15,30,45 * 1-10 * ?
    
每月前 10 天每隔 15 分钟
30 0 0 1 1 ? 2012
    
在 2012 年 1 月 1 日午夜过 30 秒时

各个时间可用值如下:

秒 0-59 , - * /

分 0-59 , - * /

小时 0-23 , - * /

日 1-31 , - * ? / L W C

月 1-12 or JAN-DEC , - * /

周几 1-7 or SUN-SAT , - * ? / L C #

年(可选字段) empty, 1970-2099 , - * /

可用值详细分析如下:

"*" —— 字符可以用于所有字段,在"分"字段中设为"*",表示"每一分钟"的含义。
"?" —— 字符可以用在"日"和"周几"字段,它用来指定"不明确的值"。
智者乐水 仁者乐山 程序员 乐字节 2 如果需要更多优质的Java、Python、架构、大数据等IT资料请加微信:lezijie007
一些例子:
   这在你需要指定这两个字段中的某一个值而不是另外一个的时候会被用到。在后面的例子中可以看到其含义。
"-" —— 字符被用来指定一个值的范。
   比如在"小时"字段中设为"10-12",表示"10 点到 12 点"。
"," —— 字符指定数个值。
       比如在"周几"字段中设为"MON,WED,FRI",表示"the days Monday, Wednesday, and Friday"。
"/" —— 字符用来指定一个值的的增加幅度。
   比如在"秒"字段中设置为"0/15"表示"第 0, 15, 30,和 45 秒"。
   而"5/15"则表示"第 5, 20, 35,和 50"。
   在'/'前加"*"字符相当于指定从 0 秒开始。每个字段都有一系列可以开始或结束的数值。
   对于"秒"和"分"字段来说,其数值范围为 0 到 59。
   对于"小时"字段来说其为 0 到 23,对于“日”字段来说为 0 到 31。
   而对于"月"字段来说为 1 到 12。
   "/"字段仅仅只是帮助你在允许的数值范围内从开始"第 n"的值。
"L" —— 字符可用在"日"和"周几"这两个字段。它是"last"的缩写,但是在这两个字段中有不同的含义。
   "日"字段中的"L"表示"一个月中的最后一天",对于一月就是 31 号,对于二月来说就是 28 号(非闰年)。
   "周几"字段中,它简单的表示"7" or "SAT"。
   但是如果在"周几"字段中使用时跟在某个数字之后,它表示"该月最后一个星期×"。
   比如"6L"表示"该月最后一个周五"。
   当使用"L"选项时,指定确定的列表或者范围非常重要,否则你会被结果搞糊涂的。
"W" —— 可用于"日"字段。用来指定历给定日期最近的工作日(周一到周五)。
   比如将"日"字段设为"15W",意为: "离该月 15 号最近的工作日"。
   因此如果 15 号为周六,触发器会在 14 号即周五调用。
   如果 15 号为周日,触发器会在 16 号也就是周一触发。如果 15 号为周二,那么当天就会触发。
   如果"日"字段设为"1W",而一号是周六,会于下周一即当月的 3 号触发,它不会越过当月的值的范围边界。
   "W"字符只能用于"日"字段的值为单独的一天而不是一系列值的时候。
   "L"和"W"可以组合用于“日”字段表示为'LW',意为"该月最后一个工作日"。
"#" —— 字符可用于"周几"字段。该字符表示"该月第几个周×"。
   比如"6#3"表示该月第三个周五( 6 表示周五,而"#3"该月第三个)。
   再比如: "2#1" 表示该月第一个周一,而"4#5" 该月第五个周三。
   注意如果你指定"#5"该月没有第五个"周×",该月是不会触发的。
"C" —— 字符可用于"日"和"周几"字段,它是"calendar"的缩写。
       它表示为基于相关的日历所计算出的值(如果有)。如果没有关联的日历,那它等同于包含全部日历。
       "日"字段值为"5C",表示"日历中的第一天或者 5 号以后"。
       "周几"字段值为"1C",则表示"日历中的第一天或者周日以后"。
       对于"月份"字段和"周几"字段来说合法的字符都不是大小写敏感的。

一些例子:

"0 0 12 * * ?" 每天中午十二点触发
"0 15 10 ? * *" 每天早上 10:15 触发
"0 15 10 * * ?" 每天早上 10:15 触发
"0 15 10 * * ? *" 每天早上 10:15 触发
"0 15 10 * * ? 2005" 2005 年的每天早上 10:15 触发
"0 * 14 * * ?" 每天从下午 2 点开始到 2 点 59 分每分钟一次触发
"0 0/5 14 * * ?" 每天从下午 2 点开始到 2:55 分结束每 5 分钟一次触发
"0 0/5 14,18 * * ?" 每天的下午 2 点至 2:55 和 6 点至 6 点 55 分两个时间段内每 5分钟一次触发
"0 0-5 14 * * ?" 每天 14:00 至 14:05 每分钟一次触发
"0 10,44 14 ? 3 WED" 三月的每周三的 14:10 和 14:44 触发
"0 15 10 ? * MON-FRI" 每个周一、周二、周三、周四、周五的 10:15 触 发
"0 15 10 15 * ?" 每月 15 号的 10:15 触发
"0 15 10 L * ?" 每月的最后一天的 10:15 触发
"0 15 10 ? * 6L" 每月最后一个周五的 10:15
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/337208.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号