Spring

1. Spring

组成--七大模块

image-20201201113240895

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.11.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.11.RELEASE</version>
</dependency>

扩展

  • Spring Boot
    • 一个快速开发的脚手架
    • 基于SpringBoot可以快速开发单个微服务
    • 约定大于配置
  • Spring Cloud
    • SpringCloud是基于SpringBoot实现的

2. IOC理论

控制反转

程序员不用再管理对象的创建了,系统的耦合性大大降低。可以更加专注在业务的实现。

IOC : 控制反转

DI : 依赖注入,控制反转的一种实现方式

  • 反转:程序本身不创建对象,编程被动的接收对象
  • 依赖注入 : 利用set方法进行注入
  • 对象是由Spring创建
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="Hello" class="com.Rickduck.pojo.Hello">
        <property name="str"    value="Spring" />
    </bean>
</beans>

IOC创建对象的方式

  • 无参构造方式创建

    • <bean id="user" class="com.Rickduck.pojo.User" name="u1,u2">
      </bean>
  • 有参构造方式

      1. 下标赋值

            <!-- 第一种,下标赋值-->
            <bean id="user" class="com.Rickduck.pojo.User">
                <constructor-arg index="0"  value="index-ref" />
            </bean>
      2. 通过类型创建

        <!-- 第二种,通过类型创建,不建议使用-->
        <bean id="user" class="com.Rickduck.pojo.User">
            <constructor-arg type="java.lang.String"  value="type-ref" />
        </bean>
      3. 直接通过参数名

        <bean id="user" class="com.Rickduck.pojo.User">
            <property name="user" value="Spring">
        </bean>

在加载配置文件的时候,容器中管理的对象就已经初始化了!!!

3. Spring配置

3.1 别名

<!-- 配置别名 -->
<alias name="name" alias="newName"></alias>

3.2 Bean的配置

<!--id:唯一标识 class:bean对象对应的全限定名 name:别名(可以取多个别名)-->
<bean id="user" class="com.Rickduck.pojo.User" name="u1,u2">
</bean>

3.3 import

一般用于团队开发,可以将多个配置文件导入合并为一个。

   <import resource="beans.xml" />

4. 依赖注入

4.1 构造器注入

image-20201201130957049

4.2 Set方式注入

  • 依赖注入:Set注入
    • 依赖:bean对象的创建依赖于容器!
    • 注入:bean对象钟的所有属性都由容器来注入!

8种类型注入:

<bean id="student" class="com.Rickduck.pojo.Student">

    <!-- 1.普通值注入-->
    <property name="name"  value="Rickduck" />
    <!-- 2.Bean注入-->
    <property name="address"  ref="address" />
    <!-- 3.数组注入-->
    <property name="books">
        <array>
            <value>水浒传</value>
            <value>红楼梦</value>
            <value>三国演义</value>
            <value>西游记</value>
        </array>
    </property>
    <!-- 4.List注入-->
    <property name="hobbys">
        <list>
            <value>歌曲</value>
            <value>代码</value>
            <value>篮球</value>
        </list>
    </property>
    <!-- 5.Map注入-->
    <property name="card">
        <map>
            <entry key="ID" value="123456" />
            <entry key="key" value="value" />
        </map>
    </property>
    <!-- 6.Set注入-->
    <property name="games">
        <set>
            <value>LOL</value>
            <value>QQ飞车</value>
        </set>
    </property>
    <!-- 7.空值注入-->
    <property name="wife">
        <null />
    </property>
    <!-- 8.Properties注入-->
    <property name="info">
        <props>
            <prop key="学号">123456</prop>
            <prop key="姓名">2333</prop>
        </props>
    </property>


</bean>

4.3 扩展方式注入

p命名空间方式注入

c命名空间方式注入

image-20201210143430701

注意点 :p命名、c命名空间不能直接使用,需要导入xml约束

4.4 Bean的作用域

image-20201210143656649

  1. 单例模式(默认)

    • 每次从容器中get取得时候都是同一个对象,只有一个实例对象
  2. 原型模式

    • 每次从容器中get的时候都会产生一个对象
  3. 其余得request、session、application 这些只在web中使用

