天天看点

Spring教程(由浅入深)

1、Spring教程(由浅入深)

1.1、简介

  • 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
  • 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
  • 很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
  • Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
  • 官网 : http://spring.io/
  • 官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
  • GitHub :https://github.com/spring-projects

1.2、优点

  • Spring是一个开源免费的框架 , 容器 .
  • Spring是一个轻量级的框架 , 非侵入式的 .
  • 控制反转 IoC , 面向切面 Aop
  • 对事物的支持 , 对框架的支持

一句话概括:

Spring是一个轻量级非侵入式的控制反转(IoC)和面向切面(AOP)的容器(框架)。

1.3、组成

Spring教程(由浅入深)

1.4、扩展

Spring教程(由浅入深)
  • Spring Boot
    • Spring 的一套快速配置脚手架
    • 基于Spring Boot 可以快速开发单个微服务
    • 约定大于配置
  • Spring Cloud
    • Spring Cloud是基于Spring Boot实现的

Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架。

Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。

SpringBoot在SpringClound中起到了承上启下的作用,学习SpringCloud必须要学习SpringBoot,学习SpringBoot前必须掌握Spring和Spring MVC。

2、IOC理论编导

2.1、IOC基础

1、先写一个UserDao接口

public interface UserDao {
   public void getUser();
}
           

2、再去写Dao的实现类

public class UserDaoImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("获取用户数据");
  }
}
           

3、然后去写UserService的接口

public interface UserService {
   public void getUser();
}
           

4、最后写Service的实现类

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoImpl();

   @Override
   public void getUser() {
       userDao.getUser();
  }
}
           

5、测试一下

@Test
public void test(){
   UserService service = new UserServiceImpl();
   service.getUser();
}
           

这是原来的方式 .

把Userdao的实现类增加一个 .

public class UserDaoMySqlImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("MySql获取用户数据");
  }
}
           

紧接着要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoMySqlImpl();

   @Override
   public void getUser() {
       userDao.getUser();
  }
}
           

在假设, 我们再增加一个Userdao的实现类 .

public class UserDaoOracleImpl implements UserDao {
   @Override
   public void getUser() {
       System.out.println("Oracle获取用户数据");
  }
}
           

那么要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设需求非常大 , 这种方式就根本不适用了, 每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .

解决方式

可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set .

public class UserServiceImpl implements UserService {
   private UserDao userDao;
// 利用set实现
   public void setUserDao(UserDao userDao) {
       this.userDao = userDao;
  }

   @Override
   public void getUser() {
       userDao.getUser();
  }
}
           

现在去我们的测试类里 , 进行测试 ;

@Test
public void test(){
   UserServiceImpl service = new UserServiceImpl();
   service.setUserDao( new UserDaoMySqlImpl() );
   service.getUser();
   //那我们现在又想用Oracle去实现呢
   service.setUserDao( new UserDaoOracleImpl() );
   service.getUser();
}
           

以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .

这种思想 , 从本质上解决了问题 , 程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这是IOC的原型

2.2、IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

3、HelloSpring

3.1、步骤

  • 导入Jar包 - spring-webmvc
  • 编写代码
    • 编写一个实体类
  • 编写spring配置文件 , 命名为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">

   <!--bean就是java对象 , 由Spring创建和管理
	   	把数据添加到Spring容器
	-->
   <bean id="hello" class="dao.Hello">
       <!-- property相当于给属性设值-->
       <property name="name" value="Spring"/>
   </bean>

</beans>
           
  • 测试
@Test
public void test(){
   //解析beans.xml文件 , 生成管理相应的Bean对象
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   //getBean : 参数即为spring配置文件中bean的id(也可使用类名.class) .
   Hello hello = (Hello) context.getBean("hello");
   hello.show();
}
           

修改案例一

新增一个Spring配置文件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">

   <bean id="MysqlImpl" class="dao.impl.UserDaoMySqlImpl"/>
   <bean id="OracleImpl" class="dao.impl.UserDaoOracleImpl"/>
   <bean id="ServiceImpl" class="service.impl.UserServiceImpl">
       <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
       <!--引用另外一个bean , 不是用value 而是用 ref-->
       <property name="userDao" ref="OracleImpl"/>
   </bean>

