Spring学习记录(未完)

Spring框架简介

1. 简介

Spring是基于javaEE应用一站式轻量级开源框架,主要核心是IOC(控制反转/依赖注入)和AOP(面向切面编程)两大技术,实现项目在开发过程中轻松解耦,提高开发效率。
项目引入Spring可以降低组件之间的耦合度,实现软件各层之间的解耦。可以使用容器提供的众多服务,比如事务管理、消息服务等。容器提供了AOP技术,可以很容易实现如权限拦截、运行期监控等功能。

2. Spring 源码架构

Spring组件被整合在核心容器(core Container)、Aop(Aspect Oriented Programming)和设备支持(Instrmentation)、数据访问及集成(Data Access/Integeration)、Web、报文发送(Messaging)、测试6个模块集合中。

  • 核心容器:Spring-beans和Spring-core。包含控制反转和依赖注入,提供Spring框架的基本功能。主要组件是BeanFactory,工厂模式的实现.
    Spring上下文Spring Context:是一个配置文件,向Spring框架提供上下文信息。
    Spring-Expression模块是同一表达式语言的扩展模块,可以查询、管理运行中的对象,同时也方便调用对象方法、操作数组、集合等。语法类似于传统EL,但提供了额外的功能。
  • Spring-AOP:是Spring的另一个核心模块,以JVM动态代理技术为基础,设计出了一系列的AOP横切实现。
  • Spring Data Access(数据访问):由Spring-jdbc、Spring-tx、Spring-orm、Spring-jms、Spring-oxm5个模块组成。
    Spring-jdbc:是Spring提供的JDBC抽象框架的主要实现模块,用于简化Spring JDBC。
    Spring-tx:Spring JDBC事务控制实现模块。
    Spring-orm:ORM框架支持模块,用于资源管理、数据访问对象(DAO)的实现和事务策略。
    Spring-jms:Java Messaging Service 能够发送和接收信息。
    Spring-oxm:提供一个抽象层以支持OXM(OXM 即 Object-to-XML-Mapping,是一个O/M-mapper,将java对象映射成xml数据,或者将xml数据映射成java对象)
  • Web模块:由spring-web、spring-webmvc、spring-websocket和spring-wenbmvc-portlet 4个模块组成,建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。还简化了处理多部份请求以及将请求参数绑定到域对象的工作。
  • 报文发送Spring-messaging:主要职责是为Spring框架集成一些基础的报文传送应用。
  • 单元测试Spring-test:主要为测试提供支持。

Spring IOC — 控制反转/依赖注入

1. Spring框架环境搭建

  1. 新建maven项目
  2. 调整项目环境
    修改JDK版本
    修改单元测试JUnit版本
    (删除多余配置,pluginManagement标签内容)
  3. 添加Spring框架的依赖坐标

pom.xml

    
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.3.14version>
    dependency>
  1. 编写Bean对象
package com.xxxx.service;

public class UserService {
    public void test(){
        System.out.println("UserService test...");
    }
}
  1. 添加Spring配置文件
    在项目src\main目录下创建资源目录resources;
    在src\main\resources目录下新建spring.xml文件,并拷贝官网文档提供的模板内容到xml中

<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="..." class="...">  
        
    bean>

    <bean id="..." class="...">
        
    bean>

    

beans>
  1. 在spring.xml中配置Bean对象


    <bean id="userService" class="com.xxxx.service.UserService">
        
    bean>
    <bean id="userService02" class="com.xxxx.service.UserService02">
        
    bean>
  1. 加载配置文件,获取实例化对象
package com.xxxx.test;

import com.xxxx.service.UserService;
import com.xxxx.service.UserService02;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Starter01 {
    public static void main(String []args){
        //等到Spring的上下文环境
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");

        //通过id属性值得到指定的Bean对象
        UserService userService=(UserService) ac.getBean("userService");
        UserService02 userService02=(UserService02)ac.getBean("userService02");
        //调用实例化好的JavaBean对象中的方法
        userService.test();
        userService02.test();

    }
}

2. Spring IOC容器Bean对象实例化模拟

  1. 定义Bean工厂接口,提供获取bean方法
  2. 定义Bean工厂接口实现类,解析配置文件,实例化bean对象
  3. 实现获取Bean方法

2.1 定义Bean属性对象

用来接收配置文件中bean标签的id与class属性值

package com.xxxx.spring;

/**
 * 用来接收配置文件中bean标签的id与class属性值
 */
public class MyBean {
    private String id;//bean对象的id属性值
    private String clazz;//bean对象的类路径

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

    public MyBean() {
    }

    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;
    }
}

2.2 dom4j坐标依赖

    
    <dependency>
      <groupId>dom4jgroupId>
      <artifactId>dom4jartifactId>
      <version>1.1version>
    dependency>
    
    <dependency>
      <groupId>jaxengroupId>
      <artifactId>jaxenartifactId>
      <version>1.1.6version>
    dependency>

2.3 准备自定义配置文件

spring.xml


<beans>
    
    <bean id="userDao" class="com.xxxx.dao.UserDao">bean>
    <bean id="userService" class="com.xxxx.service.userService">bean>
beans>

2.4 定义Bean工厂接口

package com.xxxx.spring;

/**
 * 定义Bean工厂接口
 */
public interface MyFactory {
    //通过id值获取对象
    public Object getBean(String id);
}

2.5 定义Bean接口的实现类

package com.xxxx.spring;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

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

/**
 * 模拟spring实现:
 *  1.通过构造器得到相关配置文件
 *  2.通过dom4j解析xml文件,得到List集合,存放id和class属性值
 *  3.通过反射实例化得到对象,通过Map存储。(遍历list集合,通过获取对应的class属性,利用Class.forName(类的全路径).newInstance())
 *  4.通过id属性值得到指定的实例化对象
 */
