Loading

02、Spring

一、引言

1.1、原生web开发中存在哪些问题?

  传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。

  部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。

  侵入性强,移植性差(例如:DAO实现的更换,从Connection到sqlSession)。

二、Spring框架

2.1、概念

Spring是一个项目管理框架,同时也是一套Java EE解决方案。

Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。

Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。

2.2、访问与下载

官方网站:https://spring.io/

下载地址:http://repo.spring.io/release/org/springframework/spring/

三、Spring架构组成

Spring架构由诸多模块组成,可分类为

核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。

测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。

数据访问:事务,DAO支持,JDBC,ORM,封送XML。

Spring MVC和 Spring WebFlux Web框架。

集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。

语言:Kotlin,Groovy,动态语言。

四、自定义工厂

4.1、配置文件

userDao = com.huiruan.dao.impl.UserDaoImpl
userService = com.huiruan.service.impl.UserServiceImpl

4.2、工厂类

public class MyFactory {
    private Properties properties = new Properties();

    public MyFactory() {
    }

    public  MyFactory(String conf) throws IOException {
//        加载配置文件
        properties.load(MyFactory.class.getResourceAsStream(conf));
    }

//    获取对象
    public Object getBean(String beanName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//        获取类路径
        String property = properties.getProperty(beanName);
        if (property != null){
            Class claz = null;
//            反射:加载类对象
            claz = Class.forName(property);
//            反射:获取对象
            return claz.newInstance();
        }
        return null;
    }
}

五、构建Maven项目

5.1、新建项目

5.2、选择Maven目录

5.3、GAV坐标

六、Spring环境搭建

6.1、pom.xml中引入Spring常用依赖

    <dependencies>
        <!-- spring常用依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
    </dependencies>

6.2、创建Spring配置文件

  命名无限制,约定俗成命名有:spring-context.xml、applicationContext.xml、beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

七、Spring工厂编码

定义目标Bean类型

public class UserDaoImpl {
    public void selectUserById(){
        System.out.println("Hello World");
    }
}

spring-context.xml中的<beans >内部配置bean标签

        <!-- 配置实例(id:"唯一标识" class="需要被创建的目标对象全限定名") -->
        <bean id="userDaoImpl" class="com.huiruan.dao.UserDaoImpl"></bean>

调用Spring工厂API (ApplicationContext接口)

public class TestFactory {
//    程序中的对象都交由Spring的ApplicationContext工厂进行创建。
    public static void main(String[] args) {
//        1,读取配置文件中所需创建的bean对象,并获得工厂对象
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
//        2,通过id获取bean对象
        UserDaoImpl userDao = (UserDaoImpl) applicationContext.getBean("userDaoImpl");
//        3,使用对象
        userDao.selectUserById();
    }
}

八、依赖与配置文件详解

  Spring框架包含多个模块,每个模块各司其职,可结合需求引入相关依赖Jar包实现功能。

8.1、Spring依赖关系

注意:Jar包彼此存在依赖,只需引入最外层Jar即可由Maven自动将相关依赖Jar引入到项目中。

8.2、schema

配置文件中的顶级标签中包含了语义化标签的相关信息

xmlns:语义化标签所在的命名空间。

xmlns:xsi: XMLSchema-instance标签遵循Schema标签标准。

xsi:schemaLocation: xsd文件位置,用以描述标签语义、属性、取值范围等。

九、loC (Inversion of Control)控制反转【重点】

lnverse Of Controll: 控制反转

反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转)

解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健

9.1、项目中强耦合问题

public class UserDAOImpl implements UserDA0{...}
public class UserServiceImpl implements UserService {
    // !!!强耦合了UserDAOImpl!!!,使得UserServicelmpl变得不稳健!!
    private UserDA0 userDAO = new UserDAOImp1();
    @override
    public User queryUser(){
        return userDAO.queryUser();
    }
}

9.2、解决方案