</beans>
           

3.2、思考

  • Hello 对象是谁创建的 ?
    • hello 对象是由Spring创建的
  • Hello 对象的属性是怎么设置的 ?
    • hello 对象的属性是由Spring容器设置的这个过程就叫控制反转
  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

依赖注入 : 就是利用set方法来进行注入的.

IOC是一种编程思想,由主动的编程变成被动的接收

要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配

4、IOC创建对象的方式

  1. 默认使用无参构造器创建
  2. 使用有参构造器创建
    1. 下标赋值
      <bean id="user" class="domain.User">
          <!-- 使用构造器参数下标赋值-->
          <constructor-arg index="0" value="阿凡达"/>
      </bean>
                 
    2. 类型赋值
      <bean id="user" class="domain.User">
          <!-- 使用属性类型赋值,不推荐-->
          <constructor-arg type="java.lang.String" value="阿凡达1"/>
      </bean>
                 
    3. 参数名赋值
      <bean id="user" class="domain.User">
      	<!--	可用value/ref
              因为构造器参数内只有一个String name,所以可以直接用value-->
          <constructor-arg name="name" value="阿凡达2"/>
      </bean>
                 

new ClassPathXmlApplicationContext时容器内所有\的对象都会被初始化创建!

5、Spring配置

alias

设置别名 , 为bean设置别名 , 可以设置多个别名

<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
           

Bean的配置

<!--bean就是java对象,由Spring创建和管理-->

<!--
   id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
   如果配置id,又配置了name,那么name是别名
   name可以设置多个别名,可以用逗号,分号,空格隔开
   如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;

class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
   <property name="name" value="Spring"/>
</bean>
           

import

团队的合作通过import来实现

<import resource="{path}/beans.xml"/>
           

如果有重名的bean标签,则后导入会覆盖前导入

6、依赖注入

概念:

  • 依赖注入(Dependency Injection,DI)。
  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .

6.1、构造器注入

  • name - value(根据参数值)
  • index(根据参数下标)
  • type(根据参数类型)

6.2、Set 注入 (重点)

要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .

测试pojo类 :

Address.java

public class Address {
     private String address;
     public String getAddress() {
         return address;
    }
     public void setAddress(String address) {
         this.address = address;
    }
 }
           

Student.java

public class Student {
 
     private String name;
     private Address address;
     private String[] books;
     private List<String> hobbys;
     private Map<String,String> card;
     private Set<String> games;
     private String wife;
     private Properties info;
    /**
    *一些set get方法
    */
     public void show(){
        System.out.println("name="+ name
                + ",address="+ address.getAddress()
                + ",books="
       );
        for (String book:books){
            System.out.print("<<"+book+">>\t");
       }
}
           
  • 常量注入
    <bean id="student" class="domain.Student">
    	<property name="name" value="小明"/>
    </bean>
               
@Test
public void test01(){
    ApplicationContext context = new 			ClassPathXmlApplicationContext("applicationContext.xml");
    //使用类名.class时无需强转
	Student student = context.getBean(Student.class);
  //  Student student = (Student) context.getBean("student");

    System.out.println(student.getName());

}
           
  • Bean注入

注意点:这里的值是一个引用,ref

<bean id="addr" class="domain.Address">
    <property name="address" value="常青树"/>
</bean>

<bean id="student" class="domain.Student">
    <property name="name" value="小明"/>
    <property name="address" ref="addr"/>
</bean>
           
  • 数组注入
<bean id="address" class="domain.Address">
    <property name="address" value="常青树"/>
</bean>
<bean id="student" class="domain.Student">
    <!-- 普通注入-->
    <property name="name" value="小明"/>
    <!-- Bean注入-->
    <property name="address" ref="address"/>
    <!-- 数组注入-->
    <property name="books">
        <array>
            <value>红楼梦</value>
            <value>水浒传</value>
            <value>三国演义</value>
        </array>
    </property>
</bean>
           