5. Bean的自动装配

  • 自动装配是Spring满足bean依赖得一种方式!
  • Spring会在上下文中自动寻找,并自动给bean装配属性

三种Spring装配方式

  1. xml中显示配置
  2. java中显示配置
  3. 隐式的自动装配bean(重要)
    <bean id="cat" class="com.Rickduck.pojo.Cat"></bean>
    <bean id="dog" class="com.Rickduck.pojo.Dog"></bean>   
    <!--
    byName:自动在容器上下文和自己对象set方法值对应的beanid
    byType:自动在容器上下文和自己对象属性类型相同的bean(保证全局唯一)

    -->
    <bean id="person" class="com.Rickduck.pojo.Person" autowire="byName">
        <property name="name" value="Rickduck"></property>
    </bean>
  • byName:保证beanid唯一,并且和需要注入属性set方法的值一致
  • byType:保证bean的class唯一

使用注解实现自动装配

jdk1.5支持注解

spring2.5支持注解

步骤:

  1. 导入约束:context约束
  2. 配置注解的支持:context:annotation-config/
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解支持 -->
    <context:annotation-config/>

</beans>
  • @Autowired

    • 直接在属性上使用
    • 可不使用set方法
    • 默认byType
  • @Qualifier

    • 可指定属性
    • value = "name"
  • @Resouce

    • 默认byName

6.使用注解开发

1.bean

  1. 开启注解

  2. 扫描包

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解支持 -->
    <context:annotation-config/>
    <!-- 开启包扫描 -->
    <context:component-scan base-package="com.Rickduck"/>
</beans>

2.属性注入

  • @Component
    • 组件,用在pojo类上,
@Component
public class User{

    public String name;

    //相当于applicationContext.xml中配置Bean时的属性注入
    @value("Rickduck")
    public void setName(String name){
        this.name = name;
    }

}

3.衍生的注解

@Component有几个衍生注解,在web开发中会按照mvc三层架构分层!

  • dao : @Repository
  • service : @Service
  • controller : @Controller

4. 自动装配

- @Autowired : 自动装配通过类型。名字
    如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value="xxx")

- @Nullable : 说明该字段可以为null

- @Resource : 自动装配通过名字。类型

5.作用域

@Component
@Scope("prototype")
public class User{

    public String name;

    //相当于applicationContext.xml中配置Bean时的属性注入
    @value("Rickduck")
    public void setName(String name){
        this.name = name;
    }

}

6. 小结

xml与注解

  • xml更加万能,适用于任何场合!
  • 注解不是自己的类不能用,维护相对复杂!

7.使用Java方式配置Spring

JavaConfig是Spring一个子项目,在Spring4之后成为一个核心功能

实体类

@Component
public class User{
    private String name;

    public String getName(){
        return name;
    }

    @value("Rickduck")    //属性值注入
    public void setName(String name){
        this.name = name;
    }
}

配置类:

@Configuration
@ComponentScan("com.Rickduck.pojo")
@Import(Rickduck2.class)
public class RickduckConfig{

    // 注册一个bean,相当于xml中的bean标签
    // 方法名 : bean的id
    // 方法返回值 :bean的class属性
    @Bean
    public User getUser(){
        return new User();
    }
}

测试类

public class MyTest{
    public static void main(String[] args){
        //使用了配置类方式,只能通过AnnotationConfig上下文来获取容器
        ApplicationContext context = new AnnotationConfigApplicationContext(RickduckConfig.class);
        User getUser = (User)context.getBean("getBean");
        System.out.println(getUser.getName());
    }
}

纯Java的配置方式,在SpringBoot中随处可见

8. 代理模式

SpringAOP的底层是代理模式

代理模式的分类:

  • 静态代理
  • 动态代理

8.1 静态代理

角色分析

  • 抽象角色 :一般会使用接口或者抽象类来解决
  • 真实角色 : 被代理的角色
  • 代理角色 :代理真实角色(代理真实角色后一般会做一些附属操作)
  • 客户:访问代理对象的人

