Spring

Spring学习笔记

注: @Data指Lombak自动生成JavaBean规范的注解,仅仅是为了方便阅读

一.Spring的核心功能介绍

  1. IOC(Inversion(相反,反转) Of Control(控制)): 控制反转
  2. AOP(Aspect(面向…什么.) oriented(朝向) Programming(编程)): 面向切面编程(代表:声明式事物)

1.1 IOC控制反转

IOC是一种设计理念,角度比较宏观

会将对象全部交给IOC容器进行管理

1.2 IOC和DI的关系

IOCDI指的是同一个概念

IOC is also known as dependency injection(DI)

IOC也被称作为DI

DI在Spring里面是通过反射技术实现的

1.3 传统的对比Spring创建对象

1⃣️传统

/**
 * @author :Jack_zhou
 */
@Date 
public class User {
    private String username;
    private String password;
    public User(){
        System.out.println("无参构造器");
    }
}

App

/**
 * @author :Jack_zhou
 */
public class App {
    public static void main(String[] args) {
        User user = new User();
    }
}

2⃣️.DI注入

导入依赖

<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>

会把其余的相关lib也导入

org.springframeword:spring-aop:5.3.14
org.springframeword:spring-beans:5.3.14
org.springframeword:spring-core:5.3.14
org.springframeword:spring-expression:5.3.14

创建xml文件,引入相关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 http://www.springframework.org/schema/beans/spring-beans.xsd">
  <!-- 装配bean,将user对象交给IOC容器管理 -->
  <bean id="user" class="pers.zhou.entity.User" />
</beans>

获得IOC容器,获得里面的Bean

public static void main(String[] args) {
  //获得IOC容器
  ApplicationContext Context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  User user = Context.getBean("user", User.class);
  System.out.println(user);
}

1.4 IOC好处和坏处

优点

  1. 如果依赖的类修改了,比如修改了构造函数,如果没有依赖注入,则需要修改依赖对象调用着,如果依赖注入则不需要–提高代码的灵活性
  2. 降低代码的耦合性
  3. 提高代码的可维护性

缺点

  1. 创建对象的步骤变复杂了(新手而言)
  2. IOC通过反射创建对象,所以资源消耗稍微会比普通方式高,但是对于优点来说还是微不足道的
  3. 虽然提高了代码的拓展性,但是对于xml而言,如果修改了类名,那么也需要手动修改xml

二.装配Bean的方式

  1. 基于构造器(有参无参)
  2. 基于静态工厂
  3. 基于实例工厂

Instantiating Beans(实例化bean)

每个Bean都是一个BeanDefintion (Bean 定义)

2.1 基于构造器

Instantiating with a Constructor 基于构造器


you may need a default (empty) Constructor 您可能需要一个空的无参构造器

基于无参构造器

<!-- 装配bean,将user对象交给IOC容器管理 -->
<bean id="user" class="pers.zhou.entity.User" />

基于有参构造器

基于有参构造器,就跟DI注入有关系了

<bean id="user" class="pers.zhou.entity.User">
    <constructor-arg name="username" value="zhou"/>
    <constructor-arg name="password" value="123"/>
</bean>

除了name还有别的选择项,可以定位到相关属性

index:把形参转换为列表通过索引定位
name:通过有参构造器的形参名字定位
type: 要保持顺序一致

2.2 基于静态工厂

工厂主要作用是隐藏过程

Instantiation with a Static Factory Method --用静态工厂方法实例化

<!-- 基于静态工厂方法创建bean    -->
<bean id="user" class="pers.zhou.factory.UserStaticFactory" factory-method="createUser"/>
/**
 * 基于静态工厂创建User对象
 * @return
 */
public static User createUser(){
  User user = new User("admin","123");
  return user;
}
<!--  静态工厂传参数-->
<bean id="user" class="pers.zhou.factory.UserStaticFactory" factory-method="createUserProperties">
  <constructor-arg name="username" value="admin"/>
  <constructor-arg name="password" value="1234"/>
</bean>
/**
 * 基于静态工厂创建User对象
 * @return
 */
public static User createUserProperties(String username,String password){
  User user = new User(username,password);
  return user;
}

2.3 基于实例工厂

Instrantiation by Using an Instance Factory Method --使用实例化工厂方法实例化Bean

 instantiation with an instance factory method invokes a non-static method of 
 an existing bean from the container to create a new bean. 
 通过工厂实例的方法去实例化Bean,具体的说就是通过调用已经存在于IOC容器中的一个Bean(工厂Bean)
 通过该Bean对象调用它的一个非静态方法(工厂方法)去创建另外一个Bean(你需要工厂创建的Bean,工厂方法的返回值)
<!-- 实例工厂实例化对象-->
<!--先把工厂类交给IOC容器做管理-->
<bean id="InstanceUser" class="pers.zhou.factory.UserStaticFactory"/>
<!--将class设置为null,factory-bean设置为IOC容器里面的工厂Bean,factory-method的值为工厂Bean中的非静态方法-->
<bean factory-bean="InstanceUser" factory-method="createUserProperties"/>
public class UserStaticFactory {
  /**
    * 基于实例工厂创建User对象
    */
  public User createUserProperties(){
    User user = new User("admin123","123");
    return user;
  }
}

