Spring框架基础知识

发布时间 2023-09-10 12:54:21作者: 小曾Study平台

Spring框架基础知识

1、简介

1、Spring框架是一个开源的JAVAEE的应用程序,主要是IOC(控制反转和依赖注入)和AOP(面向切面编程)两大技术。
2、Spring IOC(控制反转/依赖注入) Spring AOP SpringJDBC+事务
3、Spring 是众多开源java项目中的一员,基于分层的javaEE应用一站
式轻量级开源框架,主要核心是 IOC(控制反转/依赖注入)与AOP(面向切面)两大技术,实现项目在开发过程中的轻松解耦,提高项目的开发效率。在项目中引入 Spring 立即可以带来下面的好处 降低组件之间的耦合度,实现软件各层之间的解耦。可以使用容器提供的众多服务,如:事务管理服务、消息服务等等。当我们使用容器管理事务时,开发人员就不再需要手工控制事务.也不需处理复杂的事务传播。容器提供单例模式支持,开发人员不再需要自己编写实现代码。容器提供了AOP技术,利用它很容易实现如权限拦截、运行期监控等功能。
4、第三方功能:邮件发送、定时任务、异步消息处理。

image-20230830092207594

2、SpringIOC容器

image-20230830202005068

  • 三种获取配置文件的方式
1、多配置文件获取

2、配置文件相对路径获取

3、配置文件绝对路径获取
  • 测试代码
 //加载配置文件
         ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
         //获取Bean对象
         TestService testService = (TestService)context.getBean("testService");
         testService.TestFramework();
          //绝对路径加载文件(了解)
         FileSystemXmlApplicationContext context1 = new FileSystemXmlApplicationContext("D:\\bigCode\\SpringStudyDemo\\SpringDay01\\src\\main\\resources\\application.xml");
         TestService testService1 = (TestService) context1.getBean("testService");
         testService1.TestFramework();
         //加载多个配置文件  两种方式
         //1、编写多个文件名
        //ApplicationContext context2 = new ClassPathXmlApplicationContext("application.xml","applicationDao.xml");
         //2、在XML核心配置文件中导入外部文件集成一个文件,通过一个我配置文件进行加载,优点:在配置文件中导入时可以检查文件错误异常,防止写错文件名
          // <import resource="application.xml"></import>
         //    ApplicationContext context2 = new ClassPathXmlApplicationContext("application.xml");

  • 三种Bean对象实例化方式
1、构造器实例化对象,必须要有空构造器。
2、静态工厂实例化,在工厂类中定义静态方法,实例化对象。
3、实例工厂实例化,在工厂类中定义普通方法,实例化对象。
  • spring核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--可配置多个bean对象-->
     <!--配置构造器实例化-->
    <bean id="userDao" class="com.zwf.sevice.UserDao"></bean>
     <!--静态工厂实例化-->
    <bean id="roleDao" class="com.zwf.staticDemo.StaticFactoryDemo" factory-method="getInstance"></bean>
     <!--实例工厂实例化-->
      <!--实例化工厂-->
    <bean id="instanceFactory" class="com.zwf.instance.InstanceFactory" factory-bean="instanceDao"></bean>
    <bean id="instanceDao" factory-bean="instanceFactory" factory-method="getInstance"></bean>
</beans>
  • 测试代码
//定义实例化工厂
public class InstanceFactory {

    public InstanceDao getInstance(){
        return new InstanceDao();
    }
}

//定义静态工厂
public class StaticFactoryDemo {


    public static RoleDao getInstance(){

        return new RoleDao();
    }


}
  • 总结
1、构造器实例化用于Bean的缺省构造函数,当各个bean关联较少,比较独立的时候使用。
2、静态工厂实例化,方便在一个工厂统一实例化,用于统一管理Bean对象,一个静态方法就是一个Bean实例化。
3、实例工厂实例化,把工厂类实例化,在bean类中引入工厂类的对象实例化方法。可以把Bean和factory进行互换。可以用于引入其他框架管理方法。

3、模拟spring获取Bean对象的方式

基本思路:
1、定义一个实体类,用封装id和class属性值。
2、编写一个bean xml核心配置文件。
2、定义一个工厂接口,定义getBean()抽象方法。
3、编写实现类接口,实现工厂接口,dom4j解析xml配置文件,获取xml文件中的id和class属性值,封装在实体类中,然后把多个实体类,添加到集合中。
4、把集合中的实体类遍历出来获取class属性值字符串,利用反射进行实例化,然后把实例化的对象和遍历出来的实体类Id属性值存放在Map中。
5、获取Map的Id值获取Bean实例化对象。
[注意]整个实例化过程都是单例模式,也就是只实例化一次,获取两个相同的bean对象是同一个地址。
  • 模拟核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="userDao" class="com.zwf.dao.UserDao"></bean>
    <bean id="roleDao" class="com.zwf.dao.RoleDao"></bean>
</beans>
  • 模拟工厂类
public interface MyApplicationContext {

    Object getBean(String id);

}



package com.zwf.bean;

import com.zwf.pojo.BeanPojo;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-08-30 10:38
 */
public class MyClassPathApplicationContext implements MyApplicationContext{
      //存放解析后的Id和类实例化对象
    private Map<String,Object> beanMap=new HashMap<>();
     //存放解析后的Id和class全类名
    private List<BeanPojo> lists=null;

    //构造器加载配置文件和解析配置文件
    public MyClassPathApplicationContext(String fileName){
        //解析配置文件
        this.XMLParse(fileName);
        //实例化对象
        this.classNews();

    }

    private void classNews() {
        for(BeanPojo beans:lists){
            String id = beans.getId();
            String clazz = beans.getClazz();
            try {
                Object o = Class.forName(clazz).newInstance();
                beanMap.put(id,o);
            }catch (Exception e){
                e.printStackTrace();
            }
        }


    }

    private void XMLParse(String fileName) {
        //获取配置文件URL
        URL resource = this.getClass().getClassLoader().getResource(fileName);
        //对配置文件进行解析
        try{
            SAXReader reader  = new SAXReader();
            //读取配置文件URL 获取文档
            Document read = reader.read(resource);
            //获取根元素
            Element rootElement = read.getRootElement();
            List<Element> bean = rootElement.elements();
              if(bean!=null||bean.size()>0){
                  lists= new ArrayList<>();
                  for(Element element:bean){
                      //获取子节点的id属性值
                      String id = element.attributeValue("id");
                      //获取子节点的class属性值
                      String clazz = element.attributeValue("class");
                      BeanPojo beans=new BeanPojo(id,clazz);
                      lists.add(beans);
                  }
              }

        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}
  • 实体类
package com.zwf.pojo;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-08-30 10:34
 */
public class BeanPojo {
    //配置文件的Id
    private String id;
    //配置文件的Clazz
    private String clazz;

    public BeanPojo(String id, String clazz) {
        this.id = id;
        this.clazz = clazz;
    }

    public BeanPojo() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }
}

4、SpringIOC-Set方法注入

1、set Bean对象依赖注入。
2、set 基本数据类型属性值注入。
3、set 集合类型属性值注入
4、set Map类型属性值注入
5、set properties对象属性值注入
6、运行机制:先实例化往注入的类,再实例化被注入的类,然后往注入的类调用setter方法注入对象。
  • 核心配置文件
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
    <!--要注入的Dao对象 set方式Bean对象依赖注入-->
    <bean id="baseDao" class="com.zwf.dao.BaseDao"></bean>
    <!--往service对象注入-->
    <bean id="baseService" class="com.zwf.service.BaseService">
        <property name="baseDao" ref="baseDao"/>
        <!--基本数据类型注入 比如String Integer-->
        <property name="host" value="localhost"/>
        <property name="port" value="8080"/>
        <!--List注入 set注入-->
        <property name="list" >
            <list>
                <value>one</value>
                <value>two</value>
                <value>three</value>
                <value>four</value>
                <value>five</value>
            </list>
        </property>