  • List注入
<!-- List注入-->
<property name="hobbys">
    <list>
        <value>打球</value>
        <value>1</value>
        <value>playGames</value>
    </list>
</property>
           
  • Map注入
<!-- Map注入-->
<property name="card">
    <map>
        <entry key="中国邮政" value="999998888" />
        <entry key="建设银行" value="856664565" />
    </map>
</property>
           
  • set注入
<!-- Properties注入-->
<property name="info">
    <props>
        <prop key="学号">20160499010</prop>
        <prop key="性别">男</prop>
        <prop key="学籍">带专</prop>
    </props>
</property>
           
  • null注入
<!-- null注入-->
<property name="wife">
    <null/>
</property>
           
  • properties注入
<!-- Properties注入-->
<property name="info">
    <props>
        <prop key="driver">com.jdbc.cj.......</prop>
        <prop key="username">root</prop>
        <prop key="password">123456</prop>
    </props>
</property>
           

6.3、拓展方式注入

p命名和c命名注入

User.java :【注意:这里没有有参构造器】

public class User {
     private String name;
     private int age;
     //set get方法
 }
           
  • p命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p"
 
 <!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="domain.User" p:name="奥利给" p:age="20" /> 
           
  • c 命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
 <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="domain.User" c:name="狂神" c:age="18"/>
           

发现问题:爆红了,刚才我们没有写有参构造.

解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入.

6.4、Bean的作用域

singleton (默认)为每个 Spring IoC 容器的单个 object 实例定义单个 bean 定义。
prototype 为任意数量的 object 实例定义单个 bean 定义。
request 将单个 bean 定义范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例,该实例是在单个 bean 定义的后面创建的。仅在 web-aware Spring

ApplicationContext

的 context 中有效。
session 将单个 bean 定义范围限定为 HTTP

Session

的生命周期。仅在 web-aware Spring

ApplicationContext

application 将单个 bean 定义范围限定为

ServletContext

ApplicationContext

websocket

WebSocket

ApplicationContext

  1. 单利模式(Spring默认机制)
    <bean id="user" class="domain.User" p:name="奥利给" p:age="20" scope="singleton"/>
               
  2. 原型模式:每次从容器中get的时候都会产生一个新对象
  3. 其余的request、session、application只能在web开发中使用到

7、Bean的自动装配

  • 自动装配是使用spring满足bean依赖的一种方法
  • spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  1. 在xml中显式配置;
  2. 在java中显式配置;
  3. 隐式的bean发现机制和自动装配。(重点)

Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

推荐不使用自动装配xml配置 , 而使用注解 .

7.1、配置文件自动装配

byName

<bean id="dog" class="domain.Dog"/>
<bean id="cat" class="domain.Cat"/> 
<!-- byName 在容器上下文查找和自己对象set方法后面对应的bean的id-->
<bean id="person" class="domain.Person" autowire="byName" />
           

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。

byType

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

NoUniqueBeanDefinitionException
           

错误写法

<bean id="dog" class="domain.Dog"/>
    <bean id="cat" class="domain.Cat"/>
	<bean id="ca2t" class="domain.Cat"/>  此时会报错,因为类型在容器中不唯一
    <bean id="person" class="domain.Person" autowire="byType" />
           

正确写法

<bean id="dog" class="domain.Dog"/>
    <bean id="cat" class="domain.Cat"/>  <!--id可为任意值,也可去掉--> 
	<!-- byType 在容器上下文查找和自己对象类型相同(类里有)的bean-->
    <bean id="person" class="domain.Person" autowire="byType" />
           

因为是按类型装配,所以id值并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

7.2、使用注解自动装配

jdk1.5开始支持注解,spring2.5开始全面支持注解。

步骤:

1、在spring配置文件中引入context文件头

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

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

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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">
        <!-- 开启注解配置-->
        <context:annotation-config />
</beans>
           

@Autowired