//不引用任何一个具体的组件(实现类),在需要其他组件的位置预留存取值入口(set/get)
public class UserServiceImpl implements UserService {
    //!!!不再耦合任何DAO实现!!!,消除不稳健因素!!!
    private UserDA0 userDA0;
    //为userDA0定义set/get,允许userDA0属性接收spring赋值
    //Getters And Setters
    @override
    public User queryUser(){
        return userDAO.queryUser();
    }
}
<bean id="userDao" class="com.huiruan.dao.impl.UserDaoImpl"></bean>
<!-- UserServiceImpl组件 -->
<bean id="userservice" class="com.huiruan.service.impl.UserServiceImpl">
    <!-- 由spring为userDAO属性赋值,值为id="userDao"的bean -->
    <property name="userDao" ref="userDao"/>
</bean>

此时,如果需要更换其他UserDAO实现类,则UserServicelmpl不用任何改动!

则此时的UserServicelmpl组件变得更加稳健!

十、DI (Dependency Injection)依赖注入【重点】

10.1、概念

  在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

10.2、Set注入

  创建对象时,Spring工厂会通过Set方法为对象的属性赋值。

10.2.1、定义目标Bean类型

public class User {
    private Integer id;
    private String password;
    private String sex;
    private Integer age;
    private Date bornDate;
    private String[] hobbys;
    private Set<String> phones;
    private List<String> names;
    private Map<String,String> countries;
    private Properties files;
}

10.2.2、基本类型+字符串类型+日期类型

        <bean name="user" class="com.huiruan.entity.User">
                <property name="id" value="1001"></property>
                <property name="password" value="123456"/>
                <property name="sex" value="male"/>
                <property name="age" value="20"/>
                <property name="bornDate" value="2021/08/24"/> <!-- 注意格式 -->
        </bean>

10.2.3、容器类型

        <bean id="user2" class="com.huiruan.entity.User">
                <!--array-->
                <property name="hobbys">
                        <array>
                                <value>run</value>
                                <value>swim</value>
                                <value>climb</value>
                        </array>
                </property>
                <!-- set -->
                <property name="phones">
                        <set>
                                <value>110</value>
                                <value>120</value>
                                <value>119</value>
                        </set>
                </property>
                <!--list-->
                <property name="names">
                        <list>
                                <value>tom</value>
                                <value>jack</value>
                                <value>marry</value>
                        </list>
                </property>
                <!-- map -->
                <property name="countries" >
                        <map>
                                <entry key="cn" value="china"></entry>
                                <entry key="us" value="america"/>
                                <entry key="kr" value="korea"/>
                        </map>
                </property>
                <!-- properties -->
                <property name="files">
                        <props>
                                <prop key="first">one</prop>
                                <prop key="second">two</prop>
                                <prop key="third">three</prop>
                        </props>
                </property>
        </bean>

10.2.4、自建类型

        <!-- 次要bean,被作为属性 -->
        <bean id="empDao" class="com.huiruan.dao.impl.EmpDaoImpl"></bean>

        <!-- 主要bean,操作的主体 -->
        <bean id="empServiceImpl" class="com.huiruan.service.impl.EmpServiceImpl">
                <!-- empDao属性引用empDao对象 -->
                <property name="empDao" ref="empDao"></property>
        </bean>

        <!-- 次要bean,被作为属性 -->
        <bean id="address" class="com.huiruan.entity.Address">
                <property name="position" value="广州市番禺区"/>
                <property name="zipcode" value="100001"/>
        </bean>

        <!-- 主要bean,操作的主体 -->
        <bean id="user3" class="com.huiruan.entity.User">
                <!-- User属性引用Address对象 -->
                <property name="address" ref="address"/>
        </bean>

10.3、构造注入【了解】

  创建对象时,Spring工厂会通过构造方法为对象的属性赋值。

10.3.1、定义目标Bean类型

public class Student {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;

    public Student(Integer id, String name, String sex, Integer age) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
}