        <property name="set">
            <set>
                <value>set one</value>
                <value>set two</value>
                <value>set three</value>
                <value>set four</value>
                <value>set five</value>
            </set>
        </property>
         <!--properties注入-->
        <property name="properties">
            <props>
                <prop key="username">root</prop>
                <prop key="password">admin</prop>
            </props>
        </property>
           <!--Map注入-->
        <property name="maps">
            <map>
                <entry>
                    <key><value>MYSQL url</value></key>
                    <value>jdbc:mysql://localhost:3306/notebook</value>
                </entry>
                <entry>
                    <key><value>ORACLE url</value></key>
                    <value>jdbc:oracle:thin:@localhost:5621:ORCL</value>
                </entry>
            </map>
        </property>
    </bean>
</beans>
  • 测试代码
package com.zwf.service;

import com.zwf.dao.BaseDao;
import org.w3c.dom.ls.LSOutput;

import java.util.*;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-08-30 16:22
 */
public class BaseService {
    //设置注入对象
    private BaseDao baseDao;
    //设置setter方法
    public void setBaseDao(BaseDao baseDao) {
        this.baseDao = baseDao;
    }

    private String host;

    public void setHost(String host) {
        this.host = host;
    }

    private Integer port;

    public void setPort(Integer port) {
        this.port = port;
    }
    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }

    private Set<String> set;

    public void setSet(Set<String> set) {
        this.set = set;
    }

    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
      //不能使用HashMap 只能用接口
    private Map<String,Object> maps;

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public void printlnT(){
        //执行方法
        baseDao.setPrint();
        System.out.println("主机名:"+host);
        System.out.println("端口号:"+port);
        list.forEach(System.out::println);
        set.forEach(System.out::println);
        System.out.println(maps.get("ORACLE url"));
        System.out.println(properties.getProperty("username"));
    }
}

5、构造器注入

1、可以实现一个或多参构造器对象注入。
2、也可实现基本数据类型参数注入。
3、遇到循环注入,就无法使用,必须set注入方式。
4、构造器注入流程:先实例化要被注入的对象,再实例化往注入的对象,实现注入。
  • 配置文件核心
<!--构造器注入
       可以实现对象间注入。
       可以实现基本数据类型注入。
       如果碰到循环注入,就会报错,必须使用set方法注入。
       循环注入就是类与类之间相互构造器注入。
    -->
    <bean id="constructorDao" class="com.zwf.dao.ConstructorDao"></bean>
    <bean id="constructorDao1" class="com.zwf.dao.ConstructorDao1"></bean>
    <bean id="constructorService" class="com.zwf.service.ConstructorService">
        <constructor-arg name="constructorDao" ref="constructorDao"/>
        <constructor-arg name="constructorDao1" ref="constructorDao1"/>
        <!--基本数据类型注入-->
        <constructor-arg name="encoding" value="encoding"/>
    </bean>
  • 测试代码
package com.zwf.service;

import com.zwf.dao.ConstructorDao;
import com.zwf.dao.ConstructorDao1;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-08-30 17:50
 */
public class ConstructorService {
    private ConstructorDao constructorDao;
    private ConstructorDao1 constructorDao1;
     //多参构造注入
    private String Encoding;
    public ConstructorService(ConstructorDao constructorDao, ConstructorDao1 constructorDao1,String encoding) {
        this.constructorDao = constructorDao;
        this.constructorDao1 = constructorDao1;
        this.Encoding=encoding;
    }
     //单参构造注入
    public ConstructorService(ConstructorDao constructorDao) {
        this.constructorDao=constructorDao;
    }

    public void toPrint(){
        constructorDao.printWord();
        constructorDao1.printWord();
        System.out.println("执行了Service方法……");
    }
}

6、静态工厂模式实例化和实例工厂模式实例化set注入

1、静态工厂模式实例化和实例工厂模式实例化是自己定义类用来管理Bean对象,不交给IOC容器管理。
2、set注入跟前面一样,先定义再相应的实例化对象,再写Setter方法进行依赖注入。
  • 核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--静态工厂模式实例化-->
    <bean id="baseDao" class="com.zwf.factory.StaticFactory" factory-method="getBaseDao"></bean>
    <!--实例工厂模式实例化 先实例化 再引用工厂和方法-->
    <bean id="commonFactory" class="com.zwf.factory.CommonFactory"></bean>
    <bean id="constructorDao" factory-bean="commonFactory" factory-method="getConStructorDao"></bean>

    <!--静态工厂模式和实例工厂模式Set注入-->
    <bean id="factoryService" class="com.zwf.service.FactoryService">
        <property name="baseDao" ref="baseDao"/>
        <property name="constructorDao" ref="constructorDao"/>
    </bean>

</beans>
  • 测试代码
public class FactoryService {
    private BaseDao baseDao;
    private ConstructorDao constructorDao;

    public void setBaseDao(BaseDao baseDao) {
        this.baseDao = baseDao;
    }

    public void setConstructorDao(ConstructorDao constructorDao) {
        this.constructorDao = constructorDao;
    }

    public void printIN(){
        baseDao.setPrint();
        constructorDao.printWord();
        System.out.println("工厂模式注入!!");
    }
}

7、P命名空间的使用

  • 核心配置文件
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--静态工厂模式实例化-->
    <bean id="baseDao" class="com.zwf.factory.StaticFactory" factory-method="getBaseDao"></bean>
    <!--实例工厂模式实例化 先实例化 再引用工厂和方法-->
    <bean id="commonFactory" class="com.zwf.factory.CommonFactory"></bean>
    <bean id="constructorDao" factory-bean="commonFactory" factory-method="getConStructorDao"></bean>

    <!--静态工厂模式和实例工厂模式 p标签引入-->
    <bean id="factoryService" class="com.zwf.service.FactoryService" p:baseDao-ref="baseDao" p:constructorDao-ref="constructorDao" p:name="zs"></bean>
</beans>

8、注解自动注入