  • @Autowired是按类型自动转配的,不支持id匹配。
  • 需要导入 spring-aop的包!

使用Autowired可以去掉set方法,前提是这个自动装配属性在IOC容器中存在且符合命名ByName

测试:

1、将User类中的set方法去掉,使用@Autowired注解

public class User {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   private String str;
   
   public Cat getCat() {
       return cat;
  }
   public Dog getDog() {
       return dog;
  }
   public String getStr() {
       return str;
  }
}
           

2、此时配置文件内容

<context:annotation-config/>

<bean id="dog" class="domain.Dog"/>
<bean id="cat" class="domain.Cat"/>
<bean id="user" class="domain.User"/>
           

3、测试,成功输出结果!

Autowired存在一个required属性,默认为true。

如果定义了required属性为false,则说明这个对象可以为null

@Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier不能单独使用。

配置文件修改内容,保证类型存在对象。且名字不为类的默认名字

<!-- 开启注解配置-->
    <context:annotation-config />
    <bean id="dog123" class="domain.Dog"/>
    <bean id="dog1" class="domain.Dog"/>
    <bean id="cat321" class="domain.Cat"/>
    <bean id="cat1" class="domain.Cat"/>
    <bean id="person" class="domain.Person"/>
           

没有加Qualifier测试,直接报错

在属性上添加Qualifier注解(类似在容器中查找对应id)

@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
           

@Resource

Resource是JDK自带的注解,无需导Spring包

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。

byName -> byType

@Resource(name = "cat2")
           

小结

@Autowired与@Resource异同:

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

8、使用注解开发

在spring4之后,想要使用注解形式,必须得要引入aop的包

8.1、Bean的实现

1、配置扫描哪些包下的注解

<!--指定注解扫描包my-->
<context:component-scan base-package="my"/>
           

2、在指定包下编写类,增加注解

//@Component 
//@Component("user")等价于 xml里<bean id="user" class="my.domain.User"/>
@Component   //默认是类名首字母小写
public class User {
    private String name = "阿凡达";
           

3、测试

8.2、属性注入

使用注解注入属性

1、可以不用提供set方法,直接在直接名上添加@value(“值”)

@Component
public class User {
   // 相当于配置文件中 <property name="name" value="阿凡达"/>
   @Value("阿凡达")
   public String name;
}
           

2、如果提供了set方法,在set方法上添加@value(“值”)

@Component("user")
public class User {
   public String name;
   @Value("奥利给")
   public void setName(String name) {
       this.name = name;
  }
}
           

8.3、衍生注解

为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

  • @Controller:web层
  • @Service:service层
  • @Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了

8.4、自动装配注解

8.5、作用域

@scope

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 :推荐最佳实践

  • xml管理Bean
  • 注解完成属性注入
  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

9、基于java类进行配置

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

1、新建一个config配置包,编写一个MyConfig配置类

@Configuration  //代表这是一个配置类
@ComponentScan("domain")   //扫描doamin下的包/类
public class MyConfig {

   @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
   public User getUser(){
       return new User();
  }

}
           

2、编写一个实体类User

@Component
public class User {
    @Value("奥利给")
    private String name;
}
           
public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User getUser = (User) context.getBean("getUser");
        User user = (User) context.getBean("user");
        System.out.println(getUser);
        System.out.println(user);
}

Output:
	User{name='奥利给'}
	User{name='奥利给'}
           

这里有两个bean

  • 一个是注解@Bean获得的id为方法名getUser.
  • 另外一个是@ComponentScan(“domain”)+@Component扫描包获得的id为类名小写user

@Configuration相当于把这个类变成了配置文件,在里面用@Bean相当于写配置文件里的标签。加上@ComponentScan相当于你在配置文件中开启了注解扫描,@Component相当于把这个类添加到容器里。

另一个User类上的注解因为你现在开启了扫描才能被扫描到加入容器中现在等于容器中有2个User类

导入其他配置

@Configuration  //代表这是一个配置类
public class MyConfig2 {
}
           
@Configuration
@Import(MyConfig2.class)  //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {
   @Bean
   public Dog dog(){
       return new Dog();
  }

}
           