10.3.2、注入

        <!-- 构造注入 -->
        <bean id="student" class="com.huiruan.entity.Student">
                <!-- 除标签名称有变化,其他均和Set注入一致 -->
                <constructor-arg name="id" value="1234"/>
                <constructor-arg name="name" value="tom"/>
                <constructor-arg name="age" value="20"/>
                <constructor-arg name="sex" value="male"/>
        </bean>

10.4、自动注入【了解】

不用在配置中指定为哪个属性赋值,及赋什么值

由spring自动根据某个"原则",在工厂中查找一个bean,为属性注入属性值

public class EmpServiceImpl implements EmpService {
    private EmpDao empDao;
  // getters and setters    ... }
        <bean id="empDao" class="com.huiruan.dao.impl.EmpDaoImpl"></bean>

        <!-- 为EmpServiceImpl中的属性基于类型自动注入值 -->
        <bean id="empServiceImpl" class="com.huiruan.service.impl.EmpServiceImpl" autowire="byType"></bean>
        <bean id="empDao" class="com.huiruan.dao.impl.EmpDaoImpl"></bean>

        <!-- 为EmpServiceImpl中的属性基于名称自动注入值 -->
        <bean id="empServiceImpl" class="com.huiruan.service.impl.EmpServiceImpl" autowire="byName"></bean>

十一、Bean细节

11.1、控制简单对象的单例、多例模式

  配置<bean scope="singleton | prototype" />

        <!--
            singleton(默认):每次调用工厂,得到的都是同一个对象。
            prototype: 每次调用工厂,都会创建新的对象。
         -->
        <bean id="student" class="com.huiruan.entity.Student" scope="singleton"></bean>

注意: 需要根据场景决定对象的单例、多例模式。

可以共用: Service、DAO、SqlSessionFactory (或者是所有的工厂)。

不可共用: Connection、SqlSession、ShoppingCart。

11.2、FactoryBean创建复杂对象【了解】

作用: 让Spring可以创建复杂对象、或者无法直接通过反射创建的对象。

 

11.2.1、实现FactoryBean接口

 

public class MyConnectionFactoryBean implements FactoryBean<Connection> {
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        String url ="jdbc:mysql://localhost:3306/mydb2?userUnicade=true&characterEncoding=utf8";
        String passworld ="root";
        String username = "root";
        Connection connection = DriverManager.getConnection(url,passworld,username);
        return connection;
    }

    public Class<?> getObjectType() {
        return Connection.class;
    }

    public boolean isSingleton() {
        return false;
    }
}

注意: isSingleton方法的返回值,需根据所创建对象的特点决定返回true/false。

例如: Connection不应该被多个用户共享,返回false。

例如: SqlSessionFactory重量级资源,不该过多创建,返回true。

11.2.2、配置spring-context.xml

 

<bean id="connection" class="com.huiruan.utils.MyConnectionFactoryBean"/>

11.2.3、特例

 

十二、Spring工厂特性

12.1、饿汉式创建优势

工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。

提高程序运行效率。避免多次lO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)

12.2、生命周期方法

自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。

自定义销毁方法:添加“destroy-method"属性,Spring则会在销毁对象之前,调用此方法。

销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。

分类:Singleton对象由Spring容器销毁、Prototype对象由JVM销毁。

public class Address {
    private String position;
    private String zipcode;

    public Address() {
        System.out.println("构造方法 执行");
    }

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        System.out.println("set方法执行");
        this.position = position;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    // 初期化
    @PostConstruct
    public void init(){
        System.out.println("init 初期化");
    }

    // 销毁
    @PreDestroy
    public void destroy(){
        System.out.println("destroy 销毁");
    }
}
        <bean id="address" class="com.huiruan.entity.Address" init-method="init" destroy-method="destroy">
                <property name="position" value="广州市番禺区"/>
                <property name="zipcode" value="100001"/>
        </bean>
    @Test
    public void getAddress(){
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
        System.out.println("构建完成");
        applicationContext.close();
    }

12.3、生命周期阶段