1、@Resource([name=" "])注解首先通过属性名匹配核心配置文件中的bean标签中的Id值,如果匹配不到,就匹配class类型。如果使用name属性可以指定名称与Id值匹配,就不会匹配class类型,可以在属性值上声明,也可以在setter方法上声明,有无setter方法都生效。
2、@Autowired注解只会匹配class类型,不会匹配名字,如果一个接口的多个实现类实例化进行注入,要使用@Qualifier([value=""])指定实现类Id的名字进行注入,可以在属性值上声明,也可以在setter方法上声明,有无setter方法都生效。
  • 核心配置文件(开启注解)
<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--静态工厂模式实例化-->
    <bean id="baseDao" class="com.zwf.factory.StaticFactory" factory-method="getBaseDao"></bean>
    <!--实例工厂模式实例化 先实例化 再引用工厂和方法-->
    <bean id="commonFactory" class="com.zwf.factory.CommonFactory"></bean>
    <bean id="constructorDao" factory-bean="commonFactory" factory-method="getConStructorDao"></bean>
    <bean id="factoryService" class="com.zwf.service.FactoryService"></bean>
     <!--开启注解配置  自动注入-->
    <context:annotation-config/>

</beans>
  • 测试代码
package com.zwf.service;

import com.zwf.dao.BaseDao;
import com.zwf.dao.ConstructorDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.Resource;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-08-30 19:44
 */
public class FactoryService {
    /*1、先通过属性名去匹配Id值
      如果Id值找不到,就匹配class类型
      2、可以声明在属性上,也可以声明在setter方法上
      3、也可以通过指定name属性与Id值匹配    @Resource(name="Id值")
      4、可以使用setter方法也可以不使用setter方法
     */
    @Resource
    private BaseDao baseDao;
    @Autowired
//    @Qualifier(value = "constructorDao")
    private ConstructorDao constructorDao;
     @Value("zhangsan")
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void setBaseDao(BaseDao baseDao) {
        this.baseDao = baseDao;
    }

    public void setConstructorDao(ConstructorDao constructorDao) {
        this.constructorDao = constructorDao;
    }

    public void printIN(){
        baseDao.setPrint();
        constructorDao.printWord();
        System.out.println("工厂模式注入!!"+"******"+name);
    }
}

9、开启注解包扫描

在核心配置文件中,要开启扫描包配置,扫描对应的包名,被扫描的包下所有的类注解生效,不需要在配置文件中使用bean标签实例化。
@Repository一般用于声明在dao层类上,@Service一般用于声明在Service层类上,@Controller一般用于Controller层类上,都表示实例化对象,可以通过name属性指定Id名字进行调用,默认id名是类名的首字母小写。
@Component用于任何类上。表示类的实例化,一样可以通过name属性指定Id名,进行调用。
  • 核心配置文件
<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context           https://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启注解扫描 扫描com.zwf包下所有类  注解都生效  不用bean实例化 直接使用注解实例化-->
    <context:component-scan base-package="com.zwf"></context:component-scan>
</beans>
  • 测试代码
@Repository
public class BaseDao {

    public void setPrint(){
        System.out.println("set依赖注入成功!");
    }
}

package com.zwf.service;

import com.zwf.dao.BaseDao;
import com.zwf.dao.ConstructorDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-08-30 19:44
 */
@Service
public class FactoryService {
    @Resource
    private BaseDao baseDao;
    @Autowired
//    @Qualifier(value = "constructorDao")
    private ConstructorDao constructorDao;
     @Value("zhangsan")
    private String name;
     
    public void printIN(){
        baseDao.setPrint();
        constructorDao.printWord();
        System.out.println("工厂模式注入!!"+"******"+name);
    }
}

10、Bean作用域与生命周期

1、Bean作用域(Scope):singleton(单例模式):Bean对象初始化后会放入Bean缓存区中,下次获取Bean对象的时候,直接去缓存区加载,不会实例化。  prototype(原型模式):Bean对象初始化后不会放入Bean缓存区中,每次获取Bean对象的时候,都会实例化一次再加载。
2、懒加载:lazy-init  使用这个Bean时进行实例化,不使用不会实例化。默认是关闭懒加载,定义的Bean对象都会实例化,这样可以避免在使用前发现代码的错误。
3、Bean生命周期:定义-->初始化-->使用-->销毁
实例化:当启动Spring应用时,IOC容器就会为在配置文件中声明的每个<bean>创建一个实例。
属性赋值:实例化后,Spring就通过反射机制给Bean的属性赋值。
调用初始化方法:如果Bean配置了初始化方法,Spring就会调用它。初始化方法是在Bean创建并赋值之后调用,可以在这个方法里面写一些业务处理代码或者做一些初始化的工作。
Bean运行期:此时,Bean已经准备好被程序使用了,它已经被初始化并赋值完成。
应用程序关闭:当关闭IOC容器时,Spring会处理配置了销毁方法的Bean。
调用销毁方法:如果Bean配置了销毁方法,Spring会在所有Bean都已经使用完毕,且IOC容器关闭之前调用它,可以在销毁方法里面做一些资源释放的工作,比如关闭连接、清理缓存等。
4、init-method:监听IOC容器初始化前进行一些功能的操作,Destroy-method:监听IOC容器销毁进行一些销毁之前的操作。
<bean id="[Bean对象的名字]"  class="[类全路径名字]" init-method="[初始化方法]" destroy-method="[销毁方法]"/>
在bean类中定义方法进行初始化和销毁绑定!
  • 注解方式实现销毁和初始化方法的定义
@Configuration
public class LazyConfig {
    //  destroyMethod 设置监听销毁的方法
    //  initMethod 设置监听初始化的方法
    @Bean(name ="loginDao",destroyMethod = "destroy",initMethod = "init")
//    @Lazy   //懒加载
    @Scope
    public LoginDao getInstance(){
        return new LoginDao();
    }
    @Bean
    public User getUser(){
        return new  User();
    }
}

-----------------------------------------------------------------------------------------
/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-09-01 8:47
 */
public class LoginDao {

   private Map<String,Object> maps=new HashMap<>();

    public LoginDao() {
        System.out.println("hhhhhhheeeee");
    }

    public void init(){
        System.out.println("Bean对象初始化!!");
    }
    public void destroy(){
        System.out.println("Bean对象销毁了!!!");
    }

  
}

11、代理模式(AOP切面编程的原理)

  • 静态代理模式
代理模式相当于目标类和代理类实现同样的接口和功能,但是代理类可以对目标类的功能进行加强。好比我们租房子,房东提供房源,租房中介相当于代理,我们租房子与中介联系。但是这样的模式有一个缺陷,如果类太多了,我们需要定义很多类就会出现类爆炸。
  • 模拟代码
//功能接口
public interface GetMarry {

   String toMarry();
}
//目标对象类
public class GetMarryImpl implements GetMarry{
    @Override
    public String toMarry() {
        System.out.println("结婚啦……!");
        return "hello";
    }
}


//目标行为代理类
public class StaticProxy implements GetMarry {

    //注入行为接口 进行目标对象行为加强
    private GetMarry getMarry;
    public StaticProxy (GetMarry getMarry){
        this.getMarry=getMarry;
    }