public class MyClassPathXmlApplicationContext implements MyFactory{
    private Map<String,Object> beanMap=new HashMap<>();//存放实例化后的对象,通过id获取对应的对象
    private List<MyBean> beanList;//存放从配置文件获取到的bean标签的信息
    //1.通过构造器得到相关配置文件
    public MyClassPathXmlApplicationContext(String fileName){
        //2.通过dom4j解析xml文件,得到list,存放id和class
        this.parseXml(fileName);
        //3.通过反射实例化得到对象 Class.forName(类的全路径).newInstance();通过Map存储
        this.instanceBean();
    }
    /**
     * 通过dom4j解析xml文件,得到list,存放id和class
     *  1.获取解析器
     *  2.得到配置文件的URL
     *  3.通过解析器解析xml文件(Spring.xml)
     *  4.通过xPath语法,获取beans标签下的所有bean标签
     *  5.通过指定语法解析文档对象,返回集合
     *  6.判断集合是否为空,遍历集合
     *  7.获取标签元素的属性
     *  8.得到Bean对象,将Bean对象设置到集合中
     * @param fileName
     */
    private void parseXml(String fileName) {
        // 1.获取解析器
        SAXReader reader=new SAXReader();
        // 2.得到配置文件的URL
        URL url=this.getClass().getClassLoader().getResource(fileName);
        // 3.通过解析器解析xml文件(Spring.xml)
        try{
            //3.通过解析器解析xml文件(Spring.xml)
            Document document=reader.read(url);
            // 4.通过xPath语法,获取beans标签下的所有bean标签
            XPath xPath=document.createXPath("beans/bean");
            // 5.通过指定语法解析文档对象,返回集合
            List<Element> elementList=xPath.selectNodes(document);
            // 6.判断集合是否为空,遍历集合
            if(elementList!=null&&elementList.size()>0){
                beanList=new ArrayList<>();
                for(Element el:elementList){
                    // 7.获取标签元素的属性
                    String id=el.attributeValue("id");
                    String clazz=el.attributeValue("class");
                    //System.out.println("id: "+id);
                    //System.out.println("class: "+clazz);
                    // 8.得到Bean对象,将Bean对象设置到beanList集合中
                    MyBean bean=new MyBean(id,clazz);
                    beanList.add(bean);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }
    /**
     * 通过反射实例化得到对象
     *  Class.forName(类的全路径).newInstance();
     *  通过Map存储
     */
    private void instanceBean() {
        //1.判断beanList集合是否为空,不为空则遍历得到Bean对象
        if(beanList!=null&&beanList.size()>0){
            for(MyBean bean:beanList){
                String id=bean.getId();
                String clazz= bean.getClazz();
                try{
                    //通过全路径名 反射 实例化对象
                    Object object=Class.forName(clazz).newInstance();
                    //将id与实例化对象设置到map对象中
                    beanMap.put(id,object);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 通过key获取map中指定的value
     * @param id
     * @return
     */
    @Override
    public Object getBean(String id) {
        Object object=beanMap.get(id);
        return object;
    }
}

2.6 测试自定义IOC容器

  1. 创建与配置文件中对应的Bean对象
    UserService.java
package com.xxxx.service;

public class UserService {
    public void testService(){
        System.out.println("test Service ...");
    }
}

UserDao.java

package com.xxxx.dao;

public class UserDao {
    public void testDao(){
        System.out.println("test dao...");
    }
}
  1. 测试结果
    App.java
package com.xxxx;

import com.xxxx.dao.UserDao;
import com.xxxx.service.UserService;
import com.xxxx.spring.MyClassPathXmlApplicationContext;
import com.xxxx.spring.MyFactory;

/**
 * Hello world!
 *  */
public class App 
{
    public static void main( String[] args ) {
        //得到工厂的实现对象
        MyFactory factory=new MyClassPathXmlApplicationContext("spring.xml");
        //得到对应的实例化对象
        UserDao userDao=(UserDao) factory.getBean("userDao");
        userDao.testDao();

        UserService userService=(UserService) factory.getBean("userService");
        userService.testService();
    }
}

运行结果:
Spring学习记录(未完)_第1张图片
核心技术:

  • 工厂设计模式(简单工厂、工厂方法、抽象工厂)
  • XML解析(dom4j)
  • 反射技术(实例化对象、反射获取方法、反射获取属性、反射获取构造器、反射调用方法)
  • 策略模式(加载资源)
  • 单例(IOC创建实例化对象对象)

3. SPringle IOC配置文件加载

3.1 Spring配置文件加载

spring.xml


<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="userService" class="com.xxxx.service.UserService">bean>

根据相对路径加载资源:

ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");

根据绝对路径加载资源:

ApplicationContext ac=new FileSystemXmlApplicationContext("C:\Users\17208\IdeaProjects\spring03\src\main\resources\spring.xml");

3.2 Spring多配置文件加载

Spring框架启动时可以加载多个配置文件到环境中,项目在启动部署时会将多个配置文件同时加载出来。

service.xml


<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="userService" class="com.xxxx.service.UserService">bean>

dao.xml


<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="userDao" class="com.xxxx.service.UserDao">bean>

3.2.1 可变参数,传入多个文件名

//同时加载多个资源文件
ApplicationContext ac=new ClassPathXmlApplicationContext("service.xml","dao.xml");

3.2.2 通过总的配置文件import其他配置文件

spring.xml

<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">
	
	<import resource="service.xml"/>
	<import resource="dao.xml"/>
beans>

加载时只需要加载总的配置文件即可:

//加载总的资源文件
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");

4. Spring IOC容器Bean对象实例化

4.1 构造器实例化

通过默认构造器创建,空构造方法必须存在,否则会创建失败。
可能产生循环依赖问题,如果产生问题,使用Set注入。

设置配置文件spring.xml


<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="userService" class="com.xxxx.service.UserService">bean>
beans>

获取实例化对象

ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
UserService userService= (UserService) ac.getBean("userService");
userService.test();

4.2 静态工厂实例化

注意:

  • 要有该工厂类及工厂方法
  • 工厂方法为静态
  1. 定义静态工厂类:
package com.xxxx.factory;

import com.xxxx.dao.UserDao03;

/**
 * 静态工厂
 */
public class StaticFactory {
    /**
     * 定义静态方法,返回实例化对象
     * @return
     */
    public static UserDao03 createTypeDao(){
        return new UserDao03();
    }
}
  1. 设置配置文件spring.xml

<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="userDao03" class="com.xxxx.factory.StaticFactory" factory-method="createTypeDao">bean>

  1. 获取实例化对象
ApplicationContext ac=new ClassPathXmlApplicationContext("spring03.xml");
UserService03 userService03= (UserService03) ac.getBean("userService03");
userService03.test();

Spring先解析配置文件,并根据配置文件指定信息,通过反射调用静态工厂类的静态工厂方法,将该静态工厂方法返回值作为Bean实例,该过程中Spring不负责创建Bean实例,而是由用户提供的静态工厂方法创建实例。

4.3 实例化工厂实例化

注意:

  • 工厂方法为非静态方法
  • 需要配置工厂Bean,并在业务bean中配置factory-bean,factory-method属性
  1. 定义工厂类
package com.xxxx.factory;

import com.xxxx.dao.UserDao03;

public class InstanceFactory {
    public UserDao03 createUserDao03(){
        return new UserDao03();
    }
}
  1. 设置配置文件spring.xml

<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="instanceFactory" class="com.xxxx.factory.InstanceFactory">bean>
    
    <bean id="userDao03" factory-bean="instanceFactory" factory-method="createUserDao03">bean>

beans>
  1. 获取实例化对象
ApplicationContext ac=new ClassPathXmlApplicationContext("spring03.xml");
UserService03 userService03= (UserService03) ac.getBean("userService03");
userService03.test();

4.4 三种实例化Bean方法比较

  • 构造器实例化:通过bean的缺省构造函数创建,当各个bean业务逻辑相互比较独立时或和外界关联较少的时候使用。
  • 静态工厂实例化:利用静态factory的静态方法创建,可以统一管理各个bean的创建。如各个bean在创建之前需要相同的初始化处理,则可使用这个factory方法进行统一处理。
  • 实例化工厂实例化:利用实例化factory方法创建,将factory方法也作为业务bean来控制,可用于集成其他框架的bean创建管理方法,能够使bean和factory角色互换。

5. Spring IOC注入

5.1 手动装配(注入)

一般选择set方式注入

5.1.1 set方法注入

注:

  • 属性字段需要提供set方法

Spring学习记录(未完)_第2张图片

业务对象JavaBean:

  1. 属性字段提供set方法
public class UserService {
    //业务逻辑对象 JavaBean对象 set方法注入
    private  UserDao userDao;
    public void setUserDao(UserDao userDao){
        this.userDao=userDao;
    }
  1. 配置文件中bean标签设置property标签

<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="userDao" class="com.xxxx.dao.UserDao">bean>
    <bean id="studentDao" class="com.xxxx.dao.StudentDao">bean>
    <bean id="userService" class="com.xxxx.service.UserService">
        
        <property name="userDao" ref="userDao"/>
        <property name="studentDao" ref="studentDao"/>
        
        <property name="host" value="127.0.0.1"/>
        <property name="port" value="8080"/>
        <property name="list">
            <list>
                <value>上海value>
                <value>北京value>
                <value>杭州value>
            list>
        property>
        <property name="map">
            <map>
                <entry>
                    <key><value>Chinavalue>key>
                    <value>中国value>
                entry>
                <entry>
                    <key><value>frencvalue>key>
                    <value>法国value>
                entry>
            map>
        property>
        <property name="properties">
            <props>
                <prop key="1">oneprop>
                <prop key="2">twoprop>
                <prop key="3">threeprop>
            props>
        property>
    bean>

beans>

UserService .java

package com.xxxx.service;

import com.xxxx.dao.StudentDao;
import com.xxxx.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Set方法注入:
 *  1.属性字段提供set方法
 *  2.在配置文件中通过property属性指定属性字段
 */
public class UserService {
    //手动实例化
    //private UserDao userDao=new UserDao();

    //业务逻辑对象 JavaBean对象 set方法注入
    private  UserDao userDao;
    public void setUserDao(UserDao userDao){
        this.userDao=userDao;
    }
    //JavaBean对象
    private StudentDao studentDao;
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
    //常用类型
    private String host;

    public void setHost(String host) {
        this.host = host;
    }
    //基本类型
    private Integer port;

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

    public void setList(List<String> list) {
        this.list = list;
    }
    public void printList(){
        for(String s:list){
            System.out.print(s+" ");
        }
        System.out.println("");
    }
    //Map集合
    private Map<String,Object> map;

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
    public void printMap(){
        System.out.println(map.toString());
    }
    //properties集合
    private Properties properties;

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    public void printProperties(){
        System.out.println(properties.toString());
    }

    public void test(){
        System.out.println("UserService test...");
        userDao.test();
        studentDao.test();
        System.out.println(host);
        System.out.println(port);

        printList();
        printMap();
        printProperties();
    }
}

Starter01.java(测试)

package com.xxxx.test;

import com.xxxx.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Starter01 {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
        UserService userService= (UserService) ac.getBean("userService");
        userService.test();
    }
}

5.1.2 构造器注入

注意:需要提供带参构造。

Spring学习记录(未完)_第3张图片

UserService02.java

package com.xxxx.service;

import com.xxxx.dao.StudentDao;
import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDao02;

import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * 构造器注入
 *  需要提供带参构造
 *  可能产生循环依赖问题,如果产生问题,使用Set注入
 */
public class UserService02 {
    private UserDao02 userDao02;
    /*public UserService02(UserDao02 userDao02){
        this.userDao02=userDao02;
    }*/
    private  StudentDao studentDao;
    public UserService02(UserDao02 userDao02,StudentDao studentDao){
        this.userDao02=userDao02;
        this.studentDao=studentDao;
    }
    public void test(){
        System.out.println("UserService02 test...");
        userDao02.test();
        studentDao.test();
    }
}

spring02.xml


<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="userService02" class="com.xxxx.service.UserService02">
        <constructor-arg name="userDao02" ref="userDao02">constructor-arg>
        <constructor-arg name="studentDao" ref="studentDao">constructor-arg>
    bean>

    <bean id="userDao02" class="com.xxxx.dao.UserDao02">bean>
    <bean id="studentDao" class="com.xxxx.dao.StudentDao">bean>

beans>

Starter02.java

package com.xxxx.test;

import com.xxxx.service.UserService02;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Starter02 {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring02.xml");
        UserService02 userService02= (UserService02) ac.getBean("userService02");
        userService02.test();
    }
}

5.1.3 静态工厂注入

Spring学习记录(未完)_第4张图片

StaticFactory.java

package com.xxxx.factory;

import com.xxxx.dao.UserDao03;

/**
 * 静态工厂
 */
public class StaticFactory {
    /**
     * 定义静态方法,返回实例化对象
     * @return
     */
    public static UserDao03 createTypeDao(){
        return new UserDao03();
    }
}

UserService03.java

package com.xxxx.service;

import com.xxxx.dao.StudentDao;
import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDao03;

import java.util.List;
import java.util.Map;
import java.util.Properties;

public class UserService03 {
    private UserDao03 userDao03;
    public void setUserDao03(UserDao03 userDao03){
        this.userDao03=userDao03;
    }
    public void test(){
        System.out.println("UserService03 test...");
        userDao03.test();
    }
}

spring03.xml


<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="userService03" class="com.xxxx.service.UserService03">
		<property name="userDao03" ref="userDao03">property>
	bean>
	<bean id="userDao03" class="com.xxxx.factory.StaticFactory" factory-method="createTypeDao">bean>
beans>

Starter03.java

package com.xxxx.test;

import com.xxxx.service.UserService03;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Starter03 {
    public static void main(String[] args) {
        System.out.println("静态工厂实例化");
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring03.xml");
        UserService03 userService03= (UserService03) ac.getBean("userService03");
        userService03.test();
    }
}

5.1.4 实例化工厂注入

Spring学习记录(未完)_第5张图片

InstanceFactory.java

package com.xxxx.factory;

import com.xxxx.dao.UserDao03;

public class InstanceFactory {
    public UserDao03 createUserDao03(){
        return new UserDao03();
    }
}

spring03.xml


<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="userService03" class="com.xxxx.service.UserService03">
          <property name="userDao03" ref="userDao03">property>
    bean>
    
    <bean id="instanceFactory" class="com.xxxx.factory.InstanceFactory">bean>
    
    <bean id="userDao03" factory-bean="instanceFactory" factory-method="createUserDao03">bean>
beans>

UserService03.java

package com.xxxx.service;

import com.xxxx.dao.StudentDao;
import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDao03;

import java.util.List;
import java.util.Map;
import java.util.Properties;

public class UserService03 {
    private UserDao03 userDao03;
    public void setUserDao03(UserDao03 userDao03){
        this.userDao03=userDao03;
    }
    public void test(){
        System.out.println("UserService03 test...");
        userDao03.test();
    }
}

Starter03.java

package com.xxxx.test;

import com.xxxx.service.UserService03;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Starter03 {
    public static void main(String[] args) {
        System.out.println("实例化工厂实例化");
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring03.xml");
        UserService03 userService03= (UserService03) ac.getBean("userService03");
        userService03.test();
    }
}

5.1.5 p名称空间的使用

引用p名称空间可以将子元素简化为元素属性配置。
Spring学习记录(未完)_第6张图片

  1. 属性字段提供set方法
    UserService04.java
package com.xxxx.service;

import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDao03;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * p名称空间的使用
 */
public class UserService04 {
   private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    private String host;

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

    public void test(){
        System.out.println("UserService04 test...");
        userDao.test();
        System.out.println(host);
    }
}
  1. 在配置文件spring.xml引入p名称空间
xmlns:p="http://www.springframework.org/schema/p"

spring04.xml


<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="userDao" class="com.xxxx.dao.UserDao">bean>
    <bean id="userService04" class="com.xxxx.service.UserService04"
          p:host="127.0.0.1"
          p:userDao-ref="userDao">
    bean>

beans>

Starter04.java

package com.xxxx.test;

import com.xxxx.service.UserService04;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Starter04 {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring04.xml");
        UserService04 userService04= (UserService04) ac.getBean("userService04");
        userService04.test();
    }
}

5.2 自动装配(注入)

注解方式注入Bean,可以简化配置文件,提高开发速度,使程序看上去更简洁。通过反射技术实现。

5.2.1 准备环境

  1. 修改配置文件
    添加context环境
<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
       http://www.springframework.org/schema/context/spring-context.xsd">
beans>
  1. 开启自动化注入

    <context:annotation-config/>
    
    <bean id="userDao" class="com.xxxx.dao.UserDao">bean>
    <bean id="userService" class="com.xxxx.service.UserService">bean>
  1. 给注入的bean对象添加注解

在需要被实例化的Bean的类上添加指定的注解,注解声明在类级别
(Bean对象的id属性默认为类的首字母小写)
@Repository(Dao层)
@Service(Service层)
@Controller(Controller层)
@Component(任意层)

package com.xxxx.service;

import com.xxxx.dao.UserDao;

import javax.annotation.Resource;


public class UserService {
    @Resource
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
 
    public void test(){
        System.out.println("UserService test...");
        userDao.test();   
    }
}

5.2.2 @Resource注解

  • 默认根据属性字段名称查找对应bean对象(字段名称与bean标签id属性值相等)
  • 如果属性字段名称未找到,则会通过类型(Class类型)查找
  • 属性可以提供set方法,也可以不提供
  • 注解可以声明在属性级别或set方法级别
  • 可以设置name属性,name属性值必须与bean标签id属性值一致;如果设置了name属性值,就只会按照name属性值查找bean对象( @Resource(name=“userDao”))
  • 当注入接口时,如果接口只有一个实现则正常实例化;如果接口存在多个实现,则需要使用name属性指定需要被实例化的bean对象

Spring学习记录(未完)_第7张图片
IUserDao.java

package com.xxxx.dao;

public interface IUserDao {
    public void test();
}

UserDao.java

package com.xxxx.dao;

public class UserDao implements IUserDao{
    public void test(){
        System.out.println("UserDao test...");
    }
}

UserDaoImplement01.java

package com.xxxx.dao;

public class UserDaoImplement01 implements IUserDao{
    @Override
    public void test() {
        System.out.println("IUserDao test...\nUserDaoImplement01 test...");
    }
}

UserService.java

package com.xxxx.service;

import com.xxxx.dao.IUserDao;
import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDaoImplement01;

import javax.annotation.Resource;

/**
 * @Resource注解实现自动注入(反射)
 *  1.注解默认通过属性字段名称查找对应bean对象(属性字段名称与bean标签的id属性值一致)
 *  2.如果属性字段名称不一样,则会通过类型(class)类型
 *  3.属性字段可以提供set方法,也可不提供
 *  4.注解可以声明在属性字段上,或set方法级别
 *  5.可以设置注解的name属性,name属性值要与bean标签的id属性值一致,如果设置了name属性,则需要使用name属性查找bean对象   @Resource(name="userDao")
 *  6.注入接口时,如果接口只有一个实现类,则正常实例化,如有多个实现类,则需要使用name属性指定需要被实例化的bean对象
 */
public class UserService {
    @Resource
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Resource(name="userDaoImplement01")
    private IUserDao iUserDao;

    public void test(){
        System.out.println("UserService test...");
        userDao.test();
        iUserDao.test();
    }
}

spring.xml

<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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:annotation-config/>
    
    <bean id="userDao" class="com.xxxx.dao.UserDao">bean>
    <bean id="userService" class="com.xxxx.service.UserService">bean>
    <bean id="userDaoImplement01" class="com.xxxx.dao.UserDaoImplement01">bean>
beans>

Start01.java

package com.xxxx.test;

import com.xxxx.service.AccountService;
import com.xxxx.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.applet.AppletContext;

public class Starter01 {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");

        UserService userService=(UserService) ac.getBean("userService");
        userService.test();

//        AccountService accountService=(AccountService) ac.getBean("accountService");
//        accountService.test();

    }

}

5.2.3 @Autowired注解

  • 通过类型(Class类型)查找bean对象,与属性字段名称无关
  • 属性可提供set方法,也可以不提供
  • 注解可以声明在属性级别,也可以声明在set方法级别
  • 如果需要通过指定id名称查找bean对象,需要结合@Qualifier(value=“accountDao”)设定value属性值

Spring学习记录(未完)_第8张图片
AccountDao.java

package com.xxxx.dao;

public class AccountDao {
    public void test(){
        System.out.println("AccountDao test...");
    }
}

UserDaoImplement01.java

package com.xxxx.dao;

public class UserDaoImplement01 implements IUserDao{
    @Override
    public void test() {
        System.out.println("UserDaoImplement01 test...");
    }
}

AccountService.java

package com.xxxx.service;

import com.xxxx.dao.AccountDao;
import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDaoImplement01;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.annotation.Resource;

/**
 * @Autowired注解实现自动注入
 *  1.注解默认通过类型(class)类型查找对应bean对象(与属性字段名称无关)
 *  2.属性字段可以提供set方法,也可不提供
 *  3.注解可以声明在属性字段上,或set方法级别
 *  4.如果需要通过指定id名称查找bean对象,需要结合@Qualifier(value="accountDao")设定value属性值
 */
public class AccountService {
    @Autowired
    @Qualifier(value="accountDao")
    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Resource(name="userDaoImplement01")
    private UserDaoImplement01 userDaoImplement01;

    public void test(){
        System.out.println("UserService test...");
        accountDao.test();
        userDaoImplement01.test();
    }
}

spring.xml

<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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:annotation-config/>
    
    <bean id="userDaoImplement01" class="com.xxxx.dao.UserDaoImplement01">bean>
    <bean id="accountDao" class="com.xxxx.dao.AccountDao">bean>
    <bean id="accountService" class="com.xxxx.service.AccountService">bean>
beans>

6. Spring IOC扫描器

扫描器对扫描到的对象统一进行管理,简化开发配置,提高开发效率。

6.1 Spring IOC 扫描器配置

Spring学习记录(未完)_第9张图片

  1. 设置自动化扫描范围
<context:component-scan base-package="扫描范围"/>

spring.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
        
    <context:component-scan base-package="com.xxxx"/>
beans>
  1. 使用指定的注解(声明在类级别),(Bean对象的id属性默认为类的首字母小写)
    @Repository(Dao层)
    @Service(Service层)
    @Controller(Controller层)
    @Component(任意层)

TypeDao.java( Dao层) @Repository

package com.xxxx.dao;

import org.springframework.stereotype.Repository;

@Repository
public class TypeDao {
    public void test(){
        System.out.println("TypeDao test...");
    }
}

TypeService.java(service层) @Service

package com.xxxx.service;

import com.xxxx.dao.TypeDao;
import com.xxxx.dao.UserDao;
import com.xxxx.dao.UserDaoImplement01;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class TypeService {
    @Resource
    private TypeDao typeDao;

    public void test(){
        System.out.println("TypeService test...");
        typeDao.test();

    }
}

TypeController.java(controller层) @Controller

package com.xxxx.controller;

import com.xxxx.service.TypeService;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

@Controller
public class TypeController {
    @Resource
    private TypeService typeService;
    public void test(){
        System.out.println("TypeController test...");
        typeService.test();
    }
}

6.2 Spring模拟用户登录流程

Spring学习记录(未完)_第10张图片
pom.xml(修改配置、添加spring坐标依赖)



<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0modelVersion>

  <groupId>com.xxxxgroupId>
  <artifactId>myLoginSpringartifactId>
  <version>1.0-SNAPSHOTversion>

  <name>myLoginSpringname>
  
  <url>http://www.example.comurl>

  <properties>
    <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    <maven.compiler.source>1.8maven.compiler.source>
    <maven.compiler.target>1.8maven.compiler.target>
  properties>

  <dependencies>
    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>4.12version>
      <scope>testscope>
    dependency>
    
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.3.14version>
    dependency>
  dependencies>

  <build>
  build>
project>

spring.xml(Spring IOC配置)

<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
       http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="com.xxxx"/>
beans>

StringUtil.java(字符串工具类)

package com.xxxx.util;

/**
 * 字符串工具类
 */
public class StringUtil {
    /**
     * 判断字符串是否为空
     *  为空返回true,非空返回false
     * @param str
     * @return
     */
    public static boolean isEmpty(String str){
        if(str==null||"".equals(str.trim())){
            return true;
        }
        return false;
    }
}

User.java(用户实体类)

package com.xxxx.entity;

/**
 * User 用户实体类
 */
public class User {
    private String userName;
    private String userPwd;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }
}

MessageModel.java(消息模型类,用来接收处理结果)

package com.xxxx.entity.vo;

/**
 * 消息模型对象,用来接收处理结果
 */
public class MessageModel {
    private Integer resultCode=1;//1=成功,0=失败
    private String resultMsg;//提示信息

    public Integer getResultCode() {
        return resultCode;
    }

    public void setResultCode(Integer resultCode) {
        this.resultCode = resultCode;
    }

    public String getResultMsg() {
        return resultMsg;
    }

    public void setResultMsg(String resultMsg) {
        this.resultMsg = resultMsg;
    }
}

UserDao.java(数据库操作。查询用户对象)

package com.xxxx.dao;

import com.xxxx.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
    //定义登录账号密码
    private final String USERNAME="admin";
    private final String USERPWD="admin";

    /**
     * 通过用户名查询用户对象
     *  存在则返回对应用户
     *  不存在则返回空对象
     * @param userName
     * @return
     */
    public User queryUserByUserName(String userName){
        User user=new User();

        if(!USERNAME.equals(userName)){
            return user;
        }
        user=new User();
        user.setUserName(userName);
        user.setUserPwd(USERPWD);
        return user;
    }
}

UserService.java(业务逻辑处理,调用Dao层。验证登录是否成功)

package com.xxxx.service;

import com.xxxx.dao.UserDao;
import com.xxxx.entity.User;
import com.xxxx.entity.vo.MessageModel;
import com.xxxx.util.StringUtil;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserService {
    @Resource
    private UserDao userDao;

    /**
     * 验证登录是否成功
     *  参数非空校验
     *  通过用户名查询用户对象(调用dao层查询方法)
     *  判断密码是否正确
     * @param userName
     * @param userPwd
     * @return
     */
    public MessageModel checkUserLogin(String userName,String userPwd){
        MessageModel messageModel=new MessageModel();
        //参数非空校验
        if(StringUtil.isEmpty(userName)||StringUtil.isEmpty(userPwd)){
            messageModel.setResultCode(0);
            messageModel.setResultMsg("用户名和密码不能为空!");
            return messageModel;
        }
        //通过用户名查询用户对象(调用dao层查询方法)
        User user=userDao.queryUserByUserName(userName);
        //判断用户对象是否为空
        if(user==null){
            messageModel.setResultCode(0);
            messageModel.setResultMsg("用户名不存在!");
            return messageModel;
        }
        //判断密码是否正确
        if(!userPwd.equals(user.getUserPwd())){
            messageModel.setResultCode(0);
            messageModel.setResultMsg("密码不正确!");
            return messageModel;
        }
        //登录成功
        messageModel.setResultCode(1);
        messageModel.setResultMsg("登录成功!");

        return messageModel;
    }
}

UserController.java(接收响应请求,调用Service层)

package com.xxxx.controller;

import com.xxxx.entity.vo.MessageModel;
import com.xxxx.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    public MessageModel userLogin(String userName,String userPwd){
        MessageModel messageModel=userService.checkUserLogin(userName,userPwd);
        return messageModel;
    }
}

UserTest.java(测试)

package com.xxxx.test;

import com.xxxx.controller.UserController;
import com.xxxx.entity.vo.MessageModel;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserTest {
    public static void main(String[] args) {
        try {
            ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
            UserController userController= (UserController) ac.getBean("userController");
            MessageModel messageModel=userController.userLogin("d","admin");
            System.out.println(messageModel.getResultMsg());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

7. Bean的作用域与生命周期

默认情况下,Spring容器中的对象是单例的。

7.1 Bean的作用域

bean的作用域类型如下:

  • singleton作用域
  • prototype作用域
  • Web应用中的作用域

7.1.1 singleton作用域(单例作用域)

容器在启动情况下就实例化所有singleton的bean对象,并缓存于容器中。
默认情况下,被管理的bean只会在IOC容器中存在一个实例,对于所有获取该bean的操作,容器返回同一个bean。

<bean id="bean" class="..." lazy-init="false">bean>

lazy-init=“false”:

  • 可以提前发现潜在的配置问题
  • Bean在Spring启动时被设置到单例缓存池中,使用时不需要再去实例化,提高程序运行效率。

什么对象适合作为单例对象?(适合交给IOC容器实例化):无状态对象,即不存在改变当前对象状态的成员变量(属性)的对象,比如Controller层、Service层、Dao层,而User实体类工具类等则不适合作为单例对象。这样保证了线程安全性。

7.1.2 prototype作用域(原型作用域)

<bean id="bean1" class="..." scope="prototype"/>

通过scope="prototype"设置,Spring容器不缓存bean实例化对象,每次向容器请求获取bean都会返回一个全新的bean。

7.1.3 Web应用中的作用域

  1. request作用域
    表示每个请求需要容器创建一个全新的Bean。比如提交表单的数据必须是对每次请求新建一个Bean来保持这些表单数据,请求结束释放这些数据。
  2. session作用域
    表示每个会话需要容器创建一个全新的bean。
  3. globalSession作用域
    类似于session作用域,用于portlet(portlet是基于java的web组件,由portlet容器管理,由容器处理请求,生产动态内容)环境的web环境,非portlet环境视为session作用域。

配置方式和基本作用域相同,只是必须要有web环境支持,并配置相应容器监听器或拦截器从而能应用这些作用域。

7.2 Bean的生命周期

Bean的定义、初始化、使用、销毁。

  • Bean的定义:Spring中通常通过配置文档的方式定义Bean,通过设置bean标签定义bean对象。
  • Bean的初始化:默认在IOC容器加载时,实例化对象。
    初始化方法有两种:
    1.在配置文件通过指定method-init属性来完成。
public class UserService {
    public void init(){
		//...
	}
}
<bean id="userService" class="..." init-method="init"> </bean>

2.实现org.springframework.beans.factory.InitializingBean接口

public class UserService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        //
    }
}
<bean id="userService" class="...">bean>
  • Bean的使用
    方法一:使用BeanFactory
BeanFactory factory=new ClassPathXmlApplicationContext("spring.xml");
UserService userService=(UserService)ac.getBean("userService");

方法二:使用ApplicationCotext

ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
UserService userService=(UserService)factory.getBean("userService");
  • Bean的销毁
    实现销毁方式(可以指定bean对象销毁所要执行的方法)
    1.实现销毁方式,指定bean对象销毁要执行的方法
public class UserService {
    public void destory(){
		//...
	}
}
<bean id="userService" class="..." destory-method="destory"> </bean>

2.通过AbstractApplicationContext 对象,调用close方法销毁bean

AbstractApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml");
ctx.close();

IOC/DI 控制反转和依赖注入:
将对象实例化的创建过程交给外部容器(IOC容器,充当工厂角色)去负责;依赖注入即属性赋值操作。

Spring AOP–面向切面编程

1. 代理模式

代理模式目的旨在为服务类和客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用。
为某一个对象(委托类)提供一个来的(代理类),用来控制对这个对象的访问。委托类和代理类有一个共同的父类或父接口。代理类会对请求做预处理、过滤、将请求分配给指定对象。

代理模式设计原则:

  • 代理类与委托类具有共同的行为
  • 代理类增强委托类的行为

常见代理模式:

  • 静态代理
  • 动态代理

2. 静态代理

某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。
代理类和委托类有共同的父类或父接口。
代理类负责请求的处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

2.1 代理的三要素

  • 有共同的行为—接口
  • 目标角色/真实角色—实现行为
  • 代理角色—实现行为、增强目标对象行为

2.2 特点

  • 目标角色固定
  • 在应用程序之前就得知目标角色
  • 代理角色会增强目标角色的行为
  • 可能会存在多个代理,产生”类爆炸“(缺点)

2.3 实现

Spring学习记录(未完)_第11张图片
Marry.java(定义接口)

/**
 * 定义接口-定义行为
 */
public interface Marry {
    public void toMarry();
}

You.java(目标对象)

/**静态代理:
 * 目标角色-实现行为
 */
public class You implements Marry{
    @Override
    public void toMarry() {
        System.out.println("I will marry...");
    }
}

MarryCompanyProxy.java(代理对象)

/**
 * 静态代理:
 *  代理角色:实现行为、增强用户行为
 */
public class MarryCompanyProxy implements Marry{
    private  Marry target;//目标对象
    public MarryCompanyProxy(Marry target){
        this.target=target;
    }
    @Override
    public void toMarry() {
        before();
        target.toMarry();//调用目标对象的方法实现
        after();
    }
    public void before(){
        System.out.println("准备中...");
    }
    public void after(){
        System.out.println("结束了...");
    }
}

StaticProxy.java

public class StaticProxy {
    public static void main(String[] args) {
        You you=new You();
        MarryCompanyProxy marryCompanyProxy=new MarryCompanyProxy(you);
        marryCompanyProxy.toMarry();

        Owner owner=new Owner();
        AgentPorxy agentPorxy=new AgentPorxy(owner);
        agentPorxy.toRentHouse();
    }

}

Spring学习记录(未完)_第12张图片
RentHouse.java

public interface RentHouse {
    public void toRentHouse();
}

RentHouse.java

public class Owner implements RentHouse{
    @Override
    public void toRentHouse() {
        System.out.println("出租房屋!");
    }
}

AgentPorxy.java

public class AgentPorxy implements RentHouse{
    //目标对象
    private RentHouse target;
    //通过带参构造获取目标对象
    public AgentPorxy(RentHouse target){
        this.target=target;
    }
    @Override
    public void toRentHouse() {
        //before...
        target.toRentHouse();
        //after...
    }
}

StaticProxy.java

public class StaticProxy {
    public static void main(String[] args) {
        Owner owner=new Owner();
        AgentPorxy agentPorxy=new AgentPorxy(owner);
        agentPorxy.toRentHouse();
    }
}

3. 动态代理

相比较静态代理,动态代理在创建代理对象时更加灵活。
根据需要,会通过反射机制在程序运行期,动态为目标对象创建代理对象,无需程序员手动编写其源代码。
代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。

两种实现方式:

  • JDK动态代理
  • CGLIB动态代理

3.1 特点

  • 目标对象不固定
  • 在应用程序执行时动态创建目标对象
  • 代理对象会增强目标对象行为

3.2 JDK动态代理

JDK动态代理就是在程序运行过程中,根据被代理的接口动态生成代理类的class文件,并加载运行的过程。
JDK提供了import java.lang.reflect.Proxy类来实现动态代理,可通过它的newProxyInstance方法来获得代理实现类。同时对于代理的接口的实际处理,是一个java.lang.reflect.InvocationHandler,它提供了invoke方法供实现者提供相应的代理逻辑的实现。
注意:JDK动态代理目标对象必须有接口实现。
每一个代理类都需要实现InvocationHandler接口。
Spring学习记录(未完)_第13张图片

3.2.1 Proxy类的newProxyInstance方法获得代理实现类

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

/**
* 返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。(返回代理对象)
* @param loader    (用来定义代理类的类加载器)一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
* @param interfaces    (代理类实现的接口列表)表示将要给需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
* @param h (调度方法调用的调用处理函数)一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入InvocationHandler接口的子类)
* @return
* @throws IllegalArgumentException
*/
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
            throws IllegalArgumentException
/**
* method of Interface InvocationHandler 
* @param proxy 调用该方法的代理实例
* @param method    目标对象的方法
* @param args  目标对象方法所需的参数
* @return
* @throws Throwable
*/
    Object invoke(Object proxy,
                  Method method,
                  Object[] args)
            throws Throwable

3.2.2 获取代理对象

JdkHander.java

package com.xxxx.proxy;

import com.xxxx.proxy.staticProxy.Marry;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理
 *  每一个代理类都需要实现InvocationHandler接口
 */
public class JdkHander implements InvocationHandler {
    //目标对象
    private Object target;//目标对象类型不固定,创建时动态生成。Marry、RentHouse
    //通过带参构造器传递目标对象
    public JdkHander(Object target){
        this.target=target;
    }
    /**invoke
     *  1.调用目标对象的方法(返回Object)
     *  2.增强目标对象行为
     * @param proxy 调用该方法的代理实例
     * @param method    目标对象的方法
     * @param args  目标对象方法所需的参数
     * @return 从代理实例上的方法调用返回的值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //增强行为
        //调用目标对象中的方法(返回Object)
        Object object=method.invoke(target,args);
        //增强行为
        return object;
    }
    /**
     * 获取代理对象:
     *  public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
     *      Loader:类加载器
     *      interfaces:接口数组
     *      InvocationHandler:目标对象的接口数组
     * @return
     */
    public Object getProxy(){
        /**
         * 返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。(返回代理对象)
         * @param loader    (用来定义代理类的类加载器)this.getClass().getClassLoader()
         * @param interfaces    (目标对象的接口数组)target.getClass().getInterfaces()
         * @param h (InvocationHandler接口)传入InvocationHandler接口的实现类
         * @return
         * @throws IllegalArgumentException
         */
        //Object object= Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
        Object object=Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this );
        return object;
    }
}

JdkHandlerTest.java

package com.xxxx.proxy;

import com.xxxx.proxy.staticProxy.*;

public class JdkHandlerTest {

