你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

spring(二)之面向切面与定时任务

2021/12/10 7:05:06

spring之面向切面与定时任务

  • 一、面向切面(AOP)
  • 二、切入点表达式
  • 三、aop 应用场景(自)
  • 四、注解切面
  • 五、字符串工具(自)
  • 六、定时任务
  • 七、动态修改定时任务(自)
  • 八、异步方法
  • 九、邮件发送
  • 十、文件工具(自)
  • 十一、Easycode

一、面向切面(AOP)

  在实际开发中,经常会在某个业务逻辑层(如 service)执行前后织入一些额外的逻辑代码完成一些特殊的功能。如权限验证、日志记录、方法执行时间统计、缓存等。这时就需要使用设计模式中的动态代理模式,随着各种 JAVA 动态代理的发展,使用动态代理越来越简单。其中 aspectj 与 spring 融合后完成面向切面编程更是极大的简化开发和配置的难度。

  常见概念:
  1.Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为 execution 方式和 annotation 方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
  2.Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
  3.Aspect:切面,即 Pointcut 和 Advice。
  4.Join point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
  5.Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

1.添加 aspects 依赖,spring 的 aop 依赖aspectj 的动态代理给你,spring 与 aspectj 整合包为 spring-aspects

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.12.RELEASE</version>
</dependency>

2.在配置文件中开启 aspectj 代理,同时切面类必须定义在 spring 的包扫描下。

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

AspectJ:Java 社区里最完整最流行的动态代理框架。在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP。

3.切面类需要在包扫描下声明为组件(Component)同时还要添加注解 @Aspect 声明为切面类,在切面类中添加一个方法并使用如下的切入点注解。spring 支持 5 种类型的切入点注解,分别对应方法执行过程中的各个周期。在实际开发中更多的是使用环绕通知,因为其可以获取方法执行前后的更多细节。使用环绕通知时方法的参数为 ProceedingJoinPoint 类型的参数,spring 通过该参数获取方法的执行细节,同时通过调用参数的 proceed 调用被代理对象的方法。

@Before:前置通知,在方法执行之前执行

@After:后置通知,在方法执行之后执行

@AfterRunning:返回通知,在方法返回结果之后执行

@AfterThrowing:异常通知,在方法抛出异常之后

@Around:环绕通知,围绕着方法执行

案例:完成 service 所有方法执行时间统计

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.Arrays;

@Aspect
@Component
public class AopTest {
    @Around("execution(* cn.hxzy.spring.service.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行前--方法名:" + methodName + " 参数:" + Arrays.asList(joinPoint.getArgs()));
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        System.out.println("执行后--方法名:" + methodName + " 方法执行的返回值:" + result);
        long end = System.currentTimeMillis();
        System.out.println("执行时间:" + (end - start));
        return result;
    }
}

二、切入点表达式

  在使用 spring 框架配置 AOP 的时候,不管是通过 XML 配置文件还是注解的方式都需要定义切入点。

  例如定义切入点表达式 execution(* com.sample.service.impl….(…)) execution()是最常用的切点表达式,其语法如下所示:

  整个表达式可以分为五个部分:
  1、execution(): 表达式主体。

  2、第一个 * 号:表示返回类型,* 号表示所有的类型。

  3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

  4、第二个 * 号:表示任意类名。

  5、(…):最后这个星号表示方法名, 号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

如:

任意公共方法的执行: execution(public * *(…))

任何一个名字以“set”开始的方法的执行: execution(* set*(…))

AccountService 接口定义的任意方法的执行: execution(* com.xyz.service.AccountService.*(…))

在 service 包中定义的任意方法的执行: execution(* com.xyz.service..(…))

在 service 包或其子包中定义的任意方法的执行: execution(* com.xyz.service….(…))

切面顺序:当有两个以上的切面时,可以使用 @Order 注解指定切面的优先级, 值越大优先级越高

@Order(2)
@Aspect
@Component
public class LoggingAspect {
    ....
}

练习:

1.使用 spring 与 mybatis 整合后完成宠物管理系统的开发,完成如下功能:操作 1:查看全部宠物; 2:根据id找宠物;3:根据id删除宠物; 0:退出系统,并为 service 添加切面用于记录每个方法执行的时长。

参考代码:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TestAop {

    @Around("execution(* cn.hxzy.pet.service.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行方法名:" + methodName);
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        System.out.println("执行时长:" + (System.currentTimeMillis() - start));
        return result;
    }
}

三、aop 应用场景(自)

  数据库设计过程中,我们往往会给数据库表添加一些通用字段,比如创建人、创建时间、修改人、修改时间,在一些公司的设计过程中有时会强制要求每个表都要包含这些基础信息,以便记录数据操作时的一些基本日志记录。

  按照平常的操作来说,通用做法是输写sql时,将这些信息和对象的基本属性信息一起写入数据库,当然,这也是大家习以为常的操作,这种写法无可厚非,但是对于一个高级开发人员来说,如果所有的表都进行如此操作,未免显得有点啰嗦,而且数据表多的话,这样写就有点得不偿失了。

  其实还有一种更简便的做法,spring框架大家应该是比较熟悉的,几乎每个公司都会用到,其中aop思想(切面编程)的经典应用场景之一就是日志记录,本文结合aop思想,着重介绍下springboot框架下如何利用切面编程思想实现将创建人、创建时间、更新人、更新时间等基础信息写入数据库。 核心代码

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.util.Date;

@Aspect
@Component
@Configuration
public class CommonDaoAspect {

    private static final String creater = "creater";
    private static final String createTime = "createTime";
    private static final String updater = "updater";
    private static final String updateTime = "updateTime";

    @Pointcut("execution(* cn.hxzy.service..*.update(..))")
    public void daoUpdate() {
    }

    @Pointcut("execution(* cn.hxzy.service..*.insert(..))")
    public void daoCreate() {
    }

    @Around("daoUpdate()")
    public Object doDaoUpdate(ProceedingJoinPoint pjp) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return pjp.proceed();
        }
        HttpServletRequest request = attributes.getRequest();
        HttpSession session = request.getSession();
        Object userId = session.getAttribute("userId");
        if (userId != null) {
            Object[] objects = pjp.getArgs();
            if (objects != null && objects.length > 0) {
                for (Object arg : objects) {
                    ReflectionUtils.setField(ReflectionUtils.findField(arg.getClass(), updater), arg, userId);
                    ReflectionUtils.setField(ReflectionUtils.findField(arg.getClass(), updateTime), arg, new Date());
                }
            }
        }
        Object object = pjp.proceed();
        return object;

    }

    @Around("daoCreate()")
    public Object doDaoCreate(ProceedingJoinPoint pjp) throws Throwable {
        //获取request 里面的值,确保是通过浏览器调用而不是单元测试
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return pjp.proceed();
        }
        //获取方法的参数pet: int insert(Pet pet);
        Object[] objects = pjp.getArgs();
        //获取session里面的登录消息(用户id)
        HttpServletRequest request = attributes.getRequest();
        HttpSession session = request.getSession();
        Object userId = session.getAttribute("userId");
        //遍历方法参数
        if (objects != null && objects.length > 0) {
            for (Object arg : objects) {
                //使用ReflectionUtils 工具类得到 参数的类的 creater 的属性
                Field createField = ReflectionUtils.findField(arg.getClass(), creater);
                if (createField != null) {
                    //如果该对象有该属性,就设置该属性可以暴力获取和设置
                    ReflectionUtils.makeAccessible(createField);
                    //判断该字段是否未设置,如果未设置,则设置为session里面的id
                    if (StringUtils.isEmpty(ReflectionUtils.getField(createField, arg))) {
                        ReflectionUtils.setField(createField, arg, userId);
                    }
                }
                Field createTimeField = ReflectionUtils.findField(arg.getClass(), createTime);
                if (createTimeField != null) {
                    ReflectionUtils.makeAccessible(createTimeField);
                    if (StringUtils.isEmpty(ReflectionUtils.getField(createTimeField, arg))) {
                        ReflectionUtils.setField(createTimeField, arg, new Date());
                    }
                }
            }
        }
        Object object = pjp.proceed();
        return object;
    }

}