2.4 疑问

为什么看起来,用了工厂,和原本javaSE的写法一样了,依然是new对象,之前说好的解耦呢?

解答

官方文档里面解释Bean用到了recipe菜谱,菜谱里面清楚的写了这个菜的原料(bean的属性之类的)

这个时候整个bean的创建过程对于开发者而言是暴露的

而工厂模式最根本的目的是:隐藏对象的创建过程

2.5 id和name的异同

相同:

  1. 在xml配置文件中,<bean/>标签中,idname都是用来设置对象在IOC容器的标识的
  2. 同一个配置文件中,都不允许出现多次
  3. 多个配置文件中,都可以出现多次,但是以加载顺序中,最后一个为实际bean的定义

不同:

  1. id比name更严格
  2. name可以有多个,中间用逗号隔开
  3. id只能有一个

三.DI依赖注入

Dependency Injection 依赖注入 DI

DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.
-------------------------
DI存在两种注入方式: 基于构造器注入和基于Setter注入

3.1 基于构造器注入

涉及到的标签为<constructor-arg/>

<!--通过有参构造器注入属性-->
<bean id="user" class="pers.zhou.entity.User">
  <constructor-arg name="username" value="admin"/>
  <constructor-arg name="password" value="123"/>
  <constructor-arg 如果是引用类型那么就使用 ref引入别的bean即可
</bean>

The following example uses the c: namespace to do the same thing as the from Constructor-based Dependency Injection


c标签做的工作和基于构造器注入是一样的

c标签注入

  1. 引入c标签命名空间

    xmlns:c="http://www.springframework.org/schema/c"
    
  2. 通过c标签给属性赋值

    <!-- 通过c标签给属性赋值 -->
    <bean id="user" class="pers.zhou.entity.User" c:username="admin" c:password="123" 
          c:password-ref="如果需要引用类型就用ref" />
    

3.2 基于Setter注入

涉及到的标签为<property />

Setter-based DI is accomplished by the container calling setter methods on your beans after invoking a no-argument constructor(过程为先调用无参构造器获得对象,然后通过对象调用Setter方法注入) or a no-argument static factory method to instantiate your bean.(或者对静态工厂方法不传参,实例化对象后,然后通过对象调用方法注入)
<bean id="user" class="pers.zhou.entity.User">
    <property name="username" value="admin"/>
    <property name="password" value="admin123"/>
</bean>

同样和c标签一样,<property>也可以通过<p>标签简化

The p-namespace lets you use the bean element’s attributes (instead of nested <property/> elements) to describe your property values collaborating beans, or both.
p名称空间允许使用bean元素的属性(而不是嵌套的<property/>元素)来描述协作bean的属性值,或者两者都使用。  
<bean id="user" class="pers.zhou.entity.User" P:username="admin" P:password="123"/>

3.3 构造器好还是setter好?

Constructor-based or setter-based DI?


基于构造器还是基于Setter注入好呢?

								Constructor-based or setter-based DI?
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.

The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.

Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.

两者根据需求

– 使用对特定类最有意义的 DI 样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是 DI 的唯一可用形式。

3.4 循环依赖问题

Circular(圆的,循环) dependencies(依赖)

If you use(如果你使用) predominantly constructor(构造器) injection(注入)
-- 如果你主要使用构造器注入
it is possible(可能,有可能) to create(创建,造成) an unresolvable(无法解决的) circular dependency scenario(可能发生的情况).
-- 那么可能会造成无法解决的循环依赖问题
For example(举个例子): Class A requires(需要) an instance(例子,实例) of class B through constructor injection(注入)
-- 举个例子: class a通过构造函数注入需要一个class B的实例
and class B requires an instance of class A through constructor injection.
-- 并且class B通过构造函数注入时候需要一个class A的实例
If you configure(装配,配置) beans for classes A and B to be injected(注入的)into each other(在一起)
-- 如果你为A和B装配Bean,使他们相互注入
the Spring IoC container(容器) detects(发现) this circular(循环) reference(检测) at runtime,
-- Spring IOC容器在运行时检测此循环引用
and throws a BeanCurrently(当前的)InCreation(尚未创建,创造)Exception.
-- 并且抛出BeanCurrently(当前的)Increation(尚未创建)Exception(异常)
One possible(可能,有可能) solution(解答,解决方案) is to edit(编辑) the source(来源,问题) code(代码) of some classes to be configured(配置的) by setters rather(最好,而不是) than constructors.
-- 一个可能的解决方案是编辑一些由setter而不是构造函数配置的类的源代码
Alternatively(要不,或者), avoid(避免,防止) constructor injection and use setter injection only.
-- 或者,避免构造函数注入,只使用setter注入。
In other words(换句话说), although(尽管,虽然) it is not recommended(推荐的), you can configure(安装,配置) circular(循环) dependencies(依赖) with setter injection(注入).
-- 尽管不建议这样做,但您可以使用setter注入配置循环依赖项。
Unlike(不同的) the typical(典型的,有代表性的) case(实例) (with no circular dependencies),
-- 与典型情况(没有循环依赖关系)不同,
a circular(循环) dependency(依赖) between(在..中间) bean A and bean B forces(实例) one of the beans to be injected(注入的) into the other prior(优先的) to being fully(充分的,完全具体的) initialized(初始化) itself(它本身) (a classic chicken-and-egg scenario)(这是典型的先有鸡还是先有蛋的问题).
-- bean a和bean B之间的循环依赖项迫使其中一个bean在完全初始化自己之前被注入到另一个bean中(一个典型的先有鸡还是先有蛋的场景)。