单例bean: singleton
随工厂启动 创建 ==》构造方法·==》set方法(注入值) ==》init(初始化) ==》构建完成==》随工厂关闭销毁

多例bean: prototype
被使用时 创建 ==》构造方法 ==》set方法(注入值)=》init(初始化) ==》构建完成==》JVM垃圾回收销毁

十三、代理设计模式

13.1、概念

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。

 

 

13.2、静态代理设计模式

通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

 

 

代理类 = 实现原始类相同接口 添加辅助功能调用原始类的业务方法

 

静态代理的问题

  代理类数量过多,不利于项目的管理。

  多个代理类的辅助功能代码冗余,修改时,维护性差。

 1、FangDongService

public interface FangDongService {
    public void zufang();
}

2、FangDongServiceImpl

public class FangDongServiceImpl implements FangDongService {
    public void zufang() {
        System.out.println("签合同");
        System.out.println("收房租");
    }
}

3、FangDongProxy

public class FangDongProxy implements FangDongService {
    FangDongServiceImpl fangDongService = new FangDongServiceImpl();
    public void zufang() {
        System.out.println("发布租房信息");
        System.out.println("带租户看房");
        fangDongService.zufang();
    }
}

13.3、动态代理设计模式

  动态创建代理类的对象,为原始类的对象添加辅助功能。

13.3.1、JDK动态代理实现(基于接口)

    public void jdkProxy(){
        // 目标
        final FangDongService fangDongService = new FangDongServiceImpl();
        // 额外功能
        InvocationHandler invocationHandler = new InvocationHandler(){
            // 设置回调函数(额外功能代码)
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("发布租房信息");
                System.out.println("带租户看房");
                method.invoke(fangDongService,args);
                return null;
            }
        };
        // 创建动态代理类
        FangDongService proxy = (FangDongService) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), fangDongService.getClass().getInterfaces(), invocationHandler);
        proxy.zufang();
    }

13.3.2 、CGlib动态代理实现(基于继承)

    public void CGProxy(){
        final FangDongService fangDongService = new FangDongServiceImpl();
        // org.springframework.cglib.proxy.Enhancer
        // 1、创建字节码增强对象
        Enhancer enhancer = new Enhancer();
        // 2、设置父类(等价于实现原始类接口)
        enhancer.setSuperclass(fangDongService.getClass());

        //3、设置回调函数(额外功能代码)
        enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("发布租房信息");
                System.out.println("带租户看房");
                Object invoke = method.invoke(fangDongService, objects);
                return invoke;
            }
        });

        // 4、创建动态代理类
        FangDongService fangDong =  (FangDongService)enhancer.create();
        fangDong.zufang();
    }

十四、面向切面编程【重点】

14.1、概念

AOP (Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

14.2、AOP开发术语

连接点(Joinpoint):连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。

切入点(Pointcut):被Spring切入连接点。

通知、增强(Advice):可以为切入点添加额外功能,分为︰前置通知、后置通知、异常通知、环绕通知等。

目标对象(Target):代理的目标对象

引介(Introduction):一种特殊的增强,可在运行期为类动态添加Field和Method。

织入(Weaving):把通知应用到具体的类,进而创建新的代理类的过程。

代理(Proxy):被AOP织入通知后,产生的结果类。

切面(Aspect):由切点和通知组成,将横切逻辑织入切面所指定的连接点中。

14.3、作用

Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。

14.4、环境搭建

引入AOP相关依赖

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

spring-context.xml引入AOP命名空间

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://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">


</beans>

14.5、开发流程

1、定义原始类

public interface UserService {
    public void save();
}
public class UserServiceImpl implements UserService{
    public void save() {
        System.out.println("UserServiceImpl 执行啦啦");
    }
}

2、定义通知类(添加额外功能)

public class MyAdvice implements MethodBeforeAdvice {
    // 实现前置通知接口
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MethodBeforeAdvice 执行啦 ");
    }
}