    @Override
    public String toMarry() {
        //目前行为前操作
        marryBefore();
        this.getMarry.toMarry();
        //目前行为后操作
        marryAfter();

        return "hello";
    }

    private void marryAfter() {
        System.out.println("结婚前………………");
    }

    private void marryBefore() {
        System.out.println("结婚后………………");
    }
}


//我们使用的时候,通过代理类构造器,传入目标对象,再调用代理类的接口方法。
  • 动态代理模式
1、动态代理模式有两种:JDK代理模式和CgLib代理模式。
2、JDK代理模式和cglib代理模式区别:JDK代理模式只能代理接口实现中的方法,而cglib代理模式有无接口实现类都可以实现,代理比较灵活,但是JDK代理模式效率比cglib代理模式效率高。
3、JDK代理模式底层是调用反射中Proxy类中newInstanceProxy()方法实现对象的复用代理,代理类继承Proxy类,实现目标对象接口,然后通过InvocationHandler接口实现类调用方法执行代理对象的方法进行目标对象功能加强。
4、cglib代理模式是通过继承目标对象类,然后回调代理对象中的拦截器,进行功能加强,最终返回Object对象方法。功能比较灵活。
  • JDK动态代理类测试代码
//JDK动态代理类
public class DynProxy {
    private Object obj;
    //委托对象
    public DynProxy(Object o){
        this.obj=o;
    }

    public Object getDynProxy(){
        //调用反射动态代理类 执行动态对象方法 JDK动态代理
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),obj.getClass().getInterfaces(),new MyInvocationHandle(obj));
    }
}


public class MyInvocationHandle implements InvocationHandler {

    private Object obj;
    //委托对象
    public MyInvocationHandle(Object o){
        this.obj=o;
    }
    //动态代理执行方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("目标对象行为前……");
        Object o=method.invoke(obj,args);
        System.out.println("目标对象行为后……");
        return o;
    }
}

  • cglib动态代理对象测试代码
//cglib代理对象

/**
 * cglib 动态代理基本原理:(运用反射实现对象的通用)
 * 代理类继承目标对象,对目标对象进行拦截并
 * 对功能加强执行目标对象方法及前后操作,最后返回目标对象方法的返回值。
 * 可以对非接口实现类进行代理,但是效率比JDK动态代理低。
  */
public class CgLibProxy implements MethodInterceptor {

    private Object obj;
    //注入目标对象
    public CgLibProxy(Object o){
        this.obj=o;
    }
    //功能增强方法
    public Object enchanceProxy(){
        Enhancer enhancer = new Enhancer();
        //把目标对象设置为父类

        //执行回调方法 也就intercept方法
        enhancer.setCallback(this);
        //返回创建代理对象
      return  enhancer.create();
    }

    /**
     *  拦截器
     * @param o 目标对象
     * @param method 目标对象的方法
     * @param objects 目标对象传入方法的参数
     * @param methodProxy 代理对象方法
     * @return
     * @throws Throwable
     */

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理对象前……");
        Object result = methodProxy.invoke(obj, objects);//执行代理对象的方法
        System.out.println("代理对象后……");
        return result;
    }
}

12、AOP切面编程

  • 基本概念
1、AOP切面编程一般基于动态代理模式原理,在不改变原代码情况下,动态对mvc层中任何一层进行功能加强,一般用于性能监测,事务操作等应用。
2、xml配置方式与注解配置方式通知执行的顺序不同,xml配置方式执行顺序与xml编写顺序有关,一般把环绕通知放在最前面,最终通知与方法返回后通知执行顺序与xml配置顺序有关,谁在前执行谁。而注解配置方式,先执行环绕通知,环绕前置通知先执行,然后执行前置通知、方法返回通知、最终通知、环绕方法返回通知、环绕后置通知,如果有异常执行异常通知,不执行方法返回通知,无异常执行方法返回通知。
3、切点(pointCut):定义目标对象的特定方法规则,如:* com.zwf.service..*(..) service层中所有方法。
   织入:切面引入MVC中层中实例化形成代理类对象的过程。
   引入:在不改变原代码的情况下,操作方法和属性赋值。
   切面(aspect):切点和通知形成的类叫切面。
   连接点(joinPoint):就是我们要加强的目标对象方法和类。
  • xml配置文件使用方式
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context
	   https://www.springframework.org/schema/context/spring-context.xsd
	   http://www.springframework.org/schema/aop
	   https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启注解扫描 扫描com.zwf包下所有类  注解都生效  不用bean实例化 直接使用注解实例化-->
     <context:component-scan base-package="com.zwf"/>
    <!--手动配置aop切面编程-->
    <aop:config>
        <!--配置切面 ref="切面对象id"-->
        <aop:aspect ref="xmlAspect" >
            <!--配置切点-->
            <aop:pointcut id="pointCut" expression="execution(* com.zwf.Dao..*(..))"/>
              <!--配置环绕通知 一般配置在最前面-->
            <aop:around method="RoundAdvistor" pointcut-ref="pointCut"></aop:around>
             <!--配置方法返回后通知-->
            <aop:after-returning method="methodReturn" pointcut-ref="pointCut"></aop:after-returning>
             <!--配置后置通知-->
            <aop:after method="afterMessage" pointcut-ref="pointCut"></aop:after>
             <!--配置前置通知-->
            <aop:before method="beforeMessage" pointcut-ref="pointCut"></aop:before>
            <!--配置异常通知-->
            <aop:after-throwing method="exceptionAdvice" throwing="e" pointcut-ref="pointCut"></aop:after-throwing>
        </aop:aspect>
    </aop:config>

</beans>
  • 测试代码
@Component
public class XmlAspect{
     //配置切面交给XML文件
    //设置切点
    /**
     * 表达式分析:
     * 第一个: * 表示修饰符:public protect private
     * 第二个: com.zwf.service 或者 * 表示指定的包名或者所有包
     * 第三个: .. 表示包下所有类及其子包下所有类
     * 第四个:*表示类中所有方法
     * 第五个:(..)表示任意个数的参数 ()没有参数
     * 常用几个表达式:
     *  * *(..) 所有类的方法
     *  public *(..) 所有公有修饰符的方法
     *  * com.zwf.service *(..)  所有service包下的方法
     *  * *() 所有包下的无参方法
     */
    //前置通知  @Before
    public void beforeMessage(){
        System.out.println("前置通知生效!!");
    }
    //后置通知 @After
    public void afterMessage(){
        System.out.println("后置通知生效!!");
    }
    //方法返回后通知 @AfterReturning 出现异常失效
    public void methodReturn(){
        System.out.println("方法执行后返回生效……");
    }
    //异常通知 @AfterThrowing 出现异常时生效
    public void exceptionAdvice(Exception e){
        System.out.println("异常通知时生效……"+e.getMessage());
    }
    //环绕通知 在所有通知前生效 可以有返回值
    public Object RoundAdvistor(ProceedingJoinPoint pjp){
        //获取目标对象
        Object proceed = null;
        try {
            System.out.println("环绕前置通知……");
           proceed = pjp.proceed();
            System.out.println("环绕方法返回后置通知……");
        } catch (Throwable e) {
                System.out.println("环绕异常通知……"+e.getMessage());
//                throw new RuntimeException(e);
        }
        System.out.println("环绕后置通知……");
        return proceed;
    }

}
  • 注解方式测试代码
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context
	   https://www.springframework.org/schema/context/spring-context.xsd
	   http://www.springframework.org/schema/aop
	   https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--开启注解扫描 扫描com.zwf包下所有类  注解都生效  不用bean实例化 直接使用注解实例化-->
     <context:component-scan base-package="com.zwf"/>
    <!--开启自动代理-->
    <aop:aspectj-autoproxy/>