代码

User

@Data
private String username;
private String password;
private Music music;

Music

@Data
private String name;
private String singer;
private User user;

ApplicationContext.xml

<!--通过有参构造器注入属性-->
<bean id="user" class="pers.zhou.entity.User">
  <constructor-arg name="username" value="admin"/>
  <constructor-arg name="password" value="123"/>
  <constructor-arg name="music" ref="music"/>
</bean>
<bean id="music" class="pers.zhou.entity.Music">
  <constructor-arg name="name" value="我要你"/>
  <constructor-arg name="singer" value="小胡"/>
  <constructor-arg name="user" ref="user"/>
</bean>

App

ApplicationContext Context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
User user = Context.getBean("user", User.class);
Music music = Context.getBean("music", Music.class);
Error creating bean with name 'user': Requested(要求) bean is currently(当时) in creation(创建): Is there an unresolvable(不能解决的) circular referenc(引用)
创建名称为“user”的bean时出错:请求的bean当前正在创建中:是否存在不可解析的循环引用

ApplicationContext.xml

<!--通过Setter注入属性-->
<bean id="user" class="pers.zhou.entity.User">
    <property name="username" value="admin"/>
    <property name="password" value="123"/>
    <property name="music" ref="music"/>
</bean>
<bean id="music" class="pers.zhou.entity.Music">
    <property name="name" value="我要你"/>
    <property name="singer" value="小胡"/>
    <property name="user" ref="user"/>
</bean>
User无参构造器
Music的Setter方法

为什么通过构造器注入报错,而通过setter注入没问题呢?

  1. 跟执行流程有关,通过构造器constructor-arg有参构造器,连对象都创建不了
  2. 使用Setter注入会先通过反射拿到无参构造器实例化User对象,User被实例化之后,Music正常创建,当Music创建完成之后,那么User随之也就完成了

3.5 Inner Bean 内部Bean

  1. SpringIOC 容器 只管创建,不负责管理
  2. Inner Bean 专属于当前外部Bean所有,其余的外部Bean没办法引用
  3. 因为上述特点所以内部Beanid可以不写,因为没有必要
<bean id="user" class="pers.zhou.entity.User">
  <property name="username" value="admin"/>
  <property name="password" value="123"/>
  <property name="music">
    <bean class="pers.zhou.entity.Music">
      <property name="name" value=""/>
      <property name="singer" value="地雷"/>
    </bean>
  </property>
</bean>

3.6 常见类型注入

1⃣️注入Array

<!--注入数组 -->
<bean id="Person" class="pers.zhou.entity.Person">
    <property name="name" value="地雷"/>
    <property name="age" value="18"/>
    <property name="wife">
        <array>
            <value>媛媛</value>
            <value>灿灿</value>
            <value>周周</value>
        </array>
    </property>
</bean>

2⃣️.注入set

<!-- 注入set -->
<property name="hobby">
  <set>
    <value>吃饭</value>
    <value>睡觉</value>
    <value>打豆豆</value>
  </set>
</property>

3⃣️.注入list

<property name="habit">
    <list>
        <value>抽烟</value>
        <value>喝酒</value>
        <value>嚼槟榔</value>
    </list>
</property>

4⃣️.注入Map

<property name="cartoonMap">
  <map>
    <entry key="1" value="天线宝宝"/>
    <entry key="2" value="迪迦奥特曼"/>
  </map>
</property>

当使用接口作为约束类型时,Spring在转型时会默认用实现类进行接收

List--->ArrayList
Set--->LinkedHashSet
Map--->LinkedHashMap

四.Bean的常用元属性metaDate

  1. Scope: 代表Bean的作用域
  2. lazy-init: 懒加载
  3. init-method: 执行自定义初始化后方法
  4. destory-method:执行自定义销毁方法
  5. depends-on: 依赖于某个实现Bean

4.1 Scope

代表Bean的作用域

1… Singleton(单独)

(Default) Scopes a single(单一的) bean definition(定义) to a single(单一的) object instance(实例) for each Spring IoC container(容器). 
(默认)将单个bean定义作用于每个Spring IoC容器的单个对象实例。
代表一个容器中只会创建一个Bean的实例对象--->单例模式

2.prototype(原型)

Scopes(范围) a single(单一) bean definition(定义) to any number of object instances.
原型多实例,代表容器可以创建任意数量的Bean的实例对象

3. request