    public static void main(String[] args) {
        //目标对象
        You you=new You();
        //得到代理类
        JdkHandler jdkHandler=new JdkHandler(you);
        //得到代理对象
        Marry marry=(Marry) jdkHandler.getProxy();
        //通过代理对象调用目标对象的方法
        marry.toMarry();

        Owner owner=new Owner();
        JdkHandler jdkHandler_o=new JdkHandler(owner);
        RentHouse rentHouse=(RentHouse) jdkHandler_o.getProxy();
        rentHouse.toRentHouse();
    }
}

3.3 CGLIB动态代理

JDK动态代理只能代理实现了接口的类,而不能实现接口的类则不能使用JDK动态代理。
CGLIB是针对类来实现代理,其原理是对指定目标类生成一个子类,并覆盖其中方法实现增强,采用的是继承原理,所以不能对final修饰的类进行代理。
代理类继承目标类,重写目标类中方法。

3.3.1 CGLIB动态代理使用(需要实现MethodInterceptor接口)

Spring学习记录(未完)_第14张图片

1. 添加依赖
pom.xml文件引入cglib相关依赖

<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2.2</version>
    </dependency>

2. 定义类,实现MethodInterceptor接口
通过带参构造得到目标对象target–>调用getProxy方法得到代理对象–>调用目标对象方法时会被拦截器所拦截,会调用intercept方法。

CglibInterceptor.java

package com.xxxx.proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

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();
    }

    /**
     * 拦截器:
     *  1.目标对象的方法调用
     *  2.行为增强
     * @param o cglib动态生成的代理类实例
     * @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("begin...");
        //调用目标类中的方法
        Object object=methodProxy.invoke(target,objects);
        //增强行为
        System.out.println("finish...");
        return object;
    }
}
package com.xxxx.proxy;

import com.xxxx.proxy.staticProxy.Marry;
import com.xxxx.proxy.staticProxy.You;

public class CglibInterceptorTest {
    public static void main(String[] args) {
//        //目标对象
//        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 user1=(User) cglibInterceptor.getProxy();
        user1.test();
    }
}

3.4 JDK代理和CGLIB代理的区别

  • JDK动态代理实现接口,Cglib动态代理继承思想
  • JDK动态代理(目标对象存在接口时)执行效率高于Cglib
  • 如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择Cglib

4. Spring AOP

通过代理模式可以在指定位置执行对应流程,这样可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想被称为面向切面编程,即AOP(Aspect Oriented Programing)。
aop考虑更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。

AOP能做什么:
主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的复用。

AOP的特点:

  • 降低模块间的耦合度,提高业务代码的聚合度(高内聚低耦合)
  • 提高代码的复用性
  • 提高系统的扩展性(高版本兼容低版本)
  • 可以在不影响原有功能的基础上添加新的功能

AOP底层实现:
动态代理(JDK+CGLIB)

AOP基本概念:

  • Joinpoint(连接点):被拦截到的每一个点,spring中指被拦截到的每一个方法
  • Pointcut(切入点):对连接点进行拦截的定义(匹配规则定义:规定拦截哪些方法,对哪些方法进行处理)
  • Advice(通知):拦截到每一个连接点后要做的操作。
    前置通知(前置增强):before()执行方法前通知
    返回通知(返回增强):afterReturn方法正常结束返回后的通知
    异常抛出通知(异常抛出增强):afterThrow()
    最终通知:after无论方法是否发生异常,均会执行该通知
    环绕通知:around包围一个连接点的通知。最强大的一种通知类型。可以在方法调用前后完成自定义的行为。也会选择是否继续执行连接点或者直接返回它们自己的返回值或抛出异常来结束执行。
  • Aspect(切面):切入点与通知结合,决定了切面的定义。切面是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面是对横切关注点的抽象。
  • Target(目标对象):被代理的目标对象。
  • Weave(织入):将切面应用到目标对象并生成代理对象的过程
  • Introduction(引入):在不修改原有应用程序代码情况下,在程序运行期间为类动态添加方法或者字段的过程。

5. Spring AOP的实现

5.1 Spring Aop环境搭建

  1. pom.xml引入坐标依赖
    
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.3.14version>
    dependency>
    
    <dependency>
      <groupId>org.aspectjgroupId>
      <artifactId>aspectjweaverartifactId>
      <version>1.8.9version>
    dependency>
  1. 添加spring.xml配置


    
    <aop:aspectj-autoproxy/>
beans>

5.2 注解实现

@Aspect 声明在类级别,声明类为切入面
@PoinCut 声明在方法级别,定义该方法为切入点

@Pointcut(“匹配规则”)
匹配规则execution(* com.xxxx.service.*.*(..))

  • 第一个*代表方法的修饰范围(public、private、protected、*表示执行所有范围)

    1.执行所有公共方法

    •  execution(public *(..))
      

    2.执行任意set方法

    •  execution(* set*(..))
      

    3.执行指定包下任意类的任意方法(如指定包;com.xxxx.service)

    •  execution(* com.xxxx.service.*.*(..))
      

    4.执行指定包及其子包下任意类的任意方法

    •  execution(* com.xxxx.service..*.*(..))
      
package com.xxxx.aspect;

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

@Component//将对象交给IOC实例化
@Aspect//声明当前类是一个切面
public class LogCut {

    //@Pointcut("execution(* *(..))")//定义为切入点
    @Pointcut("execution(* com.xxxx.service..*.*(..))")
    public void cut(){

    }

    /**
     * 声明前置通知,并将通知应用到指定切入点上,目标类执行目标方法前会执行该通知
     */
    @Before(value = "cut()")//该通知指向切入点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("异常通知...");
    }
    /**
     * 声明环绕通知,并将通知应用到指定切入点上
     * 目标类方法执行前后都可通过该通知定义响应的处理
     * 需要显示调用该方法,否则无法访问指定方法 pjp.proceed()
     */
    @Around(value="cut()")
    public Object around(ProceedingJoinPoint pjp){
        System.out.println("环绕通知-前置通知...");
        Object object=null;
        try{
            //显示调用对应的方法
            object=pjp.proceed();
            System.out.println(pjp.getTarget());
            System.out.println("环绕通知-返回通知...");
        }catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知-异常通知...");
        }
        System.out.println("环绕通知-最终通知...");
        return object;
    }
}