</beans>
  • 测试代码
package com.zwf.area;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-09-01 18:00
 */

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 编写切面  切面由切点+消息通知
 * 通知:前置通知  后置通知  方法返回通知 异常通知  环绕通知
 */
@Component
@Aspect   //此类是切面类
public class CutArea {

    //设置切点
    /**
     * 表达式分析:
     * 第一个: * 表示修饰符:public protect private
     * 第二个: com.zwf.service 或者 * 表示指定的包名或者所有包
     * 第三个: .. 表示包下所有类及其子包下所有类
     * 第四个:*表示类中所有方法
     * 第五个:(..)表示任意个数的参数 ()没有参数
     * 常用几个表达式:
     *  * *(..) 所有类的方法
     *  public *(..) 所有公有修饰符的方法
     *  * com.zwf.service *(..)  所有service包下的方法
     *  * *() 所有包下的无参方法
     */
    @Pointcut(value = "execution(* com.zwf.Dao..*(..))")
    public void cutPoint(){

    }

    //前置通知  @Before
    @Before(value = "cutPoint()")
    public void beforeMessage(){
        System.out.println("前置通知生效!!");
    }
    //后置通知 @After
    @After(value = "cutPoint()")
    public void afterMessage(){
        System.out.println("后置通知生效!!");
    }
    //方法返回后通知 @AfterReturning 出现异常失效
    @AfterReturning(value = "cutPoint()")
    public void methodReturn(){
        System.out.println("方法执行后返回生效……");
    }
    //异常通知 @AfterThrowing 出现异常时生效
    @AfterThrowing(value = "cutPoint()",throwing = "e")
    public void exceptionAdvice(Exception e){
        System.out.println("异常通知时生效……"+e.getMessage());
    }
    //环绕通知 在所有通知前生效 可以有返回值
    @Around(value = "cutPoint()")
    public Object RoundAdvistor(ProceedingJoinPoint pjp){
        //获取目标对象
        Object proceed = null;
        try {
            System.out.println("环绕前置通知……");
            proceed = pjp.proceed();
            System.out.println("环绕方法返回后置通知……");
        } catch (Throwable e) {
            System.out.println("环绕异常通知……"+e.getMessage());
//            throw new RuntimeException(e);
        }
        System.out.println("环绕后置通知……");
        return proceed;
    }
}

13、SpringJdbc与事务

1、Spring提供了JDBC模式类,可以对数据库中的数据表数据进行增删改查操作,但是我们一般使用mybatis对数据库进行操作,了解即可。
2、Spring还提供了事务管理,spring不直接管理事务,而是通过事务管理对事务进行操作,常见的事务管理器有JPA、Hibernate、DataSource、Jta(原生事务管理器),JDBC使用数据源事务管理器。
3、事务实现ACID原则保证了数据的安全性,避免代码操作异常带来的损失,如:银行转账如果出现转账异常失败,没有使用事务操作,就会出现转出账户钱扣了,收款账户钱没有得到的损失局面。
ACID原则:原子性、一致性、隔离性、持久性
原子性:要么数据库操作同时成功或者同时失败。
一致性:所有的数据表数据都要同步一致。
隔离性:在事务执行前后的操作,不干预事务操作。
持久性:数据持久化保存,不会因断电关机丢失。
隔离等级:读未提交、读已提交、可重复读、串行读
读未提交:可脏读、不可重读读、可幻读
读已提交:不可脏读、不可重复读、可幻读 (mysql、Oracle默认)
可重复读:不可脏读、可重复读、可幻读 (锁行)
串行读:不可脏读、可重复读、不可幻读 (锁表)

image-20230903175510349

  • 需要导入的依赖
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.22</version>
        </dependency>
        <!--Aop织入包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.9.1</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--事务提交-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.22</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.22</version>
        </dependency>
        <!--c3p0-->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
        <!--springAop-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.22</version>
        </dependency>
        <!--spring单元测试-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.22</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
  • DataSourceTransAction配置方式
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="c3p0"></property>
    </bean>
  • Hibernate 事务
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.Hiberna
teTransactionManager">
 <property name="sessionFactory"
ref="sessionFactory" />
</bean>
  • Java 持久化 API 事务(JPA)
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransaction
Manager">
 <property name="sessionFactory"
ref="sessionFactory" />
</bean>
  • Java 原生 API 事务
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTra
nsactionManager">
 <property name="transactionManagerName"
value="java:/TransactionManager" />
</bean
  • jdbc事务配置方式
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context
	   https://www.springframework.org/schema/context/spring-context.xsd
	   http://www.springframework.org/schema/aop
	   https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
	   https://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--开启注解扫描 扫描com.zwf包下所有类  注解都生效  不用bean实例化 直接使用注解实例化-->
    <context:component-scan base-package="com.zwf"/>
    <!--加载properties配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--配置数据库连接池-->
     <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
         <property name="user" value="${jdbc.user}"></property>
         <property name="password" value="${jdbc.pwd}"></property>
         <property name="jdbcUrl" value="${jdbc.url}"></property>
         <property name="driverClass" value="${jdbc.driver}"></property>
         <!--初始化连接数量 默认是3 应该在minPoolSize和maxPoolSize之间-->
         <property name="initialPoolSize" value="20"></property>
         <property name="maxPoolSize" value="50"></property>
         <property name="minPoolSize" value="10"></property>
         <property name="maxIdleTime" value="60"></property>
         <property name="maxStatementsPerConnection" value="5"></property>
     </bean>
    <!--配置jdbc模板 引入c3p连接池-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="c3p0"></property>
    </bean>
    <!--配置数据源事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="c3p0"></property>
    </bean>
    <!--配置事务级别和传播类别
      tx:method的属性:
       name
			是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。
	   		通配符(*)可以用来指定一批关联到相同的事务属性的方法。
       			如:'get*'、'handle*'、'on*Event'等等.
       propagation
			不是必须的,默认值是REQUIRED 有事务使用事务 没事务创建事务
       		表示事务传播行为, 包括:
				REQUIRED,SUPPORTS,MANDATORY,NEVER
				REQUIRES_NEW,NOT_SUPPORTED,NESTED
       isolation
			不是必须的,默认值DEFAULT
            表示事务隔离级别(数据库的隔离级别)
       timeout
			不是必须的,默认值-1(永不超时)
            表示事务超时的时间(以秒为单位)
       read-only
			不是必须的,默认值false不是只读的
            表示事务是否只读
       rollback-for
			不是必须的
            表示将被触发进行回滚的 Exception(s);以逗号分开。
            	如:'com.foo.MyBusinessException,ServletException'
       no-rollback-for
			不是必须的
            表示不被触发进行回滚的 Exception(s);以逗号分开。
            	如:'com.foo.MyBusinessException,ServletException'
            	任何 RuntimeException 将触发事务回滚
    -->
    <tx:advice transaction-manager="transactionManager"  id="transAction">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="out*" propagation="REQUIRED"/>
            <tx:method name="in*" propagation="REQUIRED"/>
            <tx:method name="transform*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="query*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!--配置Aop事务切入-->
    <aop:config>
        <aop:advisor advice-ref="transAction" pointcut-ref="daoPoint" id="advisor"></aop:advisor>
        <aop:aspect >
            <!--配置切点-->
            <aop:pointcut id="daoPoint" expression="execution(* com.zwf.service..*(..))"/>
            <!--配置消息通知-->
        </aop:aspect>
    </aop:config>
    <!--开启注解事务-->
    <tx:annotation-driven/>