web环境下,每一次独立的HTTP请求都存在唯一实例,实例的存在依赖于请求的生命周期

4. session

web环境下,Session会话中存在唯一实例

5.application

web环境下,ServletContext中存在唯一实例

6.WebSocket

web环境下,Bean的作用域限定在webSocket(双向长连接)的生命周期内

Singletonprototype的区别

单例作用域下,无论找容器要多少次实例对象,拿到的都是同一个,而且在容器初始化过程中,单例Bean就会被实例化

多实例作用域下,无论找容器要多少次实例对象,拿到的都是新的,不同的,而且在容器初始化过程中,多实例Bean不会被立刻实例化,除非调用getBean(),或者其他的单例引用到了它,多实例的Bean也会提前实例化一次

单例在多线程下存在并发修改的问题,多实例就不存在

<bean id="Person" class="pers.zhou.entity.Person" scope="prototype"></bean>
//获得IOC容器
ApplicationContext Context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Person person1 = (Person) Context.getBean("Person");
Person person2 = (Person) Context.getBean("Person");
System.out.println(person1==person2); //false
<bean id="Person" class="pers.zhou.entity.Person" scope="singleton"></bean>
//获得IOC容器
ApplicationContext Context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Person person1 = (Person) Context.getBean("Person");
Person person2 = (Person) Context.getBean("Person");
System.out.println(person1==person2); //true

4.2lazy-init懒加载

根据Singleton的定义,在容器初始化过程中,会实例化单例Bean,如果想要单例在容器初始化中不去实例化可以使用lazy-init懒加载

<bean id="Person" class="pers.zhou.entity.Person" scope="singleton" lazy-init="true"></bean>

4.3 depends-on

依赖于某个实例

<bean id="Person" class="pers.zhou.entity.Person" scope="singleton" lazy-init="true" depends-on="user">
  <property name="name" value="地雷"/>
</bean>
<bean id="user" class="pers.zhou.entity.User"/>

depends-onref有什么区别?

  1. ref的耦合性更强,而depends-on只是建立管理关系,并不一定要持有对象

五.Bean的生命周期

  1. 实例化:Spring在对Bean的实例化(默认调用无参构造器Spoce为singleton)
  2. 属性赋值:容器对属性进行注入
  3. 如果Bean实现了BeanNameAware接口,容器就将Bean的id通过setBeanName方法传进去
  4. 如果Bean实现了BeanFactory接口,那么容器就将BeanFactory容器实例通过setBeanFactory传入
  5. 如果Bean实现了ApplicationContextAware接口,那么容器将Bean所在的应用上下文引用传入
  6. 如果Bean实现了BeanPostProcessor接口,那么就调用postProcessBeforeInitialization方法对Bean进行前置处理
  7. 如果Bean实现了InitializingBean接口,那就通过调用afterPropertiesSet方法进行自定义的初始化,作用等同于为Bean设置init-method进行后置处理
  8. 如果Bean实现了DisposableBean接口,那就通过调用desatory方法进行自定义的销毁,作用等同于为Bean设置destory-method进行销毁处理

六.手刃简易版IOC容器

pom.xml

<!-- dom4j:利用java对XML文件进行解析的工具包 -->
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.1</version>
</dependency>
<!--jaxen:是java支持对Xpath表达式的解析工具包 XML Path Language-->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>

applicationContextSimple

<?xml version="1.0" encoding="utf-8" ?>
<container>
    <entity id="user" class="pers.zhou.entity.User">
        <setter name="username" value="迪迦"/>
        <setter name="password" value="123"/>
    </entity>
</container>

ApplicationContext

public interface ApplicationContext {
  /**
     * 通过名字获取Bean
     * @param beanName Bean的id或者name
     * @return 返回获得的对象
     */
  Object getBean(String beanName);
}

ApplicationContextImpl

package pers.zhou.simple;

import org.dom4j.*;
import org.dom4j.io.SAXReader;
import pers.zhou.entity.User;

import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author :Jack_zhou
 */
public class ApplicationContextImpl implements ApplicationContext {
    /**
     * 容器本身使用ConcurrentHashMap是线程安全的
     */
    private Map<Object, Object> container = new ConcurrentHashMap();