10、代理模式

  • 静态代理
  • 动态代理

10.1、静态代理

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
  • 客户 : 使用代理角色来进行一些操作 .

代码实现

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}
           

Host . java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}
           

Proxy . java 即代理角色

//代理角色:中介
public class Proxy implements Rent {

   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }

   //租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }
}
           

Client . java 即客户

//客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       //房东要租房
       Host host = new Host();
       //中介帮助房东
       Proxy proxy = new Proxy(host);

       //你去找中介!
       proxy.rent();
  }
}
           

分析:在这个过程中,直接接触的就是中介,就如同现实生活中的样子,看不到房东,但是依旧租到了房东的房子通过代理,这就是所谓的代理模式。

静态代理的好处:

  • 可以使得真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

10.2、动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理

JDK的动态代理需要了解两个类

核心 : InvocationHandler 和 Proxy

【InvocationHandler:调用处理程序】

【Proxy : 代理】

public interface Rent {
    void rent();
}
           

Master . java 即真实角色

//真实角色:房东
public class Master implements Rent {
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
           

ProxyInvocationHandler. java 即代理角色

//动态代理
public class ProxyInvocationHandler implements InvocationHandler {
    //定义被代理的对象
    private Object target;

    //传入真实对象设置被代理的对象
    public void setTarget(Object target) {
        this.target = target;
    }

    //获得代理对象
    public Object getProxy(){
//        newProxyInstance()参数1 是加载到哪个位置,参数2 代理的接口 参数3 InvocationHandler
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
	//自动调用invoke方法
    //获得被代理对象执行的方法 (处理代理实例返回结果)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       	//method.getName()
        //动态代理的机制就是利用反射实现
        Object result = method.invoke(target, args);
        return result;
    }
}
           

Client . java

public class Client {
    public static void main(String[] args) {
        //有家住户要卖房子 (定义真实对象)
        Master master = new Master();
        //某代理成立了代理公司 (生成动态代理类)
        ProxyInvocationHandler proxy = new ProxyInvocationHandler();
        //张三说想买房子,带到售楼部去看房 (动态设置被代理对象)
        proxy.setTarget(master);
        //员工1对这个行业很熟悉,委托1号员工为他服务 (动态生成代理对象)
        Rent proxy1 = (Rent) proxy.getProxy();
        //1号员工为他服务,完成操作 (代理对象执行方法)
        proxy1.rent();
    }
}
           

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

动态代理优点:

  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!

11、AOP

Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

11.1、简介

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

11.2、Aop在Spring中的作用

提供声明式事务;允许用户自定义切面

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

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

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

11.3、使用Spring实现Aop

【重点】使用AOP织入,需要导入一个依赖包!

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

使用 Spring API实现

applicationContext.xml

需要额外添加约束

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
           
<!-- 注册bean-->
    <bean id="userService" class="service.Impl.UserServiceImpl"/>
    <bean id="log" class="log.Log"/>
    <bean id="afterLog" class="log.AfterLog"/>

    <!-- aop配置-->
    <aop:config>
        <!-- aop切入点 切入点 expression:表达式匹配要执行的方法
                访问修饰符(可省略),返回值,包名.包名.报名...类名.方法名(参数列表)
        -->
        <aop:pointcut id="pointcut" expression="execution(* service.Impl.UserServiceImpl.*(..))" />
        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点
			简单来说就是在pointcut-ref插入advice-ref
-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
           

UserServiceImpl.java

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("添加了一个用户");
    }

    public void del() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void select() {
        System.out.println("查找了一个用户");
    }
}
           

编写增强类 , 个前置增强 一个后置增强

public class Log implements MethodBeforeAdvice {
    /**
     * @param method  要执行的目标对象的方法
     * @param args    执行方法的参数
     * @param target  目标对象
     * @throws Throwable
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法");
    }
}
           
public class AfterLog implements AfterReturningAdvice {
    /**
     * @param returnValue  返回值
     * @param method       被调用的方法
     * @param args         被调用的方法的对象的参数
     * @param target       目标对象
     * @throws Throwable
     */
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法,返回值是"+returnValue);
    }
}
           