四、注解切面

  注解切面也是一种使用比较常用的切面,作用与前面案例相同,只是注解切面针对的是带有相关注解的方法。

  案例:

  创建一个切面类,切点设置为拦截所有标注 PermissionsAnnotation 的方法,截取到接口的参数并进行简单的参数校验。

  具体的实现步骤:

  1.使用 @Target、@Retention、@Documented 自定义一个注解,其中 ElementType.METHOD 表明该注解只能用于方法上,RetentionPolicy.RUNTIME 表示该注解运行时有效。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}

  2.创建第一个 AOP 切面类,并在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现第一步权限校验逻辑:

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class PermissionFirstAdvice {

    @Around("@annotation(cn.hxzy.spring.annotation.PermissionAnnotation)")
    public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===================第一个切面===================:" + System.currentTimeMillis());

        //获取请求参数,详见接口类
        Object[] objects = joinPoint.getArgs();

        // 第一个参数 id小于0则抛出非法id的异常
        if ((int)objects[0] < 0) {
            return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}");
        }
        return joinPoint.proceed();
    }
}

  3.创建测试类,并在目标方法上标注自定义注解 @PermissionsAnnotation 并测试。

import cn.hxzy.spring.annotation.PermissionAnnotation;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @PermissionAnnotation
    public JSONObject get(int id) {
        return JSON.parseObject("{'aa':1}");
    }
}

练习:

  1.使用 spring 与 mybatis 整合后完成宠物管理系统的开发,完成如下功能:操作 1:查看全部宠物; 2:根据id找宠物;3:根据id删除宠物; 0:退出系统,为 service 添加注解切面用于缓存方法执行结果和更新方法执行结果。

  创建注解 @MyCacheAble(缓存) 与 @MyCacheEvict(清除缓存),凡是加入该注解的方法都可以得到对应的缓存和清除缓存的能力,缓存数据放在文件中。

  注解:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCacheAble{
}

切面:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.io.*;
import java.util.Arrays;

@Component
@Aspect
public class AopTest {

    @Around("@annotation(cn.hxzy.aop.MyCacheAble)")
    public Object aop2(ProceedingJoinPoint point) throws Throwable {
        System.out.println("缓存文件");
        String name = point.getSignature().getName();
        String args = Arrays.toString(point.getArgs());
        if (new File(name + args+".cache").exists()) {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(name + args+".cache"));
            Object o = objectInputStream.readObject();
            objectInputStream.close();
            return o;
        }
        ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(name + args+".cache"));
        Object proceed = point.proceed();
        stream.writeObject(proceed);
        stream.close();
        return proceed;
    }

    @Around("@annotation(cn.hxzy.aop.MyCacheEvict)")
    public Object aop3(ProceedingJoinPoint point) throws Throwable {
        File file = new File("./");
        File[] files = file.listFiles();
        for (File f : files) {
            if (f.getName().endsWith(".cache")){
                f.delete();
            }
        }
        Object proceed = point.proceed();
        return proceed;
    }
}

五、字符串工具(自)

spring 针对字符串提供一套比较常用的方法。如:判断字符串是否为空串、是否全为字母等。使用方法如下:

StringUtils.isBlank(obj)

判断obj里面为空 空字符串 只有空格

StringUtils.isEmpty(obj)

判断obj里面为空 空字符串 (为“ ”时不判断)

StringUtils.isAlpha(obj)

判断obj是否全是是字母

StringUtils.isNumeric(obj)

判断obj是否全是是数字

StringUtils.isAlphanumeric(obj)

判断obj是否是是数字或字母(全是数字,全是字母,又有数字又有字母)

StringUtils.deleteWhitespace(“aa bb cc”):

去除字符串中的空白(包括全角空格、TAB)

StringUtils.trim(" abc ")

去除字符串头尾空白