    /**
     * 通过无参构造器初始化资源
     */
    public ApplicationContextImpl() {
        try {
            //得到配置文件,读取配置文件
            String path = this.getClass().getResource("/applicationContextSimple.xml").getPath();
            System.out.println("配置文件的地址为" + path);
            // dom4j解析path里面的路径
            SAXReader saxReader = new SAXReader();
            Document read = saxReader.read(new File(path));
            //read.getRootElement() 获得XML资源文件的根节点
            //selectNodes("entity") 获得子节点
            List<Node> entity = read.getRootElement().selectNodes("entity");
            for (Node node : entity) {
                // 强制转换为Element接口,因为Node是顶级接口,功能不完善,但是其子接口Element功能比较完善
                Element element = (Element) node;
                // 获得entity的id和class属性,传入属性的名,返回属性的值
                String id = element.attributeValue("id");
                String aClass = element.attributeValue("class");
                System.out.println("id的名" + id + "class的值为:" + aClass);
                //通过反射获取对象
                Class<?> clazz = Class.forName(aClass);
                //调用newInstance()创建对象
                Object object = clazz.newInstance();
                //接着获得三级节点
                List<Node> property = element.selectNodes("setter");
                for (Node node1 : property) {
                    //强制转换为Element接口,因为Node是顶级接口,功能不完善,但是其子接口Element功能比较完善
                    Element element1 = (Element) node1;
                    // 获得name和value
                    String name = element1.attributeValue("name");
                    String value = element1.attributeValue("value");
                    // 获取set方法的名字  set+属性名首字母大写
                    String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
                    System.out.println("拼接出来的set方法为:"+methodName);
                    // 通过反射拿到对应的Method对象
                    Method method = clazz.getMethod(methodName,String.class);
                    //反射赋值
                    method.invoke(object,value);
                }
                //传入到Map
                container.put(id,object);
                System.out.println("容器初始化完毕");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String beanName) {
        // 通过name获取对应的值
        return container.get(beanName);
    }
}

七. 注解开发

7.1 为什么会有注解开发?

优点:

  1. 摆脱XML繁琐的配置
  2. 可读性比较好,声明式的开发方式,对中小型项目是很方便的

配合XML配置,开启注解开发,需要以下的声明开启组件扫描

<!--开启注解扫描-->
<context:component-scan base-package="pers.zhou"/>

7.2 注解分类

1.用来标记和管理Bean
@Component : 通用注解,意思就是交给IOC容器做管理
@Controller: 在WEB环境下放在控制层中的代码上,意思就是交给IOC容器管理
@Service:    在Server层的代码上,意思就是交给IOC容器管理
@Repository: 在Dao层的代码上,意思就是交给IOC容器管理
@Component
public class User {
2.帮助容器注入属性的注解

自动装配的注解

按类型注入:
a. @Autowired : 由容器按照管理的Bean的类型去注入
b. @Inject:     来自JSR-330文件,通过类型注入
按名称注入:
a. @Named:			来自JSR-330文件,通过名称注入
b. @Resource:来自JSR-250文件:先按照名称注入,如果没有匹配到,在尝试按照类型注入
<!--开启注解扫描-->
<context:component-scan base-package="pers.zhou"/>
@Component
public interface UserDao {
    /**
     * 通过id寻找用户
     * @param id 用户id
     * @return 返回用户
     */
    User queryUserById(Integer id);
}
@Component
public class UserDaoImpl implements UserDao{
    @Override
    public User queryUserById(Integer id) {
        return new User("盈盈","admin");
    }
}
@Component
public class UserDaoService {
  	@Autowired
    private UserDaoImpl userDao;

    public void test(){
        User user = userDao.queryUserById(1);
        System.out.println(user);
    }
}
public static void main(String[] args) {
    //获得IOC容器
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    UserDaoService userDaoService = context.getBean("userDaoService", UserDaoService.class);
    userDaoService.test();
}

@Autowired

  1. 根据类型注入依赖,如果不满足还会尝试根据属性名注入
  2. 默认情况下,如果该注解放在属性上,Spring通过反射破坏权限直接赋值
  3. @Qualifier(value = “studentDao”)还可以通过Qualifier注入
  4. 如果需要通过setter注入.直接把@Autowired放在set方法上即可

@Inject:来自JSR-330文件,通过类型注入

按名称

  1. @Named:来自JSR-330文件,通过名称注入
  2. @Resource:来自JSR-250文件:先按照名称注入,如果没有匹配到,在尝试按照类型注入
@Resource
private UserDaoImpl test;
3.辅助位的注解

对Bean的元数据进行设置的注解

@Qualifier一般和@Autowired配合使用,用来指定注入那一块的Bean
@Promary:一般用在数据类型注入的情况,相同类型有多个Bean满足,那么在希望被注入的Bean上加入该注解,相同类型确保					不要有多个@Promary
@Scope(value = "prototype"):用来指定Bean的范围,和XML一样,默认为Singleton,可以设置value为prototype多实例
@PostConstruct:自定义初始化方法,相当于XML配置文件中的init-method属性
@PreDestroy:自定义销毁方法,相当于XML配置文件中的destory-method属性

多实例容器的Bean,容器只负责创建,不负责销毁

*@value:为Bean注入静态数据

jdbc.properties

myLogin=admin

application.xml

<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 标签不可以出现多次,但是可以使用通配符:classpath: *.properties -->

APP

public static void main(String[] args) {
  //获得IOC容器
  ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  UserDaoService userDaoService = context.getBean("userDaoService", UserDaoService.class);
  userDaoService.test();
}

UserDaoService

@Value("${myLogin}")
private String admin;

7.3 Java配置类

  1. 用带有@Bean注解方法替代bean标签
  2. 方法名用来替代id,方法返回值替代class
/**
 * Java配置类
 * @Author :Jack_Zhou
 * @Configuration: 声明是一个配置类
 */
@Configuration
@ComponentScan({"pers.zhou.dao","pers.zhou.entity"})
public class ContextConfig {
    /**
     *  @Bean: 声明是一个Bean
     *  id是admin,class是User类
     *  @Scope("prototype") : 不是单例模式
     */
    @Bean
    @Scope("prototype")
    public User admin(){
        return new User("admin","123");
    }
}
public static void main(String[] args) {
  // 实例化基于Java配置类使用AnnotationConfigApplicationContext();
  AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ContextConfig.class);
  User admin = (User) applicationContext.getBean("admin");
  System.out.println(admin);
}

八.AOP

8.1 了解AOP

Aspect(方向,朝向) Oriented(以...为方向) Programming(设计,规划) with(用,与,随着) Spring
使用Spring进行面向切面的编程

  1. 是对OOP(Object Priented Programming)的补充
  2. 在Spring框架中,AOP的典型实现是声明式事务
  3. AOP的底层基于动态代理
  4. 动态代理分JDK动态代理和CGLIB动态代理,默认SpringAop使用的是JDK的动态代理
术语说明
Aspect切面:在SpringAop中,切面就是一个普通的类,里面封装的就是一些需要增强的代码(功能)
Join Point连接点:程序执行过程中的一点,SpringAop中,连接点通常代表一个方法的运行
Advice通知:指的就是拦截到具体的Join Point之后要做什么,什么时候做,理解为具体增强了什么
Pointcut切点:配合切点表达式来定位在那些方法和类上执行
Weaving织入:指的就是将通知(Advice)应用到目标对象(Target Object)上,形成代理对象(Aop proxy)的过程

简单一句话:SpringAop的概念就说了一件事:什么地方,什么事件,做什么事==>增强

  1. 引入依赖

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
    
  2. 书写业务层

    public class MyAspect {
        /**
         * 检查用户是否登录
         * @return 返回结果
         */
        public boolean CheckUserLoginAspect(){
            System.out.println("检查用户是否登录");
            return false;
        }
    }
    
    @Component
    public class LoginService {
        /**
         * 用户对应歌曲
         * @return 返回用户歌曲
         */
        public Music UserByMusic(){
            System.out.println("进入用户歌曲单");
            return null;
        }
    }
    
  3. 配置XML

    <!--开启注解扫描-->
    <context:component-scan base-package="pers.zhou.aop"/>
    <!-- 把Aspect配置类交给IOC容器 -->
    <bean id="MyAspect" class="pers.zhou.aop.aspect.MyAspect"/>
    <!-- 配置AOP-->
    <aop:config>
      <!-- 定义切点,在什么地方对目标对象做什么事情-->
      <aop:pointcut id="firstAspect" expression="execution(* pers..*Service.*(..))"/>
      <!-- 告诉IOC这个Bean作为切面 -->
      <aop:aspect ref="MyAspect">
        <!-- 在目标方法执行之前执行切面方法-->
        <!-- 将通知和切点结合在一起 -->
        <aop:before method="CheckUserLoginAspect" pointcut-ref="firstAspect"/>
      </aop:aspect>
    </aop:config>
    

8.2 父子模块产生关联

父模块打包方式为pom

<!--外层的父模块打包成pom-->
<packaging>pom</packaging>
<!--  只做版本的管理,不会引入依赖-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子模块引入依赖不需要版本

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
</dependencies>

8.3 代理模式

1.静态代理
/**
 * @author :Jack_zhou
 * 静态代理
 */
public interface StaticServiceInterface {
    /**
     * 租房子的方法
     */
    public void rentHouse();
}
/**
 * @author :Jack_zhou
 * 房东类
 */
public class HouseLandlord implements StaticServiceInterface{
    /**
     * 重写租房子的方法
     */
    @Override
    public void rentHouse() {
        System.out.println("我出租了一套房");
    }
}
/**
 * @author :Jack_zhou
 * 中介类
 */
public class IntermediaryAgent implements StaticServiceInterface{
    /**
     * 需要一个房东
     */
    private HouseLandlord houseLandlord;

    @Override
    public void rentHouse() {
        System.out.println("提交中介费");
        // 调用租房子的方法
        houseLandlord.rentHouse();
    }
}
2.动态代理

2.1 JDK动态代理

基于接口实现

8.4 切点表达式

切点表达式execution(* com…*Service.*(…))
方法签名void pers.zhou.service,MyMusicService.selectList()
  1. Execution :执行

涉及到的通配符(从左到右)

  1. *: 代表返回值的类型,代表匹配所有返回值的方法
  2. ..:包的通配符,..代表当前包以及子包
  3. *Service:代表类名的通配符,以Service结尾的
  4. *:代表方法名字的匹配,*代表所有方法的匹配
  5. (…):代表参数,(..)代表所有参数任意类型参数
  6. ps:(String,ing)代表参数两个,必须是String,int

8.5SpringAOP通知类型

目标方法 : (被切点表达式所匹配到的方法)

类型说明
before advice前置通知:在目标方法执行之前
after returning advice返回后通知:在目标方法执行返回数据后执行(正常结束执行)
after throwinng advice异常通知:在方法执行抛出异常后执行
after finally advice后置通知:在目标方法运行后执行(无论目标方法是正常结束还是抛异常)
Around advice环绕通知
<!--开启注解扫描-->
<context:component-scan base-package="pers.zhou"/>
<!-- 把Aspect配置类交给IOC容器 -->
<bean id="MyAspect" class="pers.zhou.advice.Advice"/>
<!-- 配置AOP-->
<aop:config>
    <!-- 定义切点,在什么地方对目标对象做什么事情-->
    <aop:pointcut id="firstAspect" expression="execution(* pers.zhou.advice.TargetMethod.download())"/>
    <!-- 告诉IOC这个Bean作为切面 -->
    <aop:aspect ref="MyAspect">
        <!-- 在目标方法执行之前执行切面方法-->
        <!-- 将通知和切点结合在一起 -->
        <!-- 前置 通知 -->
       <aop:before method="checkUser" pointcut-ref="firstAspect"/>
        <!-- 返回后通知 -->
        <aop:after-returning method="success" pointcut-ref="firstAspect"/>
        <!-- 异常通知 -->
        <aop:after-throwing method="exception" pointcut-ref="firstAspect"/>
        <!-- finally通知 -->
        <aop:after method="finaActive" pointcut-ref="firstAspect"/>
    </aop:aspect>
</aop:config>
/**
 * @author :Jack_zhou
 * 目标方法
 */
@Component("targetMethod")
public class TargetMethod {
    public void download(){
        System.out.println("正在下载音乐....");
        throw new RuntimeException("无中生有");
    }
}
/**
 * @author :Jack_zhou
 * 通知
 */
public class Advice {

    /**
     * 前置通知
     */
    public void checkUser(){
        System.out.println("检查用户是否具备下载条件");
    }
    /**
     * 返回后通知
     */
    public void success(){
        System.out.println("下载成功...");
    }
    /**
     * 异常后通知
     */
    public void exception(){
        System.out.println("哦豁,出现异常了...");
    }
    /**
     * finally通知
     */
    public void finaActive(){
        System.out.println("欢迎下次光临~");
    }

APP

/**
 * @author :Jack_zhou
 */
public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContextAspect.xml");
        TargetMethod targetMethod = (TargetMethod) context.getBean("targetMethod");
        targetMethod.download();
    }
}

2. 环绕通知

/**
 * @author :Jack_zhou
 * 环绕通知
 */
public class AroundActive {
    /**
     * 环绕通知
     */
    public void aroundActive(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Advice advice = new Advice();
        try {
            //前置通知
            advice.checkUser();
            //执行目标方法
            Object proceed = proceedingJoinPoint.proceed();
            //返回后通知
            advice.success();
        } catch (Exception e) {
            e.printStackTrace();
            //异常后通知
            advice.exception();
            //抛出去
            throw new RuntimeException("无中生有");
        }finally{
            //finally通知
            advice.finaActive();
        }
    }
}
<!--开启注解扫描-->
<context:component-scan base-package="pers.zhou"/>
<!-- 把Aspect配置类交给IOC容器 -->
<bean id="MyAspect" class="pers.zhou.advice.AroundActive"/>
<!-- 配置AOP-->
<aop:config>
    <!-- 定义切点,在什么地方对目标对象做什么事情-->
    <aop:pointcut id="firstAspect" expression="execution(* pers.zhou.advice.TargetMethod.download())"/>
    <!-- 告诉IOC这个Bean作为切面 -->
    <aop:aspect ref="MyAspect">
       <aop:around method="aroundActive" pointcut-ref="firstAspect"/>
    </aop:aspect>
</aop:config>

8.6通过注解对AOP实现

/**
 * @author :Jack_zhou
 */
@Component
@Aspect //<bean id="MyAspect" class="pers.zhou.advice.Adivce"/>
public class Advice {