</beans>
  • JdbcTemplate基本使用
package com.zwf.dao.imp;

import com.zwf.dao.DataBao;
import com.zwf.pojo.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;


/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-09-02 15:17
 */
@Repository
public class DataBaoImp implements DataBao {
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Override
    //添加数据返回受影响行数
    public Integer addAccountToRow(Account account) {
        String sql="insert into spring_jdbc(account_name,account_type,money,remark,create_time,update_time,user_id) values(?,?,?,?,?,?,?) ";
        int row = jdbcTemplate.update(sql, new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {
                ps.setString(1, account.getAccountName());
                ps.setString(2, account.getAccountType());
                ps.setDouble(3, account.getMoney());
                ps.setString(4, account.getRemark());
                ps.setDate(5,new Date(System.currentTimeMillis()));
                ps.setDate(6,new Date(System.currentTimeMillis()));
                ps.setInt(7, account.getUserId());
            }
        });
        return row;
    }
   //添加数据返回主键值
    @Override
    public Integer addAccountToHashKey(Account account) {
        String sql="insert into spring_jdbc(account_name,account_type,money,remark,create_time,update_time,user_id) values(?,?,?,?,?,?,?) ";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement pst = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                pst.setString(1, account.getAccountName());
                pst.setString(2, account.getAccountType());
                pst.setDouble(3, account.getMoney());
                pst.setString(4, account.getRemark());
                pst.setDate(5,new Date(System.currentTimeMillis()));
                pst.setDate(6,new Date(System.currentTimeMillis()));
                pst.setInt(7, account.getUserId());
                return pst;
            }
        },keyHolder);
         Number key = keyHolder.getKey();
        int i = key.intValue();
        return i;
    }
    //批量添加数据,返回受影响行数
    @Override
    public Integer addBatchAccountToRow(List<Account> accounts) {
        String sql="insert into spring_jdbc(account_name,account_type,money,remark,create_time,update_time,user_id) values(?,?,?,?,?,?,?) ";
      Integer count= jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setObject(1,accounts.get(i).getAccountName());
                ps.setObject(2,accounts.get(i).getAccountType());
                ps.setObject(3,accounts.get(i).getMoney());
                ps.setObject(4,accounts.get(i).getRemark());
                ps.setDate(5,new Date(System.currentTimeMillis()));
                ps.setDate(6,new Date(System.currentTimeMillis()));
                ps.setObject(7,accounts.get(i).getUserId());
            }

          @Override
          public int getBatchSize() {
              int size = accounts.size();
              return size;
          }
      }).length;
        return count;
    }
 //通过ID查询数据表数据个数
    @Override
    public Integer queryAccountCountById(Integer userId) {
        String sql="select count(1) from spring_jdbc where user_id=? ";
//        Object[] objects={accountId};
        Integer i = jdbcTemplate.queryForObject(sql, Integer.class, userId);
        return i;
    }
   //通过ID查询数据表中的数据集合
    @Override
    public List<Account> queryAccountById(Integer userId) {
        String sql="select * from spring_jdbc where user_id=? ";
      List<Account> lists=jdbcTemplate.query(sql, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                Account account=new Account();
                account.setAccountId(rs.getInt(1));
                account.setAccountName(rs.getString(2));
                account.setAccountType(rs.getString(3));
                account.setMoney(rs.getDouble(4));
                account.setRemark(rs.getString(5));
                account.setCreateTime(rs.getDate(6));
                account.setUpdateTime(rs.getDate(7));
                account.setUserId(userId);
                return account;
            }
        },userId);
        return lists;
    }
    //多条件查询
    @Override
    public List<Account> queryAccountByParams(Integer userId,String accountName, String accountType, String remark) {
        String sql="select * from spring_jdbc where user_id=? ";
        List<Object> list=new ArrayList<>();
        list.add(userId);
            if(StringUtils.isNotBlank(accountName)){
                sql+=" and  account_name=? ";
                list.add(accountName);
            }
            if(StringUtils.isNotBlank(accountType)){
                sql+=" and accountType=? ";
                list.add(accountType);
            }
            if(StringUtils.isNotBlank(remark)){
                sql+=" and remark=? ";
                list.add(remark);
            }
        Object[] params = list.toArray();
          List<Account> lists= jdbcTemplate.query(sql, params, new RowMapper<Account>() {
                @Override
                public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Account account=new Account();
                    account.setAccountId(rs.getInt(1));
                    account.setAccountName(rs.getString(2));
                    account.setAccountType(rs.getString(3));
                    account.setMoney(rs.getDouble(4));
                    account.setRemark(rs.getString(5));
                    account.setCreateTime(new Timestamp(rs.getDate(6).getTime()));
                    account.setUpdateTime(new Timestamp(rs.getDate(7).getTime()));
                    account.setUserId(rs.getInt(8));
                    return account;
                }
            });

        return lists;
    }
   //通过Id更新数据
    @Override
    public Integer updateAccountById(Account account) {
        String sql="update spring_jdbc set account_name=?,account_type=?,money=?,remark=? where user_id=? ";
      Integer row=jdbcTemplate.update(sql, new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {
                ps.setString(1,account.getAccountName());
                ps.setString(2,account.getAccountType());
                ps.setDouble(3,account.getMoney());
                ps.setString(4,account.getRemark());
                ps.setInt(5,account.getUserId());
            }
        });


        return row;
    }
    //方法上加上事务注解  批量更新数据
     @Transactional(propagation= Propagation.REQUIRED )
    @Override
    public Integer updateBatchAccountById(List<Account> accounts, Integer[] ids) {
        String sql="update spring_jdbc set account_name=?,account_type=?,money=?,remark=?,user_id=? where account_id=? ";
       Integer row= jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                // i 表示遍历的迭代数
                ps.setString(1,accounts.get(i).getAccountName());
                ps.setString(2,accounts.get(i).getAccountType());
                ps.setDouble(3,accounts.get(i).getMoney());
                ps.setString(4, accounts.get(i).getRemark());
                ps.setInt(5,accounts.get(i).getUserId());
                ps.setInt(6,ids[i]);
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
       int a=1/0;
        return row;
    }
    //根据ID删除数据

    @Override
    public Integer deleteAccountById(Integer accountId) {
        String sql="delete from spring_jdbc where account_id=? ";
        int row = jdbcTemplate.update(sql, accountId);
        return row;
    }
   //根据多个Id批量删除数据返回受影响行数
    @Override
    public Integer deleteBatchById(Object[] ids) {
        String sql="delete from spring_jdbc where account_id=? ";
        int[] ints = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setObject(1,ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        });
        int row = ints.length;
        return row;
    }
  //模拟转账代码
    @Override
    public Integer outAccount(Integer accountId, Double money) {
        String sql="update spring_jdbc set money=money-? where account_id=? ";
        int row = jdbcTemplate.update(sql, money, accountId);

        return row;
    }

    @Override
    public Integer InAccount(Integer accountId, Double money) {
        String sql="update spring_jdbc set money=money+? where account_id=? ";
        int row = jdbcTemplate.update(sql, money, accountId);
        return row;
    }

}
  • 测试类