Test.java

package test;

import com.xxxx.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class test {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
        UserService userService= (UserService) ac.getBean("userService");
        userService.test();
    }

}

环绕通知需要显式调用方法:ProceedingJoinPoint.proceed()

 @Around(value="cut()")
    public Object around(ProceedingJoinPoint pjp){
        Object object=null;
        try{
            //显示调用对应的方法
            object=pjp.proceed();
            System.out.println(pjp.getTarget());
        }catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知-异常通知...");
        }
        System.out.println("环绕通知-最终通知...");
        return object;
    }

5.3 XML实现

定义切面:

package com.xxxx.aspect;

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;

@Component//将对象交给IOC实例化
//@Aspect//声明当前类是一个切面
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(){
        System.out.println("异常通知...");
    }
    /**
     * 声明环绕通知,并将通知应用到指定切入点上
     * 目标类方法执行前后都可通过该通知定义响应的处理
     * 需要显示调用该方法,否则无法访问指定方法 pjp.proceed()
     */

    public Object around(ProceedingJoinPoint pjp){
        System.out.println("环绕通知-前置通知...");
        Object object=null;
        try{
            //显示调用对应的方法
            object=pjp.proceed();
            System.out.println(pjp.getTarget());
            System.out.println("环绕通知-返回通知...");
        }catch (Throwable throwable){
            throwable.printStackTrace();
            System.out.println("环绕通知-异常通知...");
        }
        System.out.println("环绕通知-最终通知...");
        return object;
    }
}