步骤:

  1. 接口

    //租房
    public interface Rent{
        public void rent();
    }
    
  2. 真实角色

    //房东
    public class Host implements Rent{
        public void rent(){
            System.out.println("房东租房!");
        }
    }
    
  3. 代理角色

    public class Proxy implements Rent{
        private Host host;
    
        public Proxy(){}
    
        public Proxy(Host host){
            this.host = host;
        }
    
        public void rent(){
            seeHouse();
            host.rent();
            contract();
            fare();
        }
    
        //看房
        public void seeHouse(){
            System.out.println("中介带看房!");
        }
        //合同
        public void contract(){
            System.out.println("签租赁合同!");
        }
        //收取中介费
        public void fare(){
            System.out.println("收中介费!");
        }
    }
  4. 客户端访问代理角色

    public class Client{
        public static void main(String[] args){
            //房东要租房子
            Host host = new Host();
            //代理,中介帮房东出租房子;中间会加上一些附属操作
            Proxy proxy = new Proxy(host);
            //客户不需要面对房东,著需要面对中介
            proxy.rent();
        }
    }

代理模式的好处:

  • 可以使真实角色的操作更纯粹,不用去关注一些公共的业务
  • 公共也就交给了代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,开发效率变低

8.2 动态代理

底层是反射

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的:基于接口、基于类的动态代理
  • 动态代理分为两大类
    • 基于接口 -- JDK动态代理
    • 基于类 -- cglib
    • java字节码实现:javasist

两个类 : Proxy代理、InvocationHandler:调用处理程序

Proxy : 提供创建动态代理类和实例的静态方法

InvocationHandler:调用处理程序并返回结果

InvocationHandler

public class ProxyInvocationHandler implements InvocationHandler{

    //被代理的接口
    private Object target;

    public void setTarget(Object target){
        this.target = target;
    }

    //生成得到代理类
    //类加载器,代理接口,处理程序
    public Object getProxy(){
        return Proxy.newProxtInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces,this);
    }

    //处理代理实例,并返回结果
    public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
        log(method.getName());
        Object result = method.invoke(target,args);
        return result;
    }

    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

测试类

public class Clent{
    public static void main(String[] args){
        //真实角色
        UserServiceImpl userService =     new UserServiceImpl();
        //代理对象通过处理程序动态生成
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置需要代理的对象
        pih.setTarget(userService);
        UserService proxy = (UserService)pih.getProxy();
        proxy.query();
    }
}

9. AOP

image-20201216140702053

SpringAOP中,通过Adivice定义横切逻辑,Spring中支持五种类型的Advice:

image-20201216140931576

9.1 Spring实现AOP

依赖导入:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>

方式一:Spring的API接口

AOP配置:

需要增加aop的约束!

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

    <!--  注册bean  -->
    <bean id="userService" class="com.Rickduck.service.UserServiceImpl" />
    <bean id="log" class="com.Rickduck.log.Log" />
    <bean id="afterlog" class="com.Rickduck.log.AfterLog" />

    <!--  配置aop:需要导入aop的约束 -->
    <aop:config>
        <!-- 切入点: expression:表达式,execution(执行的位置! * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.Rickduck.service.UserServiceImpl.*(..))"/>
        <!-- 执行环绕增加 -->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut" />
        <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut" />

    </aop:config>

</beans>

前置通知

public class Log implements MethodBeforeAdvice {
    /*
     * method : 要执行的目标对象的方法
     * args : 参数
     * target : 目标对象
     */
    @Override
    public void before(Method method,Object[] args,Object target) throws Throwable{
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }

}

后置通知

public class AfterLog implements AfterReturningAdvice{
    //returnValue : 返回值
    @Override
    public void afterReturning(Object returnValue,Method method,Object[] args,Object target) throws Throwable{
         System.out.println("执行了"+target.getClass().getName()+"方法,返回结果为:"+returnValue);
    }
}

测试类

public class MyTest {

    public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        UserService userService = context.getBean("userService",UserService.class);
        userService.add();
    }

}

方式二 自定义切入点类

自定义切面类

public class DiyPointCut {
    public void before(){
        System.out.println("方法执行前");
    }
    public void after(){
        System.out.println("方法执行后");
    }
}