//使用注解配置提取共同代码
@ContextConfiguration(locations = {"classpath:springjdbc.xml"})
/*使用运行器运行 spring测试环境*/
@RunWith(value = SpringJUnit4ClassRunner.class)
public class test1 {
    @Resource
    private JdbcTemplate jdbcTemplate;

    public void test1(){
         //获取上下文配置文件
        //测试jdbc是否连通
//        BeanFactory beanFactory= new ClassPathXmlApplicationContext("springjdbc.xml");
//        JdbcTemplate jdbcTemplate = (JdbcTemplate) beanFactory.getBean("jdbcTemplate");
        String sql="select count(1) from spring_jdbc ";
        Integer i = jdbcTemplate.queryForObject(sql, Integer.class);
         String sql1="select count(1) from spring_jdbc where user_id=? ";
         Integer i1 = jdbcTemplate.queryForObject(sql1, Integer.class, 1);
         System.out.println(i);
         System.out.println(i1);

    }
}

14、SpringTask定时任务

  • xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context
	   https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/task
	   https://www.springframework.org/schema/task/spring-task.xsd">
      <!--开启注解扫描-->
    <context:component-scan base-package="com.zwf.dao"/>
    <!--开启定时任务-->
     <task:annotation-driven/>
</beans>
  • 测试代码
public class TaskTest {

    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
       TaskJob taskJob=  (TaskJob)context.getBean("taskJob");
      taskJob.task2();
      taskJob.task2();

        ApplicationContext context1 = new ClassPathXmlApplicationContext("spring1.xml");
        TaskJob taskJob1=  (TaskJob)context1.getBean("taskJob");
        taskJob1.task2();
        taskJob1.task2();
    }
}
  • 注解配置
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	   https://www.springframework.org/schema/beans/spring-beans.xsd
	   http://www.springframework.org/schema/context
	   https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/task
	   https://www.springframework.org/schema/task/spring-task.xsd">
      <!--开启注解扫描-->
    <context:component-scan base-package="com.zwf.dao"/>
    <!--开启定时任务-->
     <task:annotation-driven/>
</beans>
  • 测试代码 (注解配置)
@Component
public class TaskJob {
    private SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
    @Scheduled(cron = "30/5 * * * * *")
    public void task1(){
        String time = simpleDateFormat.format(new Date());
        System.out.println("[任务一]:"+time);
    }
    @Scheduled(cron = "15/5 * * * * *")
    public void task2(){
        String time = simpleDateFormat.format(new Date());
        System.out.println("[任务二]:"+time);
    }
}
  • cron表达式
关于 cronExpression 表达式有至少 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-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 , - * 
  • 规则:
"*" —— 字符可以用于所有字段,在"分"字段中设为"*",表示"每一分钟"的含义。

"?" —— 字符可以用在"日"和"周几"字段,它用来指定"不明确的值"。
这在你需要指定这两个字段中的某一个值而不是另外一个的时候会被用到。在后面的例子中可以看到其含义。

"-" —— 字符被用来指定一个值的范。
比如在"小时"字段中设为"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",则表示"日历中的第一天或者周日以后"。
对于"月份"字段和"周几"字段来说合法的字符都不是大小写敏感的。

15、邮件功能(spring mail、java mail)

一、java原生发送邮件

  • 导入依赖
  <dependency>
               <groupId>com.sun.mail</groupId>
               <artifactId>javax.mail</artifactId>
               <version>1.6.2</version>
           </dependency>
           <dependency>
               <groupId>javax.mail</groupId>
               <artifactId>javax.mail-api</artifactId>
               <version>1.6.2</version>
           </dependency>
  • 常用设置的properties配置类

image-20230907170930214

  • 简介
1、java mail邮件发送支持SMTP协议,接收协议:POP3或者IMAP,MiME是一种风格,相当于restFul风格,是一种文本标准格式。
2、邮件发送首先要在相应的邮件官网设置中开启支持SMTP/POP3协议,获取授权码,用户名就是邮箱地址@以前的名称,密码就是开启协议获得授权码,主机名是smtp.qq.com、smtp.sina.com、smtp.123.com等,端口号没有开启SSL安全验证的端口号是25,开启SSL安全验证端口号是465或者875。

image-20230907171721702

image-20230907171838294

  • 测试代码
package com.zwf.mail;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-09-07 15:29
 */

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.*;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

/**
 * 发送普通文本邮箱
 */
public class TestJavaMail {

    public static void main(String[] args) throws MessagingException, UnsupportedEncodingException {
        Properties pro=new Properties();
        //配置发送主机 端口  权限验证
        pro.setProperty("mail.smtp.host","smtp.qq.com");
        // 默认是25  465或587 前提要开启SSL安全验证
        pro.setProperty("mail.smtp.port","465");//465或587
        //开启SSL安全连接
        pro.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        pro.setProperty("mail.smtp.auth","true");
        //获取Session实例
        Session session = Session.getInstance(pro);
        //开启debug
        session.setDebug(true);
        Transport transport = session.getTransport();
        //设置文本
//        Message message=createMineMessage(session);
        //发送html文本
//        Message htmlMessage = createHtmlMessage(session);
        //发送带附件的文本
        Message attached =attachFileMessage(session);
        transport.connect("smtp.qq.com","[用户名:@前的名字]","[授权码:需要开启协议获得]");
        //发送所有的方式   To普通发送   CC抄送  BCC密送
        transport.sendMessage(attached,attached.getAllRecipients());
        transport.close();
    }
   //发送普通文本
    private static Message createMineMessage(Session session) throws MessagingException {
        MimeMessage mimeMessage = new MimeMessage(session);
        mimeMessage.setSubject("这是一封邮件!");
        mimeMessage.setSentDate(new Date());
        mimeMessage.setFrom(new InternetAddress("****@qq.com"));
        mimeMessage.setRecipient(Message.RecipientType.TO,new InternetAddress("*****@qq.com"));
        mimeMessage.setText("测试java发送邮件!!");
        return mimeMessage;
    }