3、定义bean标签

    <!-- 原始对象 -->
    <bean id="userServiceImpl" class="com.huiruan.service.impl.UserServiceImpl"/>

    <!-- 辅助对象 -->
    <bean id="myAdvice" class="com.huiruan.aop.MyAdvice"/>

4、定义切入点(PointCut)

5、形成切面(Aspect)

    <aop:config>
        <!-- 切点 -->
        <aop:pointcut id="myPointcut" expression="execution(* save())"/>
    </aop:config>
    <aop:config>
        <!-- 组装切面 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/>
    </aop:config>

14.6、AOP小结

通过AOP提供的编码流程,更便利的定制切面,更方便的定制了动态代理。

进而彻底解决了辅助功能冗余的问题;

业务类中职责单一性得到更好保障;

辅助功能也有很好的复用性。

14.7、通知类【可选】

定义通知类,达到通知效果

前置通知: MethodBeforeAdvice

后置通知: AfterAdvice

后置通知: AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值

异常通知: ThrowsAdvice

环绕通知: MethodInterceptor

1、前置通知

public class MyAdvice implements MethodBeforeAdvice {
    // 实现前置通知接口
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("MethodBeforeAdvice 执行啦 ");
    }
}

2、后置通知

public class UserAfter implements AfterReturningAdvice {
    //实现后置通知接口
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("AfterReturningAdvice 执行啦");
    }
}

3、异常通知

public class UserThrows implements ThrowsAdvice {
    // 实现异常通知
    public void afterThrowing(Exception ex){
        System.out.println("ThrowsAdvice 异常通知");
    }
}

4、环绕通知

public class UserMethod implements MethodInterceptor {
    // 环绕通知
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("MethodInterceptor 前执行");
        // 触发,执行核心功能
        Object inv = invocation.proceed();
        System.out.println("MethodInterceptor 后执行");
        return inv;
    }
}

14.8、通配切入点

根据表达式通配切入点

        <!--匹配参数-->
        <aop:pointcut id="myPointCut" expression= " execution(* *(com.huiruan.entity.User))"/>
        <!--匹配方法名(无参)-->
        <aop:pointcut id="myPointcut" expression="execution(* save())"/>
        <!--匹配方法名(任意参数)-->
        <aop:pointcut id="myPointcut" expression="execution(* save(..))" />
        <!--匹配返回值类型-->
        <aop:pointcut id="myPointcut" expression="execution(com.huiruan.entity.User *(..))"/>
        <!--匹配类名-->
        <aop:pointcut id="myPointCut" expression="execution(* com.huiruan.service.impl.UserServiceImpl.*(..))"/>
        <!--匹配包名-->
        <aop:pointcut id="myPointCut" expression="execution(* com.huiruan.service.impl.*.*(..))"/>
        <!--匹配包名、以及子包名-->
        <aop:pointcut id="myPointCut" expression=" execution(* com.huiruan.service..*.*(..))"/>

14.9、JDK和CGLIB选择

spring底层,包含了jdk代理和cglib代理两种动态代理生成机制

基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理

class DefaultAopProxyFactory {
    //该方法中明确定义了JDK代理和CGLib代理的选取规则
    //基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理
    public AopProxy createAopProxy(){...}
}

14.10、后处理器

spring中定义了很多后处理器;

每个bean在创建完成之前,都会有一个后处理过程,即再加工,对bean做出相关改变和调整;

spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。

14.10.1、后处理器定义

/**
 * 定义bean后处理器
 * 作用:在bean的创建之后,进行再加工
 */