StringUtils.trimToEmpty(" ")

去除字符串头尾空白(包括TAB,但不包括全角空格), 结果为空串时返回空串

StringUtils.trimToNull(" ")

去除字符串头尾空白(包括TAB,但不包括全角空格), 结果为空串时返回null

StringUtils.contains(“defg”, “ef”)

检查 defg里面是否包含ef

StringUtils.containsIgnoreCase(“defg”, “EF”):

检查忽略大小写的检查

StringUtils.containsAny(“defg”, “ef”) 有交集

StringUtils.containsNone(“defg”, “ef”) 没有交集

交集:即是后一个字符串里面是否有字符在前一个字符串中出现

StringUtils.indexOfDifference(“aaabc”, “aaacc”) //3

返回两字符串不同处索引号

六、定时任务

  在实际开发中,经常会为项目增加定时任务来定时完成某些任务,如每天晚上零点同步数据库表,每天中午统计不活跃用户并主动邮件联系等。在 spring 中开启定时任务需要在 spring 的核心配置文件中加入如下的节点开启定时任务。

<task:annotation-driven/>

  常用的定时任务分为固定频率模式(fixedRate)和计时模式(cron),固定频率模式主要控制每隔指定时间执行一次对应方法,与 JavaScript 中的 setinterval 类似。而计时模式使用标准的 cron 表达式控制方法执行。如下配置完成后,启动 spring 容器后定时任务就会自动开始计时执行。

import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class BeanSchedule {	

    @Scheduled(fixedRate=5000)//5秒执行一次
    public void name1() {
        System.out.println("定时任务");		
    }

    @Scheduled(cron="0 35 14 ? * *")//每天14:35执行
    public void name() {
        System.out.println("定时任务14:35");
    }
}

常用 cron 表达式示例如下:

0 0 10,14,16 * * ? 每天10点,下午2点,40 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12"0 0 12 * * ?" 每天中午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期间的每1分钟触发 
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:102: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触发 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

也可以参照如下网站进行编写和调试。

网站地址

七、动态修改定时任务(自)

  有时候,动态任务的执行我们希望动态修改,通常在 Controller 层设置一个借口,调用时修改定时任务的 cron 表达式。

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import java.util.Date;

@Configuration
@EnableScheduling
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer {

    @Autowired
    private MyScheduled myScheduled;

    private int i = 0;
    private static final String DEFAULT_CRON = "0 0/10 * * * ?";
    private String cron = DEFAULT_CRON;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // 定时扫描长时间未处理的下发数据
        taskRegistrar.addTriggerTask(new Runnable() {
            @Override
            public void run() {
                try {
                    //定时方法
                    myScheduled.pushMsg();                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                CronTrigger trigger = new CronTrigger(cron);
                Date nextExecDate = trigger.nextExecutionTime(triggerContext);
                return nextExecDate;
            }
        });
    }

    public void setCron(String cron) {
        log.info("修改定时任务表达式:{}", cron);
        this.cron = cron;
    }
}

八、异步方法

  在某些请求中,需要执行比较耗时的方法比如发送邮件或文件上传,但又不希望影响前端响应。通常常使用异步方法,从 Spring3 开始提供了 @Async 注解,该注解可以被标在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给 Spring TaskExecutor 的任务中,由指定的线程池中的线程执行。

  异步方法使用有如下两步:

  1.配置 xml 开启允许异步

<task:executor id="executor" pool-size="5"  />
<task:annotation-driven executor="executor"/>

  2.控制器中调用服务层的异步方法

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    UserService userService;

    public void asyncMethod() throws InterruptedException {
        userService.asyncMethod();
        System.out.println("2");
    }
}

3.服务层的异步方法

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Async
    public void asyncMethod() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println(1);
    }
}

  注意:异步方法在同一个类中不串联调用,否则将导致异步失效,即异步方法不能写在控制层且由控制层调用。

  练习:

  1.在 service 层编写一个文件拷贝的方法由视图层调用,同时为了不影响程序的整体运行设计为异步。

  参考代码:

  调用者:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.io.IOException;