public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = context.getBean(UserService.class);
        service.del();
    }

Output:
    执行了service.Impl.UserServiceImpl的del方法
	删除了一个用户
	执行了service.Impl.UserServiceImpl的del方法,返回值是null
           

自定义类实现AOP

自定义类无法取得执行方法的相关信息

<!-- 自定义类-->
	<bean id="diy" class="log.diyLog"/>
    <!-- aop配置-->
    <aop:config>
        <!-- 自定义切面,ref引用类-->
        <aop:aspect ref="diy">
            <!-- 切入点-->
            <aop:pointcut id="point" expression="execution(* service.Impl.UserServiceImpl.*(..))"/>
            <!-- 前置增强,绑定自定义类中方法-->
            <aop:before method="before" pointcut-ref="point"/>
            <!-- 后置增强,绑定自定义类中方法-->
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
           

自定义类

public class diyLog {

    public void before(){
        System.out.println("方法调用前*****************");
    }

    public void after(){
        System.out.println("*****************方法调用后");
    }
}
           

使用注解实现AOP

第一步:编写一个注解实现的增强类

@Aspect  //标注这个类是一个切面
public class Annotation {
    //前置增强
    @Before("execution(* service.Impl.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("--------------方法前---------------");
    }
    
    //后置增强
    @After("execution(* service.Impl.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("--------------方法后---------------");
    }

    //@Around环绕增强中,可以传一个参数代表要获取处理切入的点
    @Around("execution(* service.Impl.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("--------------环绕增强前---------------");
        //执行方法
        Object proceed = point.proceed();
        System.out.println(point.getSignature()); //输入签名(具体的类名+方法名)
        System.out.println("--------------环绕增强后---------------");
    }
}
           

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

<!-- 注解实现-->
    <bean id="anno" class="log.Annotation"/>
    <!-- 开启注解支持-->
    <aop:aspectj-autoproxy/>
           

第三步:测试

public static void main(String[] args) {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService service = context.getBean(UserService.class);
        service.del();
    }

Output:
		--------------环绕增强前---------------
   		--------------方法前---------------
    	删除了一个用户
    	void service.UserService.del()
    	--------------环绕增强后---------------
    	--------------方法后---------------
           

12、整合MyBatis

  1. 导入相关包
  2. 编写配置文件

什么是 MyBatis-Spring?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。

知识基础

在开始使用 MyBatis-Spring 之前,需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。

MyBatis-Spring 需要以下版本:

MyBatis-Spring MyBatis Spring 框架 Spring Batch Java
2.0 3.5+ 5.0+ 4.0+ Java 8+
1.3 3.4+ 3.2.2+ 2.1+ Java 6+

整合实现一

1、引入Spring配置文件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">
           

2、配置数据源替换mybaits的数据源

<!--使用Spring的数据源来替换mybatis的配置  c3p0 druid  需要导入包spring-jdbc-->
    <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/mybatisdemo?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
           

3、配置SqlSessionFactory,关联MyBatis

<!-- sqlSessionFactory-->
<bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="datasource"/>
    <!-- 绑定mybais-->
    <!-- mybatis主配置文件-->
    <property name="configLocation" value="mybatis-config.xml"/>
    <!-- mapper配置文件-->
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
           

4、注册sqlSessionTemplate,关联sqlSessionFactory;

<!-- SqlSessionTemplate  就是以前使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!-- 没有set方法,通过构造器注入-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
           

5、因为Spring无法创建mybatis对象,所以要接管Mybatis就必须手动创建一个set方法

增加Dao接口的实现类;私有化sqlSessionTemplate

public class UserMapperImpl implements UserMapper {
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    public List<User> selectAll() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectAll();
    }
}
           

6、注册实现类bean实现

<!-- 注入Mapper的实现类-->
<bean id="userMapper" class="mapper.Impl.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
           

7、测试