spring.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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <context:component-scan base-package="com.xxxx"/>
    
    <aop:config>
        <aop:aspect ref="logCut02">
            <aop:pointcut id="cut" expression="execution(* com.xxxx.service..*.*(..))"/>
            <aop:before method="before" pointcut-ref="cut"/>
            <aop:after method="after" pointcut-ref="cut"/>
            <aop:after-returning method="afterReturn" pointcut-ref="cut"/>
            <aop:after-throwing method="afterThrow" pointcut-ref="cut"/>
            <aop:around method="around" pointcut-ref="cut"/>
        aop:aspect>
    aop:config>

beans>

6. 总结

代理模式三要素:

  • 接口定义
  • 目标对象与代理对象实现统一接口
  • 代理对象持有目标对象的引用,增强目标对象的行为

分类

  • 静态代理
  • 动态代理
    JDK动态代理
    CGLIB动态代理(继承思想)

Spring Task定时任务

JAVA中开发定时任务主要有三种方案:

  • JDK自带的Timer
  • 使用第三方组件Quartz
  • 使用Spring Task

1. XML配置

1.配置pom.xml


    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.3.14version>
    dependency>
  1. 定义定时任务的方法:
package com.xxxx.task;

import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class TaskJob {
    private SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public void job1(){
        System.out.println("job1..."+df.format(new Date()));

    }
    public void job2(){
        System.out.println("job2..."+df.format(new Date()));
    }
}
  1. 配置spring.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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task.xsd">

    <!--设置自动化扫描范围-->
    <context:component-scan base-package="com.xxxx"/>
    <!--配置定时任务-->
    <task:scheduled-tasks>
    	<!--ref表示任务类,method表示任务类定时方法,cron表示定时规则-->
        <task:scheduled ref="taskJob" method="job1" cron="0/2 * * * * ?"/>
        <task:scheduled ref="taskJob" method="job2" cron="0/4 * * * * ?"/>
    </task:scheduled-tasks>
</beans>

corn表达式有至少6个(或7个)有空格分割的时间元素,这些元素依次为:

Cron表达式:
1.秒 0-59
2.分 0-56
3.小时 0-23
4.月份中日期 1-31
5.月份 1-12或JAN-SAT
6.星期中的日期 1-7或SUN-SAT
7.年份 1970-2099

  •  *表示每一;
    
  •  ?表示不确定,可用在日和周几字段;
    
  •  -表示指定一个范围;      10-15
    
  •  ,表示指定数个值;       5,10,15     第5,10,15*执行
    
  •  /表示指定一个值的增加幅度;  0/5     0秒开始,每5秒执行一次
    
  •  L表示last最后,比如一周的最后一天
    
  •  W表示离当前日期最近的工作日,包括当天     15W 离15日最近的工作日
    
  •  #表示该月第几个周几      6#3     该月第三个(3)周五(6)
    

2.注解配置

  1. 配置pom.xml
  2. 配置spring.xml
<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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task.xsd">

    
    <context:component-scan base-package="com.xxxx"/>
    
    <task:annotation-driven/>
beans>
  1. 给需要定时的方法添加注解**@Scheduled(cron="")**
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 {
    private SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Scheduled(cron = "0/2 * * * * ?")
    public void job1(){
        System.out.println("job1..."+df.format(new Date()));

    }
    @Scheduled(cron = "0/5 * * * * ?")
    public void job2(){
        System.out.println("job2..."+df.format(new Date()));
    }
}

Spring JDBC 和 事务控制