public class MyBeanProcessor implements BeanPostProcessor {
    /**
     * 在bean的init方法之前执行
     * @param bean 原始的bean对象
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在bean的init方法之前执行"+bean.getClass());
        return bean;
    }

    /**
     * 在bean的init方法之后执行
     * @param bean postProcessBeforeInitialization 放回的bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在bean的init方法之后执行"+bean.getClass());
        // 此处的放回是getBean()最终的返回值
        return bean;
    }
}

14.10.2、配置后处理器

    <!-- 配置后处理器,将对工厂中所有的bean声明周期进行干预 -->
    <bean id="myBeanProcessor" class="com.huiruan.processor.MyBeanProcessor"/>

14.10.3、bean生命周期

构造 》注入属性满足依赖 》后处理器前置过程 》初始化 》后处理器后置过程 》返回 》销毁

十五、Spring + MyBatis【重点】

15.1、配置数据源

将数据源配置到项目中

 15.1.1、pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>

        <!-- spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>

        <!-- spring整合mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

    <properties>
        <!-- 设置默认编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>*.xml</include>
                    <!-- 新添加*/代表1级目录**/代表多级目录 -->
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

15.1.2、引入jdbc.properties配置文件

#jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/huiruan?useUnicode=true&amp;characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
jdbc.init=1
jdbc.minIdle=1
jdbc.maxActive=3

15.1.3、整合Spring配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://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:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 与PooledDataSource集成(二选一) -->
    <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- 与DruidDataSource集成(二选一) -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.init}"/>
        <property name="minIdle" value="${jdbc.minIdle}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
    </bean>

    <!-- 工厂bean:生成SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入连接池 -->
        <property name="dataSource" ref="druidDataSource"/>
        <!-- 注入dao-mapper文件信息,如果映射文件和dao接口 同包且同名,则此配置可省略 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:com/huiruan/dao/*.xml</value>
            </list>
        </property>
        <!-- 为 dao-mapper文件中的实体 定义缺省包路径
            如:<select id="queryAll" resultType="User"> 中User类可以不定义包
         -->
        <property name="typeAliasesPackage" value="com.huiruan.entity"/>
    </bean>
</beans>

15.2、整合MyBatis

将SqlSessionFactory、DAO、Service配置到项目中

15.2.1、导入依赖

        <!-- spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>

        <!-- spring整合mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>

15.2.2、配置sqlSessionFactory

    <!-- 工厂bean:生成SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入连接池 -->
        <property name="dataSource" ref="druidDataSource"/>
        <!-- 注入dao-mapper文件信息,如果映射文件和dao接口 同包且同名,则此配置可省略 -->
        <property name="mapperLocations">
            <list>
                <value>classpath:com/huiruan/dao/*.xml</value>
            </list>
        </property>
        <!-- 为 dao-mapper文件中的实体 定义缺省包路径
            如:<select id="queryAll" resultType="User"> 中User类可以不定义包
         -->
        <property name="typeAliasesPackage" value="com.huiruan.entity"/>
    </bean>

15.2.3、UserDao

public interface UserDao {
    public List<User> selectAll();
}

15.2.4、UserDaoMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http ://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace = 所需实现的接口全限定名-->
<mapper namespace="com.huiruan.dao.UserDao">
    <select id="selectAll" resultType="com.huiruan.entity.User">
        select * from t_users
    </select>

</mapper>

15.2.5、测试

    @Test
    public void selectAll(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-content.xml");
        SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) applicationContext.getBean("sqlSessionFactory");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

15.3、配置MapperScannerConfigurer

15.3.1、配置mapperScannerConfigurer

管理DAO实现类的创建,并创建DAO对象,存入工厂管理

  1、扫描所有DAO接口,去构建DAO实现

  2、将DAO实现存入工厂管理

  3、DAO实现对象在工厂中的id是:"首字母小写的-接口的类名"

    例如:UserDAO==>userDAO , OrderDAO==>orderDAO

    <!-- mapperScannerConfigurer -->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--
            dao接口所在的包 如果有多个包,可以用逗号或分号分隔
            <property name="basePackage" value="com.huiruan.dao,com.huiruan1.dao"/>
        -->
        <property name="basePackage" value="com.huiruan.dao"/>
        <!-- 如果工厂中只有一个sq1SessionFactory的bean,此配置可省略 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

15.3.2、测试

    @Test
    public void mapperScanner(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-content.xml");
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        List<User> users = userDao.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

15.4、配置Service

15.4.1、配置Service

    <bean id="userService" class="com.huiruan.service.impl.UserServiceImpl">
        <!-- 注意ref中的值是对应DA0接口的首字母小写的接口名 -->
        <property name="userDao" ref="userDao"></property>
    </bean>

15.4.2、创建UserServiceImpl

public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }

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

    public List<User> selectAll() {
        return userDao.selectAll();
    }
}

15.4.3、测试

    @Test
    public void userServiceImpl(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-content.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        List<User> users = userService.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

十六、事务【重点】

16.1、配置DataSourceTransactionManager

事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。

    <!-- 1.引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑 -->
    <bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

注意:DataSourceTransactionManagerSqlSessionFactoryBean要注入同一个DataSource的Bean,否则事务控制失败!!

16.2、配置事务通知

基于事务管理器,进一步定制,生成一个额外功能:Advice。

此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。

    <tx:advice id="txManager" transaction-manager="tx">
        <tx:attributes>
            <!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>-->
            <!-- 以User结尾的方法,切入此方法时,采用对应事务实行 -->
            <tx:method name="*User" />
            <!-- 以query开头的方法,切入此方法时,采用对应事务实行 -->
            <tx:method name="select*" propagation="SUPPORTS"/>
            <!-- 剩余所有方法 -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

16.3、事务属性

16.3.1、隔离级别

16.3.1.1、概念

isolation隔离级别

 

隔离级别由低到高为: read-uncommited < read-commited < repeatable-read < serialized-read

16.3.1.2、特性

安全性: 级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。

并发性: 级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

16.3.1.3、并发问题

事务并发时的安全问题

 

16.3.2、传播行为

propagation传播行为

当涉及到事务嵌套(Service调用Service)时,可以设置:

  SUPPORTS=不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)

  REQUIRED =不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。(默认值)(适合增删改)

16.3.3、读写性

readonly读写性

  true: 只读,可提高查询效率。(适合查询)

  false: 可读可写。(默认值)(适合增删改)

16.3.4、事务超时

timeout事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。

  100:自定义等待时间100(秒)。

  -1:由数据库指定等待时间,默认值。(建议)

16.3.5、事务回滚

rollback-for 回滚属性

  如果事务中抛出 RuntimeException,则自动回滚

  如果事务中抛出CheckException(非运行时异常Exception),不会自动回滚,而是默认提交事务

  处理方案:将CheckException转换成RuntimException上抛,或设置rollback-for="Exception"

    public Integer deleteUser(Integer id) {
        Integer integer = userDao.deleteUser(id);
//        Integer i = 10/0;
        if (1==1){
            try {
                throw new SQLException("test 事务");
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException("test 事务");
            }
        }
        return integer;
    }
<tx:method name="*User" rollback-for="Exception" />

16.4、编织

将事务管理的Advice切入需要事务的业务方法中

    <aop:config>
        <aop:pointcut id="pc" expression="execution(* com.huiruan.service.impl..*.*(..))"/>
        <aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
    </aop:config>

十七、注解开发

17.1、声明bean

用于替换自建类型组件的<bean...>标签;可以更快速的声明bean

  @Service 业务类专用

  @Repository dao实现类专用

  @Controller web层专用

  @Component 通用

  @Scope 用户控制bean的创建模式

//@Service说明 此类是一个业务类,需要将此类纳入工厂 等价替换掉<bean class="xxx.UserServiceImpl">
//@Service默认beanId == 首字母小写的类名"userServiceImpl"
//@Service("userService")自定义beanId为"userService"
@Service // 声明bean,且id="userServiceImpl"
@Scope("singleton") // 声明创建模式,默认为单例模式; @Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {
}

17.2、注入(DI)

用于完成bean中属性值的注入

  @Autowired基于类型自动注入

  @Resource基于名称自动注入

  @Qualifier("userDAO")限定要自动注入的bean的id,一般和@Autowired联用

  @Value注入简单类型数据(jdk8种+String)

@Service
public class UserServiceImpl implements UserService {

    @Autowired // 注入类型为UserDAO的bean
    @Qualifier("userDao") // 如果有多个类型为UserDA0的bean,可以用此注解从中挑选一个
    private UserDao userDao;
}
    @Resource("userDao3") //注入id="userDA03"的bean
    private UserDao userDao;

    @Resource //注入id="userDao"的bean
    private UserDao userDao;
public class User{
    @Value("xiaohe") // 注入String
    private String name;
}

17.3、事务控制

用于控制事务切入

  @Transactional

  工厂配置中的<tx:advice.....和<aop:config….可以省略!!

// 类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED,readOnly = false,rollbackFor = Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {


    // 该方法自己的事务控制,仅对此方法有效
    @Transactional(propagation = Propagation.SUPPORTS)
    public List<User> selectAll() {
        return userDao.selectAll();
    }
}

17.4、注解所需配置

    <!-- 告知spring,哪些包中有被注解的类、方法、属性 -->
    <!-- <context:component-scan base-package="com.huiruan.a,com.huiruan.b"/> -->
    <context:component-scan base-package="com.huiruan"/>
    <!-- 告知spring,@Transactional在定制事务时,基于 txManager=DataSourceTransactionManager -->
    <tx:annotation-driven transaction-manager="tx"/>

17.5、AOP开发

17.5.1、注解使用

// 声明此类是一个切面类:会包含切入点(pointcut)和通知( advice)
// 声明组件,进入工厂
@Aspect
@Component
public class MyAspect {

    // 定义切入点
    @Pointcut("execution(* com.huiruan.service.impl.UserServiceImpl.*(..))")
    public void pc(){}

    // 前置通知
    @Before("pc()")
    public void myBefore(JoinPoint a){
        System.out.println("target"+a.getTarget());
        System.out.println("args"+a.getArgs());
        System.out.println("name"+a.getSignature().getName());
        System.out.println("before===");
    }

    // 后置通知
    @AfterReturning(value = "pc()",returning = "ret")
    public void myAfterReturning(JoinPoint a,Object ret){
        System.out.println("after==="+ret);
    }

    // 环绕通知
    @Around("pc()")
    public Object myAround(ProceedingJoinPoint p) throws Throwable {
        System.out.println("Around1");
        Object proceed = p.proceed();
        System.out.println("Around2");
        return proceed;
    }

    // 异常通知
    @AfterThrowing(value = "pc()",throwing = "ex")
    public void myAfterThrowing(JoinPoint jp,Exception ex){
        System.out.println("AfterThrowing");
        System.out.println("==="+ex.getMessage());
    }
}

17.5.2、配置

    <!-- 添加如下配置,启用aop注解 -->
    <aop:aspectj-autoproxy/>
    <!-- 添加如下配置,启用aop注解 -->
    <!-- but was actually of type 'com.sun.proxy.$Proxy33' 加上  proxy-target-class="true"-->
    <aop:aspectj-autoproxy proxy-target-class="true" />

十八、集成 JUnit

18.1、导入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

18.2、编码

  可以免去工厂的创建过程;

  可以直接将要测试的组件注入到测试类。

// 由SpringJUnit4ClassRunner启动测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-content.xml") // spring的配置文件位置
public class SpringRun { // 当前测试类也会被纳入工厂中,所以其中属性可以注入

    @Autowired
    @Qualifier(value = "userServiceImpl") // 注入要测试的组件
    private UserServiceImpl userServiceImpl;

    @Autowired
    private UserService userService;

    @Test
    public void test(){
        // 测试使用userDAO
        List<User> users = userService.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

    // 或者使用这个
    @Test
    public void testUser(){
        List<User> users = userServiceImpl.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

}

 

 

 

 

 

 

posted @ 2021-08-29 10:09  菜鸟的道路  阅读(91)  评论(0)    收藏  举报