aop配置

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

    <!--  注册bean  -->
    <bean id="userService" class="com.Rickduck.service.UserServiceImpl" />
    <bean id="diy" class="com.Rickduck.diy.DiyPointCut" />

    <aop:config>
        <!-- 自定义切面 -->
        <aop:aspect ref="diy">
            <!-- 切入点 -->
            <aop:pointcut id="point" expression="execution(* com.Rickduck.service.UserServiceImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="point" />
            <aop:after method="after" pointcut-ref="point" />
        </aop:aspect>
    </aop:config>


</beans>

方式三:注解实现AOP

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AnnotationPointCut {

    @Before("execution(* com.Rickduck.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("方法执行前");
    }

    @After("execution(* com.Rickduck.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("方法执行后");
    }

    //在环绕增强中
    @Around("execution(* com.Rickduck.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        //执行方法
        Object proceed = jp.proceed();

        System.out.println("环绕后");
    }
}
<!-- 注册切面类bean -->    
<bean id="annotationPointCut" class="com.Rickduck.diy.AnnotationPointCut" />
<!-- 开启注解支持 默认JDK实现-->    
<aop:aspectj-autoproxy />

10. 整合Mybatis

10.1 Mybatis搭建:

步骤:

  1. 导入相关jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关的
    • aop织入
    • mybatis-spring
  2. 编写配置文件

  3. 测试

10.2 Mybatis-spring

  1. 编写数据源配置

  2. sqlSessionFactory

  3. sqlSessionTemplate

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- DataSource : 使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost:3306/mybatics?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8" />
            <property name="username" value="root"/>
            <property name="password" value="888888"/>
        </bean>
    
        <!-- sqlSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <!-- 绑定Mybatis配置 -->
            <property name="configLocation" value="classpath:mybatis-config.xml" />
            <property name="mapperLocations" value="classpath:com/Rickduck/mapper/*.xml" />
        </bean>
    
        <!-- SqlSessionTemplate:就是我们使用的sqlSession-->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <!-- 只能使用构造器注入sqlSessionFactory,因为没有set方法-->
            <constructor-arg index="0" ref="sqlSessionFactory" />
        </bean>
    
        <bean id="userMapper" class="com.Rickduck.mapper.UserMapperImpl">
            <property name="sqlSession" ref="sqlSession" />
        </bean>
    
    </beans>
  4. 需要给接口加实现类

    image-20201216164746435

  5. 测试

public class MyTest {

    //mybatis
    @Test
    public void test() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resource);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sessionFactory.openSession(true);
        UserMapper user = sqlSession.getMapper(UserMapper.class);

        List<User> userList = user.selectUser();
        for (User user1 : userList) {
            System.out.println(user1);
        }

    }

    //Spring整合mybatis后
    @Test
    public void test2() throws IOException{
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapper userMapper = context.getBean("userMapper",UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }

}

实现二:

通过实现类继承SqlSessionDaoSupport

import com.Rickduck.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {

    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}

11. 声明式事务

11.1 事务

  • 把一组业务当成一个业务做:要么都成功要么都失败
  • 设计数据一致性
  • 确保完整性和一致性

事务ACID原则:

  • 原子性
    • 要么都成功要么都失败
  • 一致性
    • 事务执行前后数据库处于一致的状态
  • 隔离性
    • 并发事务是相互隔离的,防止数据损坏
  • 持久性
    • 事务一旦提交,无论系统发生什么问题结构都不在被影响,被持久化到数据库中

11.2 spring中的事务管理

  • 声明式事务:AOP
  • 编程式事务:需要在代码中对事务进行管理

事务的传播级别:

image-20201217125749997

在applicationContext中进行事务的配置

    <!-- 配置声明式事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 结合AOP实现事务的织入 -->
    <!-- 配置事务通知: -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 给哪些方法配置事务 -->
        <!-- 配置事物的传播特性: new propagation-->
        <tx:attributes>
            <tx:method name="add" />
            <tx:method name="delete" />
            <tx:method name="update" />
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <!-- 配置事务切入 -->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.Rickduck.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
    </aop:config>

为什么需要事务:

  • 如果不配置事务,可能存在数据提交不一致的情况
  • 如果不在Spring中配置声明式事务,就需要在代码中手动配置事务
  • 事务十分重要!!!