1. Spring整合JDBC环境

  1. pom.xml文件添加坐标依赖

    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-contextartifactId>
      <version>5.3.14version>
    dependency>
    
    <dependency>
      <groupId>org.aspectjgroupId>
      <artifactId>aspectjweaverartifactId>
      <version>1.8.9version>
    dependency>
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-testartifactId>
      <version>5.2.4.RELEASEversion>
      <scope>testscope>
    dependency>

    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-jdbcartifactId>
      <version>5.2.4.RELEASEversion>
    dependency>
    
    <dependency>
      <groupId>org.springframeworkgroupId>
      <artifactId>spring-txartifactId>
      <version>5.2.4.RELEASEversion>
    dependency>
    
    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>8.0.19version>
    dependency>
    
    <dependency>
      <groupId>com.mchangegroupId>
      <artifactId>c3p0artifactId>
      <version>0.9.5.5version>
    dependency>
  1. 配置jdbc.properties文件
#驱动名
#jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.driver=com.mysql.cj.jdbc.Driver
#数据库连接
#jdbc.url=jdbc:mysql://localhost:3306/datebaseName?
jdbc.url=jdbc:mysql://localhost:3306/spring_jdbc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=UTC
#数据库用户名
jdbc.user=root
#数据库用户密码
jdbc.password=root
  1. 配置spring.xml文件
<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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <context:component-scan base-package="com.xxxx"/>

    
    <context:property-placeholder location="jdbc.properties"/>
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    bean>

beans>
  1. 加载properties配置
 
    <context:property-placeholder location="jdbc.properties"/>
  1. 配置c3p0数据源
 
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>
  1. 配置jdbcTemplae模板对象,并注入一个数据源
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    bean>
  1. 测试
    获取上下文环境
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");

获取模板类

JdbcTemplate jdbcTemplate= (JdbcTemplate) ac.getBean("jdbcTemplate");

crud操作

String sql="SELECT count(1) FROM tb_account";
Integer total=jdbcTemplate.queryForObject(sql,Integer.class);

test01

package com.xxxx.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class SpringJdbcTest01 {
    @Test
    public void testJdbc(){
        //获取上下文环境
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
        //获取模板类
        JdbcTemplate jdbcTemplate= (JdbcTemplate) ac.getBean("jdbcTemplate");
        //crud操作
        String sql="SELECT count(1) FROM tb_account";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class);

        System.out.println(total);
    }
    @Test
    public void testJdbc02(){
        //获取上下文环境
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
        //获取模板类
        JdbcTemplate jdbcTemplate= (JdbcTemplate) ac.getBean("jdbcTemplate");
        //crud操作
        String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class,1);

        System.out.println(total);
    }
}

test02

package com.xxxx.test;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class SpringJdbcTest02 {
    private JdbcTemplate jdbcTemplate;
    @Before
    public void init(){
        //获取上下文环境
        ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
        //获取模板类
        jdbcTemplate= (JdbcTemplate) ac.getBean("jdbcTemplate");
    }
    @Test
    public void testJdbc(){
        //crud操作
        String sql="SELECT count(1) FROM tb_account";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class);

        System.out.println(total);
    }
    @Test
    public void testJdbc02(){
        //crud操作
        String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class,1);
        System.out.println(total);
    }
}

test03

package com.xxxx.test;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)//将测试运行在spring测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"})//设置要加载的配置文件
public class SpringJdbcTest03 {
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Test
    public void testJdbc(){
        //crud操作
        String sql="SELECT count(1) FROM tb_account";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class);

        System.out.println(total);
    }
    @Test
    public void testJdbc02(){
        //crud操作
        String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class,1);
        System.out.println(total);
    }
}

test04 设计一个基础类,其他类继承该类

package com.xxxx.test;

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)//将测试运行在spring测试环境中
@ContextConfiguration(locations = {"classpath:spring.xml"})//设置要加载的配置文件
public class BaseTest {
}

package com.xxxx.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

public class SpringJdbcTest04 extends BaseTest{
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Test
    public void testJdbc(){
        //crud操作
        String sql="SELECT count(1) FROM tb_account";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class);

        System.out.println(total);
    }
    @Test
    public void testJdbc02(){
        //crud操作
        String sql="SELECT count(1) FROM tb_account WHERE user_id=?";
        Integer total=jdbcTemplate.queryForObject(sql,Integer.class,1);
        System.out.println(total);
    }
}

2. 持久层账户模块操作

2.1账户接口方法定义

定义实体类:
Account.java

package com.xxxx.entity;

import java.util.Date;

/**
 * 用户账户类:
 */
public class Account {
    private Integer accountId;//账户id
    private String accountName;//账户名称
    private String accountType;//账户类型
    private Double money;//账户金额
    private String remark;//账户备注
    private Integer userId;//用户id
    private Date createTime;//创建时间
    private Date updateTime;//修改事件

    public Account() {
    }

    public Account(String accountName, String accountType, Double money, String remark, Integer userId) {
        this.accountName = accountName;
        this.accountType = accountType;
        this.money = money;
        this.remark = remark;
        this.userId = userId;
    }

    public Integer getAccountId() {
        return accountId;
    }

    public void setAccountId(Integer accountId) {
        this.accountId = accountId;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public String getAccountType() {
        return accountType;
    }

    public void setAccountType(String accountType) {
        this.accountType = accountType;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

定义接口类:
IAccountDao.java

package com.xxxx.dao;

import com.xxxx.entity.Account;

import java.util.List;

public interface IAccountDao {
  /**
   *   1.账户添加
   *    添加账户记录,返回受影响的行数
   *    批量添加账户记录,返回受影响的行数
   *    添加账户记录,返回主键
   */
    public int addAccount(Account account);
    public int addAccountHasKey(Account account);
    public int addAccountBatch(List<Account> accounts);
    /**
     * 2.账户修改
     *  修改账户记录,返回受影响的行数
     *  批量修改账户记录,返回受影响的行数
     */
    public int updateAccount(Account account);
    public int updateAccountBatch(List<Account> accounts);
    /**
     * 3.账户删除
     *      删除账户记录,返回受影响的行数
     *      批量删除账户记录,返回受影响的行数
     */
    public int deleteAccount(int accountId);
    public int deleteAccountBatch(Integer[] ids);
    /**
     * 4.账户查询
     *     查询指定账户的账户总记录数,返回总记录数
     *     查询指定账户的账户详情,返回账户对象
     *     多条件查询指定用户的账户列表,返回账户集合
     */
    public int queryAccountCount(int userId);
    public Account queryAccountById(int accountId);
    public List<Account> queryAccountByParams(Integer userId,String accountName,String accountType,String createTime);
}

账户模块实现类:
AccountDaoImpl.java

package com.xxxx.impl;

import com.xxxx.dao.IAccountDao;
import com.xxxx.entity.Account;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {
    //注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public int addAccount(Account account) {}

    @Override
    public int addAccountHasKey(Account account) {}

    @Override
    public int addAccountBatch(List<Account> accounts) {}

    @Override
    public int updateAccount(Account account) {}

    @Override
    public int updateAccountBatch(List<Account> accounts) {}

    @Override
    public int deleteAccount(int accountId) {}

    @Override
    public int deleteAccountBatch(Integer[] ids) {}

    @Override
    public int queryAccountCount(int userId) {}

    @Override
    public Account queryAccountById(int accountId) {}

    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {}

2.2 账户记录添加实现

添加单条记录返回受影响的行数
方法实现:

    /**
     * 添加单条记录,返回受影响行数
     * @param account
     * @return
     */
    @Override
    public int addAccount(Account account) {
        //定义sql语句
        String sql="insert into tb_account(account_name,account_type,money,remark,user_id,create_time,update_time)" +
                "values(?,?,?,?,?,now(),now())";
        //设置参数
        Object[] objs={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId()};
        int row=jdbcTemplate.update(sql,objs);
        return row;
    }

测试:

package com.xxxx.test;

import com.xxxx.dao.IAccountDao;
import com.xxxx.entity.Account;
import org.junit.Test;

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

public class SpringJdbcAddTest extends BaseTest{
    //注入IAccountDao
    @Resource
    private IAccountDao iAccountDao;
    @Test
    public void testAddCount(){
        //准备要添加的数据
        Account account=new Account("账户3","工商银行",200.0,"奖金",1);
        //调用对象中的添加方法,返回行数
        int row=iAccountDao.addAccount(account);
        System.out.println("添加账户,受影响的行数:"+row);
    }
}

添加单条记录返回主键:
方法实现:

    /**
     * 添加单条记录返回主键
     * @param account
     * @return
     */
    @Override
    public int addAccountHasKey(Account account) {
        //定义sql语句
        String sql="insert into tb_account(account_name,account_type,money,remark,user_id,create_time,update_time)" +
                "values(?,?,?,?,?,now(),now())";
        //设置参数
        Object[] objs={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId()};
        //定义KeyHolder 获取主键
        KeyHolder keyHolder=new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                PreparedStatement ps=connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
                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 ps;
            }
        }, keyHolder);
        int key=keyHolder.getKey().intValue();
        return key;
//        jdbcTemplate.update(connection->{
//            //预编译sql语句,并设置返回主键
//            PreparedStatement ps= connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//            //设置参数
//            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 ps;
//        },keyHolder);
//        //得到主键
//        int key=keyHolder.getKey().intValue();
//        return key;
    }

测试:

    @Test
    public void testAddAccountHasKey(){
        //准备要添加的数据
        Account account=new Account("账户4","中国银行",800.0,"工资",2);
        int key= iAccountDao.addAccountHasKey(account);
        System.out.println("添加账户,返回主键:"+key);
    }

批量添加多条记录:
方法实现:

/**
     * 批量添加记录,返回受影响行数
     * @param accounts
     * @return
     */
    @Override
    public int addAccountBatch(List<Account> accounts) {
        //定义sql语句
        String sql="insert into tb_account(account_name,account_type,money,remark,user_id,create_time,update_time)" +
                "values(?,?,?,?,?,now(),now())";
        int rows=jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                //循环遍历accounts
                Account account=accounts.get(i);
                //设置参数
                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());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
        return rows;
    }

测试:

@Test
    public void testAddAccountBatch(){
        //准备要添加的数据
        Account account1=new Account("账户5","农业银行",200.0,"零花钱",3);
        Account account2=new Account("账户6","工商银行",100.0,"餐饮费",3);
        Account account3=new Account("账户7","中国银行",300.0,"交通费",3);
        Account account4=new Account("账户8","中国银行",1000.0,"工资",3);
        List<Account> accounts=new ArrayList<>();
        accounts.add(account1);
        accounts.add(account2);
        accounts.add(account3);
        accounts.add(account4);

        int rows=iAccountDao.addAccountBatch(accounts);
        System.out.println("批量添加账户,返回受影响行数:"+rows);
    }

2.3 账户记录查询实现

查询用户的账户总记录数:
方法实现:

    /**
     * 查询指定用户账户总记录数,返回记录数
     * @param userId
     * @return
     */
    @Override
    public int queryAccountCount(int userId) {
        //sql语句
        String sql="select count(*) from tb_account where user_id=?";
        int count=jdbcTemplate.queryForObject(sql,Integer.class,userId);
        return count;
    }

测试:

    @Test
    public void testQueryAccountCount(){
        //调用对象中的添加方法,返回行数
        int total=iAccountDao.queryAccountCount(1);
        System.out.println("查询user_id=1的总记录数:"+total);
    }