    /**
     * 发送html文本
     * @param session
     * @return
     * @throws MessagingException
     */
    public static Message  createHtmlMessage(Session session) throws MessagingException {
        MimeMessage mimeMessage = new MimeMessage(session);
        //发送方地址
        mimeMessage.setFrom(new InternetAddress("1872694955@qq.com"));
        //设置发送给谁
        mimeMessage.setRecipient(Message.RecipientType.TO,new InternetAddress("1872694955@qq.com"));
       mimeMessage.setSubject("这是一个html文本标题");
       mimeMessage.setSentDate(new Date());  //发送日期
        Multipart multipart = new MimeMultipart();
        BodyPart bodyPart = new MimeBodyPart();
        StringBuffer buffer=new StringBuffer();
        buffer.append("<html><body><a href='https://www.baidu.com'>百度</a></body></html>");
        bodyPart.setContent(buffer.toString(),"text/html;charset=utf8");
        multipart.addBodyPart(bodyPart);
        mimeMessage.setContent(multipart);
        return mimeMessage;
    }
    
     //带有附件的邮件发送
    public static Message attachFileMessage(Session session) throws MessagingException, UnsupportedEncodingException {
        //创建MimeMessage对象
        MimeMessage mimeMessage = new MimeMessage(session);
        //设置发送方
        mimeMessage.setFrom(new InternetAddress("****@qq.com"));
        //设置接收方  To:表示普通发送  CC表示抄送  BCC表示密送
        mimeMessage.setRecipient(Message.RecipientType.TO,new InternetAddress("******@qq.com"));
        //设置邮件主题
        mimeMessage.setSubject("附件邮件发送!");
        //设置邮件发送时间
        mimeMessage.setSentDate(new Date());
        //设置普通文本
        MimeBodyPart bodyPart = new MimeBodyPart();
        bodyPart.setContent("这是一个附件邮件!!","text/html;charset=utf8");
        //设置附件
        MimeBodyPart fileBody=new MimeBodyPart();
        DataHandler handler = new DataHandler(new FileDataSource("C:\\Users\\Administrator\\Desktop\\附件.txt"));
        fileBody.setDataHandler(handler);
        //设置附件名 MimeUtility.encodeText(handler.getName())解决乱码问题
        fileBody.setFileName(MimeUtility.encodeText(handler.getName()));
        //设置MimeMultipart 装载BodyPart 对象
        MimeMultipart multipart = new MimeMultipart();
        multipart.addBodyPart(bodyPart);
        multipart.addBodyPart(fileBody);
        mimeMessage.setContent(multipart);
        return mimeMessage;
    }
}

二、Spring支持邮件发送

  • 简要说明
1、Spring邮件发送,在配置文件中要注入SimpleMailMessage和JavaMailSenderImpl类,JavaMailSenderImpl是实现发送功能,配置服务器、端口、用户名、授权码,SimpleMailMessage类主要是发送普通文本。如果要发送附件需要获取MimeMessage对象和MimeMessageHelper对象,MimeMessageHelper对象设置发送方、接收方、文本内容、附件、构造器开启支持MimeMutipartMessage对象。
  • 导入依赖
           <dependency>
               <groupId>com.sun.mail</groupId>
               <artifactId>javax.mail</artifactId>
               <version>1.6.2</version>
           </dependency>
           <dependency>
               <groupId>javax.mail</groupId>
               <artifactId>javax.mail-api</artifactId>
               <version>1.6.2</version>
           </dependency>
           <!--spring支持依赖-->
           <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-context-support</artifactId>
               <version>5.3.22</version>
           </dependency>
  • spring核心配置
<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
       <!--开启扫描-->
    <context:component-scan base-package="com.zwf"/>
    <!--配置邮箱发送类
      host:主机名
      username:用户名 也就是邮件地址@前的名字
      defaultEncoding:默认字符编码
      password:邮箱客户端开启传输协议的授权码  SMTP  POP3/IMAP
      port:端口  默认是25 开启SSL安全验证使用465/587
  -->
     <bean id="javaMailSend" class="org.springframework.mail.javamail.JavaMailSenderImpl">
         <property name="host" value="smtp.qq.com"></property>
         <property name="username" value="[用户名]"></property>
         <property name="defaultEncoding" value="UTF-8"></property>
         <property name="password" value="[授权码]"></property>
         <!--开启SSL安全连接-->
           <property name="javaMailProperties">
             <props>
                 <prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
             </props>
         </property>
         <property name="port" value="465"></property>
     </bean>
    <!--
       配置邮件简单文本 simpleMailMessage  设置简单文本的发送方法和接收方
  from:发送方  to:接收方
    -->
    <bean id="simpleMailMessage" class="org.springframework.mail.SimpleMailMessage">
          <property name="from" value="***@qq.com"></property>
        <property name="to" value="*****@qq.com"></property>
    </bean>
</beans>
  • 测试代码
package com.zwf.service;

import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;

/**
 * @author Mr Zeng
 * @version 1.0
 * @date 2023-09-07 17:45
 */
@Service
public class MailServiceImp implements MailService {
    @Resource
    private SimpleMailMessage simpleMailMessage;
    @Resource
    private JavaMailSender javaMailSend;
    @Override
    public void sendMail() {
        simpleMailMessage.setSubject("这是一个普通文本!");
        simpleMailMessage.setText("测试javamail邮件发送!!");
        simpleMailMessage.setSentDate(new Date());
        javaMailSend.send(simpleMailMessage);  //发送
    }
     //发送附件邮件
    @Override
    public void sendAttachMail() {
        //创建MimeMessage对象支持发送附件
        MimeMessage mimeMessage = javaMailSend.createMimeMessage();
        try {
            //实例化MimeMessageHelper对象,开启文件发送支持 传入MimeMessage  true  字符编码UTF-8
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true,"UTF-8");
            mimeMessageHelper.setFrom("*******@qq.com");
            mimeMessageHelper.setTo(new InternetAddress("***@qq.com"));
            //设置普通文本
            mimeMessageHelper.setSubject("附件邮箱!");
            mimeMessageHelper.setText("这是一个附件文本!");
            //附件地址
            File file = new File("C:\\Users\\Administrator\\Desktop\\附件.txt");
            mimeMessageHelper.addAttachment(MimeUtility.encodeText(file.getName()),file);
            javaMailSend.send(mimeMessage);  //发送
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }  catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

    }
}
  • 使用测试类
public class TestMail {
   //测试发送普通文本
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
         MailService mailService=(MailService)context.getBean("mailServiceImp");
         mailService.sendMail();
    }
}


public class TestMail1 {
    //测试发送附件
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
         MailService mailService=(MailService)context.getBean("mailServiceImp");
         mailService.sendAttachMail();
    }
}