    @Pointcut("execution(void pers.zhou.advice.TargetMethod.download())")
    public void pointCat(){}
    /**
     * 前置通知
     */
    @Before("pointCat()")
    public void checkUser(){
        System.out.println("检查用户是否具备下载条件");
    }
    /**
     * 返回后通知
     */
    @AfterReturning("pointCat()")
    public void success(){
        System.out.println("下载成功...");
    }
    /**
     * 异常后通知
     */
    @AfterThrowing("pointCat()")
    public void exception(){
        System.out.println("哦豁,出现异常了...");
    }
    /**
     * finally通知
     */
    @After("pointCat()")
    public void finaActive(){
        System.out.println("欢迎下次光临~");
    }

}

环绕通知

/**
 * @author :Jack_zhou
 * 环绕通知
 */
@Component
@Aspect
public class AroundActive {
    /**
     * 环绕通知
     */
    @Around("execution(void pers.zhou.advice.TargetMethod.download())")
    public void aroundActive(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Advice advice = new Advice();
        try {
            //前置通知
            advice.checkUser();
            //执行目标方法
            Object proceed = proceedingJoinPoint.proceed();
            //返回后通知
            advice.success();
        } catch (Exception e) {
            e.printStackTrace();
            //异常后通知
            advice.exception();
            //抛出去
            throw new RuntimeException("无中生有");
        }finally{
            //finally通知
            advice.finaActive();
        }
    }
}

8.7 Spring-test

引入依赖

<!-- Junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<!--spring-text依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring-version}</version>
</dependency>
<!--   日志依赖-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.6</version>
</dependency>
@Autowired
private TargetMethod targetMethod;