查询指定账户,返回账户对象:
方法实现:

    /**
     * 查询指定账户的账户详情,返回账户对象
     * @param accountId
     * @return
     */
    @Override
    public Account queryAccountById(int accountId) {
        String sql="select * from tb_account where user_id=?";
        //查询对象
        Account account=jdbcTemplate.queryForObject(sql,(ResultSet rs,int i) ->{
            Account acc=new Account();
            acc.setAccountId(accountId);
            acc.setAccountName(rs.getString("account_name"));
            acc.setAccountType(rs.getString("account_type"));
            acc.setMoney(rs.getDouble("money"));
            acc.setRemark(rs.getString("remark"));
            acc.setUserId(rs.getInt("user_id"));
            acc.setCreateTime(rs.getDate("create_time"));
            acc.setUpdateTime(rs.getDate("update_time"));
            return acc;
        },accountId);
        return account;
    }

测试:

    @Test
    public void testQueryAccountById(){
        //接收返回对象
        Account account=iAccountDao.queryAccountById(1);
        System.out.println("查询账户,返回对象:"+account);
    }

多条件查询指定用户的账户列表,返回账户集合
方法实现:

   /**
     * 多条件查询指定用户的账户列表,返回账户集合
     * @param userId    用户id
     * @param accountName   账户名
     * @param accountType   账户类型
     * @param createTime    创建时间
     * @return
     */
    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
        String sql="select * from tb_account where user_id=?";

        //定义参数列表
        List<Object> params=new ArrayList<>();
        params.add(userId);
        //判断参数是否为空,不为空则拼接sql语句以及设置对应参数
        //account_name
        if(StringUtils.isNotBlank(accountName)){
            sql += " and account_name like concat('%',?,'%')";
            //设置参数
            params.add(accountName);
        }
        //account_type
        if(StringUtils.isNotBlank(accountType)){
            sql += " and account_type=?";
            params.add(accountType);
        }
        //create_time
        if(StringUtils.isNotBlank(createTime)){
            sql += " and create_time < ?";
            params.add(createTime);
        }
        //将集合转换成数组
        Object[] objects=params.toArray();
        List<Account> accountList=jdbcTemplate.query(sql, objects, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet resultSet, int i) throws SQLException {
                Account acc=new Account();
                acc.setAccountId(resultSet.getInt("account_id"));
                acc.setAccountName(resultSet.getString("account_name"));
                acc.setAccountType(resultSet.getString("account_type"));
                acc.setMoney(resultSet.getDouble("money"));
                acc.setRemark(resultSet.getString("remark"));
                acc.setUserId(resultSet.getInt("user_id"));
                acc.setCreateTime(resultSet.getTimestamp("create_time"));
                acc.setUpdateTime(resultSet.getDate("update_time"));
                return acc;
            }});
        return accountList;
//        Object[] objs={userId};
//        List accountList=jdbcTemplate.query(sql,objs,(ResultSet rs,int i) -> {
//            Account acc=new Account();
//            acc.setAccountId(rs.getInt("account_id"));
//            acc.setAccountName(rs.getString("account_name"));
//            acc.setAccountType(rs.getString("account_type"));
//            acc.setMoney(rs.getDouble("money"));
//            acc.setRemark(rs.getString("remark"));
//            acc.setCreateTime(rs.getDate("create_time"));
//            acc.setUpdateTime(rs.getDate("updata_time"));
//            return acc;
//        });
//        return accountList;
        }
}

测试:

@Test
public void testQueryAccountByParams(){
// List accountList=iAccountDao.queryAccountByParams(3,null,null,null);
// List accountList=iAccountDao.queryAccountByParams(3,"5",null,null);
  List<Account> accountList=iAccountDao.queryAccountByParams(3,"5","中国银行",null);
  for (Account acc:accountList) {
     System.out.println("账户id: "+acc.getAccountId());
  }
}

2.4 账户记录更新实现

更新指定账户记录,返回受影响行数
方法实现:

    /**
     * 修改账户记录,返回受影响的行数
     * @param account
     * @return
     */
    @Override
    public int updateAccount(Account account) {
        //设置sql语句
        String sql="update tb_account set account_name=?,account_type=?,money=?,remark=?,user_id=?,update_time=now() where account_id=?";
        Object[] objects={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId(),account.getAccountId()};
        int rows=jdbcTemplate.update(sql,objects);
        return rows;
    }

测试:

    @Test
    public void updateAccountTest(){
        //设置需要更新的账户信息
        Account account=new Account("账户8","农业银行",120.0,"零花钱",3);
        account.setAccountId(14);
        int rows=iAccountDao.updateAccount(account);
        System.out.println(rows);
    }

批量修改账户记录,返回受影响的行数
方法实现:

/**
     * 批量修改账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int updateAccountBatch(List<Account> accounts) {
        String sql="update tb_account set account_name=?,account_type=?,money=?,remark=?,user_id=?,update_time=now() where account_id=?";
        int rows=jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account acc=accounts.get(i);
                //设置参数
                ps.setString(1,acc.getAccountName());
                ps.setString(2,acc.getAccountType());
                ps.setDouble(3,acc.getMoney());
                ps.setString(4,acc.getRemark());
                ps.setInt(5,acc.getUserId());
                ps.setInt(6,acc.getAccountId());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
        return rows;
    }

测试:

    @Test
    public void updateAccountBatchTest(){
        //设置需要更新的账户信息
        List<Account> accountList=new ArrayList<>();
        Account account1=new Account("账户1","建设银行",180.0,"零花钱",1);
        account1.setAccountId(14);
        Account account2=new Account("账户2","交通银行",280.0,"零花钱",2);
        account2.setAccountId(14);
        Account account3=new Account("账户3","农业银行",300.0,"零花钱",3);
        account3.setAccountId(14);
        Account account4=new Account("账户4","中国银行",320.0,"零花钱",4);
        account4.setAccountId(14);
        accountList.add(account1);
        accountList.add(account2);
        accountList.add(account3);
        accountList.add(account4);
        int rows=iAccountDao.updateAccountBatch(accountList);
        System.out.println(rows);
    }

2.5 账户记录删除实现

通过账户id删除单条记录,返回受影响行数
方法实现:

    /**
     * 删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    @Override
    public int deleteAccount(int accountId) {
        String sql="delete from tb_account where account_id=?";
        int rows=jdbcTemplate.update(sql,accountId);
        return rows;
    }

测试:

    @Test
    public void deleteAccountTest(){
        int rows=iAccountDao.deleteAccount(18);
        System.out.println(rows);
    }

批量删除账户记录,返回受影响行数
方法实现:

    /**
     * 批量删除账户记录,返回受影响的行数
     * @param ids
     * @return
     */
    @Override
    public int deleteAccountBatch(Integer[] ids) {
        String sql="delete from tb_account where account_id=?";

        int rows=jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setInt(1,ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        }).length;
        return rows;
    }

测试:

@Test
    public void deleteAccountBatchTest(){
        Integer[] ids={11,12,13,14,15};
        int rows=iAccountDao.deleteAccountBatch(ids);
        System.out.println(rows);
    }

3. 事务控制

3.1 转账场景模拟实现

用户账户类 — Account.java

package com.xxxx.entity;

import java.util.Date;

/**
 * 用户账户类:
 */
public class Account {
    private Integer accountId;//账户id
    private String accountName;//账户名称
    private String accountType;//账户类型
    private Double money;//账户金额
    private String remark;//账户备注
    private int userId;//用户id
    private Date createTime;//创建时间
    private Date updateTime;//修改事件

    public Account() {
    }

    public Account(String accountName, String accountType, Double money, String remark, Integer userId) {
        this.accountName = accountName;
        this.accountType = accountType;
        this.money = money;
        this.remark = remark;
        this.userId = userId;
    }

    public Integer getAccountId() {
        return accountId;
    }