@Controller
public class FileView {
    @Autowired
    private FileService fileService;

    public void copyFile() throws IOException {
        System.out.println("开始异步");
        fileService.copyFile("D:/jdk.zip","C:/jdk.zip");
        System.out.println("主函数结束");
    }
}

  操作者:

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

@Service
public class FileService {

    @Async
    public void copyFile(String source, String target) throws IOException {
        FileInputStream inputStream = new FileInputStream(source);
        FileOutputStream outputStream = new FileOutputStream(target);
        byte[] bytes = new byte[1024];
        int n;
        while ((n = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, n);
        }
        inputStream.close();
        outputStream.close();
    }
}

九、邮件发送

  Spring Email 抽象的核心是 MailSender 接口。顾名思义,MailSender 的实现能够通过连接 Email 服务器实现邮件发送的功能。Spring 自带的一个 MailSender 的实现 JavaMailSenderImpl。它会使用 JavaMail API 来发送 Email。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.2.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
</dependency>

配置邮件发送类 JavaMailSenderImpl

<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="smtp.qq.com"/>
    <property name="port" value="25"/>
    <property name="username" value="1533481985@qq.com"/>
    <property name="password" value="nktxzgxrfgdjjjai"/>
</bean>

  注意:在 host 中 smtp 是邮件发送的基本协议,类似于 http、ftp、jdbc 等协议。后面的 qq 说明使用的是 qq 邮箱服务,如果使用 163 则为 smtp.163.com,同时一定要在发送者邮箱中开启 SMTP。

  QQ 邮箱开启 smtp 需要在浏览器中登录 QQ 邮箱,然后点击:设置->账户->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务。开启成功后会获取第三方登录使用的密码,将密码填入 JavaMailSenderImpl 即可发送常见的邮件。
在这里插入图片描述
  1.发送简单邮件

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring-context.xml")
public class AppTest {
    @Autowired
    private JavaMailSender mailSender;

    @Test
    public void sendSimpleEmail(){
        SimpleMailMessage message = new SimpleMailMessage();//消息构造器
        message.setFrom("test@163.com");//发件人
        message.setTo("shoujian@tom.com");//收件人
        message.setSubject("Spring Email Test");//主题
        message.setText("hello world!!");//正文
        mailSender.send(message);
        System.out.println("邮件发送完毕");
    }

    @Test
    public void sendEmailWithAttachment() throws MessagingException{
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);//构造消息helper,第二个参数表明这个消息是multipart类型的
        helper.setFrom("testFrom@163.com");
        helper.setTo("testTo@qq.com");
        helper.setSubject("Spring Email Test");
        helper.setText("这是一个带有附件的消息");
        //使用Spring的FileSystemResource来加载fox.png
        FileSystemResource image = new FileSystemResource("D:\\fox.png");
        System.out.println(image.exists());
        helper.addAttachment("fox.png", image);//添加附加,第一个参数为添加到Email中附件的名称,第二个人参数是图片资源
        mailSender.send(message);
        System.out.println("邮件发送完毕");
    }

    @Test
    public void sendRichEmail() throws MessagingException{
        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        helper.setFrom("testFrom@163.com");
        helper.setTo("testTo@qq.com");
        helper.setSubject("Spring Email Test");
        helper.setText("<html><body><img src='http://58.42.239.163:8888/static/img/course/default.jpg'>"
                + "<h4>Hello World!!!</h4>"
                + "</body></html>", true);//第二个参数表明这是一个HTML    
        mailSender.send(message);
    }

}

练习:

1.向 一个带有@qq.com后缀的邮箱发送一条普通、带附件和富文本邮件。

十、文件工具(自)

spring 简化了常见的文件操作

1.获取文件

File clsFile = ResourceUtils.getFile(“classpath:aa.txt”);

//获取类路径下的文件

File clsFile = ResourceUtils.getFile(“file:D:/conf/file1.txt”);

//获取文件系统中的文件

2.文件操作