@Test
public void test(){
    targetMethod.download();
}

会抛出去空指针NullPointerException

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContextAspect.xml")
public class AppTest {
    @Autowired
    private TargetMethod targetMethod;

    @Test
    public void test(){
        targetMethod.download();
    }
}

RunWith:需要来自Junit,代表可以某种环境运行测试用例 需要的值为:Runner类型的Class对象,RunnerJunit定义的环境的抽象类 SpringJUnit4ClassRunner.class 来自于Spring-test类 间接继承了Runner类,可以为Junit提供Spring环境

ContextConfigurationSpring-test提供,用来指定配置文件(locations或者value指定)或者配置类(classes指定)的路径,以便初始化IOC容器

九.spring-jdbc

  1. 引入依赖

    1. <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>${spring-version}</version>
      </dependency>
      
    2. 书写配置信息

      jdbc.driverClassName=com.mysql.cj.jdbc.Driver
      jdbc.url=jdbc:mysql://localhost:3306/vip2105?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&useUnicode=true&useSSL=false
      jdbc.username=root
      jdbc.password=123456
      
    3. xml配置

      <!--   引入配置文件-->
      <context:property-placeholder location="jdbc.properties"/>
      
      <!--  数据源-->
      <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="${jdbc.driverClassName}"/>
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
      </bean>
      <!-- SpringJdbc提供的工具类封装的crud方法-->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      <!--    开启注解扫描-->
      <context:component-scan base-package="pers"/>
      <!--  Spring事务管理器-->
      <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
    4. 实体类

      @Date   
      private int id;
      private String username;
      private String pwd;
      
    5. @Repository
      public class UserDao {
          @Autowired
          private JdbcTemplate jdbcTemplate;
      
          @Autowired
          private DataSourceTransactionManager transactionManager;
      
          public User findById(Integer id) {
              String sql = "SELECT * FROM wyy_user WHERE id=?";
              User user = jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<User>(User.class));
              return user;
          }
      
          /**
           * 通过用户名寻找密码
           *
           * @param username 用户名
           * @return 返回对应的密码
           */
          public String queryPasswordByUsername(String username) {
              String sql = "SELECT pwd FROM wyy_user WHERE username=?";
              User user = jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper<User>(User.class));
              return user.getPwd();
          }
      
          /**
           * 批量新增sql
           *
           * @param userList
           */
          public void batchInsert(List<User> userList) {
              //sql语句
              String sql = "insert into wyy_user(username,pwd) VALUES(?,?)";
      
              // 创建一个事务
              DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
              /**
               * 开启事务
               * transaction:表示开启事务之后的状态
               */
              TransactionStatus status = transactionManager.getTransaction(defaultTransactionDefinition);
              try{
                  for (int i = 0; i < userList.size(); i++) {
                      if (i == 4) {
                          throw new RuntimeException("无中生有的异常");
                      }
                      jdbcTemplate.update(sql, userList.get(i).getUsername(), userList.get(i).getPwd());
                  }
                  // 没有出现问题则提交
                  transactionManager.commit(status);
              }catch(Exception e){
                  // 发生异常
                  System.err.println("发生异常回滚");
                  transactionManager.rollback(status);
              }
      
          }
      }
      
    6. public class App {
          public static void main(String[] args) {
              ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContextJdbc.xml");
              //获得所有管理的Bean
              String[] beanDefinitionNames = context.getBeanDefinitionNames();
              for (String beanDefinitionName : beanDefinitionNames) {
                  System.out.println(beanDefinitionName);
              }
              UserDao userDao = context.getBean("userDao", UserDao.class);
              // 根据Id查用户
              System.out.println(userDao.findById(1));
              // 根据用户名查密码
              System.out.println(userDao.queryPasswordByUsername("fanfan"));
          }
      }
      

十.事务传播机制

名称说明
PROPAGATION_REQUIRED默认的,如果当前没有事务,就新建一个事务,如果当前已经存在事务,那么就加入
PROPAGATION_SUPPORTS如果当前有事务就支持当前事务,如果当前没有事务就以非事务方法执行
PROPAGATION_MANDATORY如果当前有事务则使用当前事务,如果当前没有事务就抛出异常
PROPAGATION_REQUIRES_NEW无论当前有没有事务,都要创建一个新的,如果当前存在事务,就把当前事务挂起
PROPAGATION_NOT_SUPPORTED无论当前存不存在事务,都已非事务方式执行,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER以非事务方式执行操作,如果存在错误,抛出异常
PROPAGATION_NESTED如果当前存在事务,则以子事务在嵌套事务内部执行,如果没有事务就类似于PROPAGATION_REQUIRED,新建一个事务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值