@Test
public void test() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> users = userMapper.selectAll();
    for (User user : users) {
        System.out.println(user);
    }
}
Output:
User(id=41, username=老王, birthday=Wed Feb 28 01:47:08 CST 2018, sex=男, address=北京
User(id=42, username=小二王, birthday=Fri Mar 02 23:09:37 CST 2018, sex=女, address=北京
User(id=43, username=小二王, birthday=Sun Mar 04 19:34:34 CST 2018, sex=女, address=北京
           

UserMapper.xml

<mapper namespace="mapper.UserMapper">
    <select id="selectAll" resultType="user">
        select * from user
    </select>
</mapper>
           

mybatis-config.xml

<typeAliases>
    <package name="domain"/>
</typeAliases>
           

整合实现二

mybatis-spring1.2.3版以上的才有这个 。

dao继承SqlSessionDaoSupport类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate

1、将UserMapperImpl修改一下

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    //调用getSqlSession()方法获得sqlSession对象
    public List<User> selectAll() {
        return getSqlSession().getMapper(UserMapper.class).selectAll();
    }
}
           

2、修改bean的配置

<!-- 整合方式二
     无需创建sqlSessionTemplate
    -->
<bean id="userMapper2" class="mapper.Impl.UserMapperImpl2">
    <!-- 直接调用sqlSessionFactory-->
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
           

13、声明式事务

13.1、回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题。
  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  1. 原子性(atomicity)
    • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
  2. 一致性(consistency)
    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  3. 隔离性(isolation)
    • 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  4. 持久性(durability)
    • 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

如转账,转账途中出异常,一方扣钱另一方未收到钱。

13.2、Spring中的事务管理

编程式事务管理

  • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚(try…catch)
  • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

声明式事务管理

  • 一般情况下比编程式事务好用。
  • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用Spring管理事务,注意头文件的约束导入 : tx

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

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

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

<!--开启事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSource" />
</bean>
           

配置好事务管理器后我们需要去配置事务的通知

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
       <!--配置哪些方法使用什么样的事务,配置事务的传播特性,默认REQUIRED-->
       <tx:method name="add" propagation="REQUIRED"/>
       <tx:method name="delete" propagation="REQUIRED"/>
       <tx:method name="update" propagation="REQUIRED"/>
       <tx:method name="search*" propagation="REQUIRED"/>
       <tx:method name="get" read-only="true"/>
       <!-- 所有方法-->
       <tx:method name="*" propagation="REQUIRED"/>
   </tx:attributes>
</tx:advice>
           

spring事务传播特性:

事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

配置AOP

导入aop的头文件!

<!--配置aop织入事务-->
<aop:config>
    <!--设置切入面-->							<!--任意返回值,impl包下的任意参数所有方法-->
   <aop:pointcut id="txPointcut" expression="execution(* mapper.Impl.*.*(..))"/>
   <!--在切入点添加事务-->
   <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
           

UserMapperImpl.java

public class UserMapperImpl implements UserMapper {
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    User user = new User(52, "巨魔战将", new Date(), "男", "四川德阳");

    public List<User> selectAll() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(user);
        //delUser方法报错不会执行成功,但addUser会执行成功
        mapper.delUser(51);
        return mapper.selectAll();
    }

    public int addUser(User user) {
        return sqlSession.getMapper(UserMapper.class).addUser(user);
    }

    public int delUser(int id) {
        return sqlSession.getMapper(UserMapper.class).delUser(id);

    }
}
           

test.java

public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        List<User> users = userMapper.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }

Output:
User(id=47, username=大G女, birthday=Fri May 15 20:11:36 CST 2020, sex=女, address=长沙湖南)
User(id=48, username=小马宝莉, birthday=Thu Mar 08 19:44:00 CST 2018, sex=女, address=北京)
User(id=50, username=哈拉哨, birthday=Fri May 15 20:45:43 CST 2020, sex=男, address=百京人)
User(id=52, username=巨魔战将, birthday=Wed May 20 10:57:28 CST 2020, sex=男, address=四川)
           

总结

Spring是一个轻量级的IOC和AOP框架