File clsFile = ResourceUtils.getFile(“classpath:aa.txt”);

// ① 将文件内容拷贝到一个 byte[] 中

byte[] fileData = FileCopyUtils.copyToByteArray(clsFile);

// ② 将文件内容拷贝到一个 String 中

String fileStr = FileCopyUtils.copyToString(new FileReader(clsFile));

// ③ 将文件内容拷贝到另一个目标文件

FileCopyUtils.copy(clsFile, new File(clsFile.getParent() + “/file2.txt”));

3.文件通配符搜索

PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();

Resource[] resources = patternResolver.getResources(“file:d:/*.zip”);

File file = resources[0].getFile();

ANT通配符有三种:

? 匹配任何单字符

*匹配0或者任意数量的字符

** 匹配0或者更多的目录

十一、Easycode

  Easycode 是 idea 的一个插件,可以直接对数据的表生成 entity、controller、service、dao、mapper.xml 无需任何编码,简单而强大。由于最新版本存在诸多 bug 推荐使用快照版 EasyCode-1.2.4-SNAPSHOT。
在这里插入图片描述
  Easycode 使用时需要用 idea 连接数据库。
在这里插入图片描述
  在需要生成代码的数据表上右键选择 Easycode->generater code,然后配置生成的代码包路径和生成的模板。

在这里插入图片描述
  最后点击 OK 即可生成相应代码。
在这里插入图片描述
章节练习:
1.创建项目 spring-mybatis,并完成 spring 与 mybatis 整合,使用 easycode 完成 pet 表的基本方法的生成。

2.在项目 spring-mybatis 中,为 service 层创建切面,凡是以 query 开头的方法都将获取到的数据缓存到磁盘文件,凡是以 update、delete、insert 开头的请求将删除所有缓存的文件,并重新调用 dao 层。

3.在上面项目中创建定时任务:每天晚上 12 点整向控制台打印:新的一天又开始了。

4.在上面项目中创建定时任务:所有工作日早上 7:30 向控制台打印:起床了,起床了!

参考代码:

缓存:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.io.*;
import java.util.Arrays;

@Aspect
@Component
public class AopTest {
    @Around("execution(* cn.hxzy.service.impl.*.query*(..))")
    public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("执行前--方法名:" + methodName + " 参数:" + Arrays.asList(joinPoint.getArgs()));
        String fileName = methodName + Arrays.asList(joinPoint.getArgs()) + ".cache";
        if (!new File(fileName).exists()) {
            ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(fileName));
            Object result = joinPoint.proceed();
            stream.writeObject(result);
            stream.close();
            return result;
        }
        ObjectInputStream stream = new ObjectInputStream(new FileInputStream(fileName));
        Object object = stream.readObject();
        stream.close();
        return object;
    }

    public Object clean(ProceedingJoinPoint joinPoint) throws Throwable {
        File file = new File("./");
        File[] files = file.listFiles();
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().endsWith(".cache")) {
                files[i].delete();
            }
        }
       return joinPoint.proceed();
    }


    @Around("execution(* cn.hxzy.service.impl.*.insert*(..))")
    public Object clean1(ProceedingJoinPoint joinPoint) throws Throwable {
        return clean(joinPoint);
    }
    @Around("execution(* cn.hxzy.service.impl.*.update*(..))")
    public Object clean2(ProceedingJoinPoint joinPoint) throws Throwable {
        return clean(joinPoint);
    }
    @Around("execution(* cn.hxzy.service.impl.*.delete*(..))")
    public Object clean3(ProceedingJoinPoint joinPoint) throws Throwable {
        return clean(joinPoint);
    }
}

定时任务:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class BeanSchedule {


    @Scheduled(cron = "0 00 00 ? * *")
    //每天00:00:00执行
    public void scheduled1() {
        System.out.println("新的一天又开始了。");
    }

    @Scheduled(cron = "0 30 07 ? * MON-FRI")
    //工作日 7:30:00 执行
    public void scheduled2() {
        System.out.println("起床了,起床了!");
    }
}