    public void setAccountId(Integer accountId) {
        this.accountId = accountId;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

    public String getAccountType() {
        return accountType;
    }

    public void setAccountType(String accountType) {
        this.accountType = accountType;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
}

账户模块接口定义 — IAccountDao.java(interface)

package com.xxxx.dao;

import com.xxxx.entity.Account;

import java.util.List;

/**
 * 账户模块 定义接口
 *  1.账户添加
 *      添加账户记录,返回受影响的行数
 *      批量添加账户记录,返回受影响的行数
 *      添加账户记录,返回主键
 *  2.账户修改
 *      修改账户记录,返回受影响的行数
 *      批量修改账户记录,返回受影响的行数
 *  3.账户删除
 *      删除账户记录,返回受影响的行数
 *      批量删除账户记录,返回受影响的行数
 *  4.账户查询
 *      查询指定账户的账户总记录数,返回总记录数
 *      查询指定账户的账户详情,返回账户对象
 *      多条件查询指定用户的账户列表,返回账户集合
 */
public interface IAccountDao {
  /**
   *   1.账户添加
   *    添加账户记录,返回受影响的行数
   *    批量添加账户记录,返回受影响的行数
   *    添加账户记录,返回主键
   */
    public int addAccount(Account account);
    public int addAccountHasKey(Account account);
    public int addAccountBatch(List<Account> accounts);
    /**
     * 2.账户修改
     *  修改账户记录,返回受影响的行数
     *  批量修改账户记录,返回受影响的行数
     */
    public int updateAccount(Account account);
    public int updateAccountBatch(List<Account> accounts);
    /**
     * 3.账户删除
     *      删除账户记录,返回受影响的行数
     *      批量删除账户记录,返回受影响的行数
     */
    public int deleteAccount(int accountId);
    public int deleteAccountBatch(Integer[] ids);
    /**
     * 4.账户查询
     *     查询指定账户的账户总记录数,返回总记录数
     *     查询指定账户的账户详情,返回账户对象
     *     多条件查询指定用户的账户列表,返回账户集合
     */
    public int queryAccountCount(int userId);
    public Account queryAccountById(int accountId);
    public List<Account> queryAccountByParams(Integer userId,String accountName,String accountType,String createTime);

  /**
   * 支出
   * @param accountId
   * @param money
   * @return
   */
    public int outAccount(Integer accountId,double money);

  /**
   * 收入
   * @param accountId
   * @param money
   * @return
   */
    public int inAccount(Integer accountId,double money);
}

账户模块接口实现类 — AccountDaoImpl.java

package com.xxxx.impl;

import com.xxxx.dao.IAccountDao;
import com.xxxx.entity.Account;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;

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

/**
 * 账户模块接口的实现类
 */
@Repository
public class AccountDaoImpl implements IAccountDao {
    //注入JdbcTemplate模板类
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 添加单条记录,返回受影响行数
     * @param account
     * @return
     */
    @Override
    public int addAccount(Account account) {
        //定义sql语句
        String sql="insert into tb_account(account_name,account_type,money,remark,user_id,create_time,update_time)" +
                "values(?,?,?,?,?,now(),now())";
        //设置参数
        Object[] objs={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId()};
        int row=jdbcTemplate.update(sql,objs);
        return row;
    }

    /**
     * 添加单条记录返回主键
     * @param account
     * @return
     */
    @Override
    public int addAccountHasKey(Account account) {
        //定义sql语句
        String sql="insert into tb_account(account_name,account_type,money,remark,user_id,create_time,update_time)" +
                "values(?,?,?,?,?,now(),now())";
        //设置参数
        Object[] objs={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId()};
        //定义KeyHolder 获取主键
        KeyHolder keyHolder=new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
                PreparedStatement ps=connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
                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 ps;
            }
        }, keyHolder);
        int key=keyHolder.getKey().intValue();
        return key;
//        jdbcTemplate.update(connection->{
//            //预编译sql语句,并设置返回主键
//            PreparedStatement ps= connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//            //设置参数
//            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 ps;
//        },keyHolder);
//        //得到主键
//        int key=keyHolder.getKey().intValue();
//        return key;
    }

    /**
     * 批量添加记录,返回受影响行数
     * @param accounts
     * @return
     */
    @Override
    public int addAccountBatch(List<Account> accounts) {
        //定义sql语句
        String sql="insert into tb_account(account_name,account_type,money,remark,user_id,create_time,update_time)" +
                "values(?,?,?,?,?,now(),now())";
        int rows=jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                //循环遍历accounts
                Account account=accounts.get(i);
                //设置参数
                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());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
        return rows;
    }

    /**
     * 修改账户记录,返回受影响的行数
     * @param account
     * @return
     */
    @Override
    public int updateAccount(Account account) {
        //设置sql语句
        String sql="update tb_account set account_name=?,account_type=?,money=?,remark=?,user_id=?,update_time=now() where account_id=?";
        Object[] objects={account.getAccountName(),account.getAccountType(),account.getMoney(),account.getRemark(),account.getUserId(),account.getAccountId()};
        int rows=jdbcTemplate.update(sql,objects);
        return rows;
    }

    /**
     * 批量修改账户记录,返回受影响的行数
     * @param accounts
     * @return
     */
    @Override
    public int updateAccountBatch(List<Account> accounts) {
        String sql="update tb_account set account_name=?,account_type=?,money=?,remark=?,user_id=?,update_time=now() where account_id=?";
        int rows=jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                Account acc=accounts.get(i);
                //设置参数
                ps.setString(1,acc.getAccountName());
                ps.setString(2,acc.getAccountType());
                ps.setDouble(3,acc.getMoney());
                ps.setString(4,acc.getRemark());
                ps.setInt(5,acc.getUserId());
                ps.setInt(6,acc.getAccountId());
            }

            @Override
            public int getBatchSize() {
                return accounts.size();
            }
        }).length;
        return rows;
    }

    /**
     * 删除账户记录,返回受影响的行数
     * @param accountId
     * @return
     */
    @Override
    public int deleteAccount(int accountId) {
        String sql="delete from tb_account where account_id=?";
        int rows=jdbcTemplate.update(sql,accountId);
        return rows;
    }

    /**
     * 批量删除账户记录,返回受影响的行数
     * @param ids
     * @return
     */
    @Override
    public int deleteAccountBatch(Integer[] ids) {
        String sql="delete from tb_account where account_id=?";

        int rows=jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setInt(1,ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        }).length;
        return rows;
    }

    /**
     * 查询指定用户账户总记录数,返回记录数
     * @param userId
     * @return
     */
    @Override
    public int queryAccountCount(int userId) {
        //sql语句
        String sql="select count(*) from tb_account where user_id=?";
        int count=jdbcTemplate.queryForObject(sql,Integer.class,userId);
        return count;
    }

    /**
     * 查询指定账户的账户详情,返回账户对象
     * @param accountId
     * @return
     */
    @Override
    public Account queryAccountById(int accountId) {
        String sql="select * from tb_account where user_id=?";
        //查询对象
        Account account=jdbcTemplate.queryForObject(sql,(ResultSet rs,int i) ->{
            Account acc=new Account();
            acc.setAccountId(accountId);
            acc.setAccountName(rs.getString("account_name"));
            acc.setAccountType(rs.getString("account_type"));
            acc.setMoney(rs.getDouble("money"));
            acc.setRemark(rs.getString("remark"));
            acc.setUserId(rs.getInt("user_id"));
            acc.setCreateTime(rs.getDate("create_time"));
            acc.setUpdateTime(rs.getDate("update_time"));
            return acc;
        },accountId);
        return account;
    }

    /**
     * 多条件查询指定用户的账户列表,返回账户集合
     * @param userId    用户id
     * @param accountName   账户名
     * @param accountType   账户类型
     * @param createTime    创建时间
     * @return
     */
    @Override
    public List<Account> queryAccountByParams(Integer userId, String accountName, String accountType, String createTime) {
        String sql="select * from tb_account where user_id=?";

        //定义参数列表
        List<Object> params=new ArrayList<>();
        params.add(userId);
        //判断参数是否为空,不为空则拼接sql语句以及设置对应参数
        //account_name
        if(StringUtils.isNotBlank(accountName)){
            sql += " and account_name like concat('%',?,'%')";
            //设置参数
            params.add(accountName);
        }
        //account_type
        if(StringUtils.isNotBlank(accountType)){
            sql += " and account_type=?";
            params.add(accountType);
        }
        //create_time
        if(StringUtils.isNotBlank(createTime)){
            sql += " and create_time < ?";
            params.add(createTime);
        }
        //将集合转换成数组
        Object[] objects=params.toArray();
        List<Account> accountList=jdbcTemplate.query(sql, objects, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet resultSet, int i) throws SQLException {
                Account acc=new Account();
                acc.setAccountId(resultSet.getInt("account_id"));
                acc.setAccountName(resultSet.getString("account_name"));
                acc.setAccountType(resultSet.getString("account_type"));
                acc.setMoney(resultSet.getDouble("money"));
                acc.setRemark(resultSet.getString("remark"));
                acc.setUserId(resultSet.getInt("user_id"));
                acc.setCreateTime(resultSet.getTimestamp("create_time"));
                acc.setUpdateTime(resultSet.getDate("update_time"));
                return acc;
            }});
        return accountList;
//        Object[] objs={userId};
//        List accountList=jdbcTemplate.query(sql,objs,(ResultSet rs,int i) -> {
//            Account acc=new Account();
//            acc.setAccountId(rs.getInt("account_id"));
//            acc.setAccountName(rs.getString("account_name"));
//            acc.setAccountType(rs.getString("account_type"));
//            acc.setMoney(rs.getDouble("money"));
//            acc.setRemark(rs.getString("remark"));
//            acc.setCreateTime(rs.getDate("create_time"));
//            acc.setUpdateTime(rs.getDate("updata_time"));
//            return acc;
//        });
//        return accountList;
        }

    /**
     * 支出
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int outAccount(Integer accountId, double money) {
        String sql="update tb_account set money = money - ? where account_id=?";
        Object[] objects={money,accountId};
        int rows=jdbcTemplate.update(sql,objects);
        return rows;
    }

    /**
     * 收入
     * @param accountId
     * @param money
     * @return
     */
    @Override
    public int inAccount(Integer accountId, double money) {
        String sql="update tb_account set money=money+? where account_id=?";
        Object[] objects={money,accountId};
        int rows=jdbcTemplate.update(sql,objects);
        return rows;
    }
}

结果测试:

package com.xxxx.test;

import com.xxxx.service.AccountService;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;

public class SpringJdbcTest05 extends BaseTest{
    @Resource
    private AccountService accountService;
    @Test
    public void test(){
        //account_id=1向account_id转账200
        int rs=accountService.updateAccountByTranfer(1,2,200.0);
        if(rs==1){
            System.out.println("转账成功! "+rs);
        }else {
            System.out.println("转账失败!"+rs);
        }

    }

}

3.2 Spring事务

事务四大特性:

  • 原子性
  • 一致性:只有执行前的状态或者执行完成的状态,不会访问到中间状态
  • 隔离性:
    读未提交READ_UNCOMMITTED
    读已提交READ_COMMITTED
    可重复度REPEATABLE_READ
    串行化SERIALIZABLE
  • 持久性

Spring事务核心接口:
Spring并不直接管理事务,而是提供多种事务管理器,将事务委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器接口org.springframework.transaction.PlatformTransactionManager ,通过这个接口为各个平台如JDBC、Hibernate等提供了对应的事务管理器。接口内容如下:

public interface PlatformTransactionManager extends TransactionManager {
    //由TransactionDefinition 得到TransactionStatus 对象
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
	//提交
    void commit(TransactionStatus var1) throws TransactionException;
	//回滚
    void rollback(TransactionStatus var1) throws TransactionException;
}

Spring并不关心具体的事务管理机制,只是为不同的事务API提供一致的编程模型。

3.2.1 JDBC事务

如果应用程序中直接使用JDBC来进行持久化,此时使用DataSourceTransactionManager来处理事务边界,首先需要将下面的xml内容装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
bean>

实际上,DataRourseTransactionManager是通过调用java.sql.Connection来管理事务,后者是通过DataSource获取到的。通过连接commit()方法来提交事务,同样,事务失败则通调用rollback()方法,进行回滚。

3.2.2 Hibernate事务

如果应用程序持久化通过Hibernate实现,那么需要使用HibernateTransactionManager。对于Hibernate3需要在spring上下文中添加如下声明:

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory">
    bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager实现细节是它将事务管理职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之将会调用rollback()方法。

3.2.3 JAVA持久化API事务(JPA)

使用JPA需要使用Spring的JpaTransactionManager来处理事务。需要在spring中配置JpaTranscationManager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
   <property name="sessionFactory" ref="sessionFactory"/>
bean>

JpaTranscationManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTranscationManager将与由工厂所产生的JpaEntitynManager合作来构建事务。

3.2.4 JAVA原生API事务

如果应用程序没有使用以上所述事务管理,或是跨越了多个事务管理源(比如两个或者多个不同的数据源),此时需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TranscationManager"/>
    bean>

JtaTransactionManager将事务管理任务责任委托给javax.transactio.UserTranscation和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

3.3 Spring事务控制配置

通过jdbc持久化事务,对于事务配置实现有两种方法:xml配置、注解配置

3.3.1 XML配置

1. 添加命名空间
在spring.xml配置文件添加事务和aop的命名空间
事务:

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd

AOP:

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
<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"
       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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">
  1. 设置aop代理

    <aop:aspectj-autoproxy/>
  1. 配置事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        <property name="dataSource" ref="dataSource"/>
    bean>
  1. 配置事务相关通知

<tx:advice id="txAdvice" transaction-manager="txManager">
        
        <tx:attributes>
            
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
        tx:attributes>
    tx:advice>
  1. 配置aop
    
    <aop:config>
        <aop:pointcut id="cut" expression="execution(* com.xxxx.service..*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
    aop:config>

3.3.2 注解配置

  1. 添加命名空间
  2. 配置事务管理器
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>
  1. 配置注解支持

    <tx:annotation-driven transaction-manager="txManager"/>
  1. 在方法上添加事务注解
@Transactional(propagation = Propagation.REQUIRED)

你可能感兴趣的:(学习,java,spring)