转:Spring5最新完整教程IDEA版通俗易懂

文档笔记是根据B站狂神视频来的。笔记中融入了我一些简单看法,笔记来源狂神微信公众号。

1.IOC注册对象的方式

1.无参构造方法(默认)

实体类默认有无参构造方法。如果是有参会报错

1.User.java

 public class User {
 ​
    private String name;
 ​
    public User() {
        System.out.println("user无参构造方法");
   }
 ​
    public void setName(String name) {
        this.name = name;
   }
 ​
    public void show(){
        System.out.println("name="+ name );
   }
 ​
 }

当实体类存在时,即可通过无参构造创建对象

2.beans.xml

xml文件在resources文件夹下。且此文件夹为根目录

id="user":用于获取对象时的绑定,否则怎么知道获取哪个

 <?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="user" class="com.kuang.pojo.User">
        <property name="name" value="kuangshen"/>
    </bean>
 ​
 </beans>

3.测试类

 @Test
 public void test(){
    ApplicationContext context = new                     
  ClassPathXmlApplicationContext("beans.xml");
    //在执行getBean的时候, user已经创建好了 , 通过无参构造
    User user = (User) context.getBean("user");
    //调用对象的方法 .
    user.show();
 }

 

2.有参构造

实体类在无参情况多了有参构造方法,但是注册bean的方法有三种。

1.User.java

 public class UserT {
 ​
    private String name;
 ​
    public UserT(String name) {
        this.name = name;
   }
 ​
    public void setName(String name) {
        this.name = name;
   }
 ​
    public void show(){
        System.out.println("name="+ name );
   }
 ​
 }

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的方式-->
 ​
 </beans>

 

1.下标赋值

假如只有一个属性参数,那么它的下标就是0

 <!-- 第一种根据index参数下标设置 -->
 <bean id="userT" class="com.kuang.pojo.UserT">
    <!-- index指构造方法 , 下标从0开始 -->
    <constructor-arg index="0" value="kuangshen2"/>
 </bean>

2.参数类型创建<不建议>

 <!-- 第三种根据参数类型设置 -->
 <bean id="userT" class="com.kuang.pojo.UserT">
    <constructor-arg type="java.lang.String" value="kuangshen2"/>
 </bean>

3.参数名创建<主要>

 <!-- 第二种根据参数名字设置 -->
 <bean id="userT" class="com.kuang.pojo.UserT">
    <!-- name指参数名 -->
    <constructor-arg name="name" value="kuangshen2"/>
 </bean>

4.测试

@Test
public void testT(){
    //可以读取多个xml
   ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
   UserT user = (UserT) context.getBean("userT");
   user.show();
}

结论:在配置文件加载的时候。其中管理的对象都已经初始化了!

3.总结

对象在注册时候就被实例化了,而且只有一个实例,默认单例模式

也就是说,虽然我们获取的是不同对象,但是内存中只有一个

2.spring相关配置

1.bean别名

通过bean的id对bean起别名,可以起多个,相当于在获取对象时,就可以通过这个新的id获取。

bean:

<bean id="userT" class="com.kuang.pojo.UserT">
   <!-- name指参数名 -->
   <constructor-arg name="name" value="kuangshen2"/>
</bean>

别名

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

2.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" name="user1,user2">
   <property name="name" value="Spring"/>
</bean>

3.import

用于团队开发,整合多人的不同xml任务到一起,所以我们创建一个总的xml文件在导入其他人的。

<!--示例:-->
<import resource="{path}/beans.xml"/>

<!--应用:-->
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

3.依赖注入

  • 依赖注入(Dependency Injection,DI)。

  • 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .

  • 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .

  • 这些不同的注入方式可以都在一个bean下进行

1.构造器注入

就是有参和无参构造的注入

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

package com.kuang.pojo;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
 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;
 
     public void setName(String name) {
         this.name = name;
    }
 
     public void setAddress(Address address) {
         this.address = address;
    }
 
     public void setBooks(String[] books) {
         this.books = books;
    }
 
     public void setHobbys(List<String> hobbys) {
         this.hobbys = hobbys;
    }
 
     public void setCard(Map<String, String> card) {
         this.card = card;
    }
 
     public void setGames(Set<String> games) {
         this.games = games;
    }
 
     public void setWife(String wife) {
         this.wife = wife;
    }
 
     public void setInfo(Properties info) {
         this.info = info;
    }
 
     public void show(){
         System.out.println("name="+ name
                 + ",address="+ address.getAddress()
                 + ",books="
        );
         for (String book:books){
             System.out.print("<<"+book+">>\t");
        }
         System.out.println("\n爱好:"+hobbys);
 
         System.out.println("card:"+card);
 
         System.out.println("games:"+games);
 
         System.out.println("wife:"+wife);
 
         System.out.println("info:"+info);
 
    }
 }

1.常量注入

<bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
 </bean>

测试

@Test
 public void test01(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
 
     Student student = (Student) context.getBean("student");
 
     System.out.println(student.getName());
 
 }

2.Bean注入

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

说白了就是将一个bean注入到这个bean中,类似C引用

<bean id="addr" class="com.kuang.pojo.Address">
     <property name="address" value="重庆"/>
 </bean>
 
 <bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
 </bean>

3.数组注入

 <bean id="student" class="com.kuang.pojo.Student">
     <property name="name" value="小明"/>
     <property name="address" ref="addr"/>
     <property name="books">
         <array>
             <value>西游记</value>
             <value>红楼梦</value>
             <value>水浒传</value>
         </array>
     </property>
 </bean>

4.List注入

<property name="hobbys">
     <list>
         <value>听歌</value>
         <value>看电影</value>
         <value>爬山</value>
     </list>
 </property>

 

5.Map注入

<property name="card">
     <map>
         <entry key="中国邮政" value="456456456465456"/>
         <entry key="建设" value="1456682255511"/>
     </map>
 </property>

6.set注入

<property name="games">
     <set>
         <value>LOL</value>
         <value>BOB</value>
         <value>COC</value>
     </set>
 </property>

7.Null注入

 <property name="wife"><null/></property>

8.Properties注入

<property name="info">
     <props>
         <prop key="学号">20190604</prop>
         <prop key="性别">男</prop>
         <prop key="姓名">小明</prop>
     </props>
 </property>

3.扩展方式注入

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

public class User {
     private String name;
     private int age;
 
     public void setName(String name) {
         this.name = name;
    }
 
     public void setAge(int age) {
         this.age = age;
    }
 
     @Override
     public String toString() {
         return "User{" +
                 "name='" + name + '\'' +
                 ", age=" + age +
                 '}';
    }
 }

对于P或C约束/注入需要加命名空间

1、P命名空间注入

需要在头文件中加入约束文件,可以直接注入属性值

导入约束 : xmlns:p="http://www.springframework.org/schema/p"
 
 <!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>

2、c 命名空间注入

需要在头文件中加入约束文件,需要有有参无参构造

用到了构造器注入

导入约束 : xmlns:c="http://www.springframework.org/schema/c"
 <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>

3.测试

@Test
 public void test02(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
     User user = (User) context.getBean("user");
     System.out.println(user);
 }

4.bean作用域

在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .

几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

1.Singleton

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

 <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

测试

@Test
 public void test03(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
     User user = (User) context.getBean("user");
     User user2 = (User) context.getBean("user");
     System.out.println(user==user2);
 }

2.Prototype

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
  或者
 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

3.Request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>

针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。

4.Session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

 <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。

4.自动装配

  • 自动装配是使用spring满足bean依赖的一种方法

  • spring会在应用上下文中为某个bean寻找其依赖的bean。

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

  1. 在xml中显式配置;

  2. 在java中显式配置;

  3. 隐式的bean发现机制和自动装配。(重要)

这里我们主要讲第三种:自动化的装配bean。

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

  1. 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;

  2. 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

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

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

1.环境搭建

此时只是做一个基本的搭建,但不是基础搭建。

新建两个实体类,Cat Dog 都有一个叫的方法

public class Cat {
   public void shout() {
       System.out.println("miao~");
  }
}
public class Dog {
   public void shout() {
       System.out.println("wang~");
  }
}

建立一个用户类User,前两个实体类可以属于用户类,记得加上set方法等

public class User {
   private Cat cat;
   private Dog dog;
   private String str;
}

编写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 id="dog" class="com.kuang.pojo.Dog"/>
   <bean id="cat" class="com.kuang.pojo.Cat"/>

   <bean id="user" class="com.kuang.pojo.User">
       <property name="cat" ref="cat"/>
       <property name="dog" ref="dog"/>
       <property name="str" value="qinjiang"/>
   </bean>
</beans>

测试

public class MyTest {
   @Test
   public void testMethodAutowire() {
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       User user = (User) context.getBean("user");
       user.getCat().shout();
       user.getDog().shout();
  }
}

结果正常输出,环境OK,上面没有用到自动装配。

之所以用两个类属于一个类,是希望注册对应的bean以后,不用单独注入值,而是通过一定方法让其匹配,从而获取到对象。

2.byName

autowire byName (按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

1、修改bean配置,增加一个属性 autowire="byName"

<bean id="user" class="com.kuang.pojo.User" autowire="byName">
   <property name="str" value="qinjiang"/>
</bean>

2、再次测试,结果依旧成功输出!

3、我们将 cat 的bean id修改为 catXXX

4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

byName:换句话就是set后的单词被隔离出来作为id匹配。比如setcat被独立的id就是cat,在set方法中。

小结:

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

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。

  2. 去spring容器中寻找是否有此字符串名称id的对象。

  3. 如果有,就取出注入;如果没有,就报空指针异常。

3.byType

autowire byType (按类型自动装配)

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

class="com.kuang.pojo.User"

byname是bean的id唯一

id="user"
NoUniqueBeanDefinitionException

测试:

1、将user的bean配置修改一下 : autowire="byType"

2、测试,正常输出

3、在注册一个cat 的bean对象!

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>

<bean id="user" class="com.kuang.pojo.User" autowire="byType">
   <property name="str" value="qinjiang"/>
</bean>

4、测试,报错:NoUniqueBeanDefinitionException

5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

这就是按照类型自动装配!

4.注解自动装配<重点>

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

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

    <context:annotation-config/>
   
</beans>

 

2、开启属性注解支持!

直接加在文件头后面

<context:annotation-config/>

@Autowired

  • @Autowired是按类型自动转配的,不支持id匹配。所以在多个相同类型时候会无法匹配

  • 需要导入 spring-aop的包!<文件头中加上就行>

直接在属性或set方法上运用

不再编写set方法,但是需要注册了bean.

作用:之前有通过名字或者类型的自动装配,将人中的猫狗作为属性装配到容器。但是我们也要去xml文件配置,使用注解避免了配置xml,只需要在人的猫狗属性前加就代表装配进人的bean里了。

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="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>

【小狂神科普时间】

@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
@Nullable    字段标记以后,则此字段可以为空
public People(@Nullable Sreing name){
    this.name=name;
}

当多个相同类型时候,可以组合@Qualifier解决此问题

@Qualifier

  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配

  • @Qualifier不能单独使用。

测试实验步骤:

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

<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在属性上添加Qualifier注解

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

测试,成功输出!

@Autowired
@Qualifier(value = "cat2")
//配套使用,value通过绑定id定位

@Resource

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;

  • 其次再进行默认的byName方式进行装配;

  • 如果以上都不成功,则按byType的方式自动装配。

  • 都不成功,则报异常。

  • java的注解

实体类:

public class User {
   //如果允许对象为null,设置required = false,默认为true
   @Resource(name = "cat2")
   private Cat cat;
   @Resource
   private Dog dog;
   private String str;
}

beans.xml

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>

<bean id="user" class="com.kuang.pojo.User"/>

测试:结果OK

配置文件2:beans.xml , 删掉cat2

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>

实体类上只保留注解

@Resource
private Cat cat;
@Resource
private Dog dog;

结果:OK

结论:先进行byName查找,失败;再进行byType查找,成功。

 

小结

@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。

 

5.注解开发(半注解)

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

1.bean的实现

我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解装配!

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

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

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

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
//此时由于没有id了,所以默认以类名的小写为id
public class User {
   public String name = "秦疆";
}

3、测试

@Test
public void test(){
   ApplicationContext applicationContext =
       new ClassPathXmlApplicationContext("beans.xml");
    //此处强转,也可以通过getBean("user",User.class)实现
   User user = (User) applicationContext.getBean("user");
   System.out.println(user.name);
}

2.属性注入

使用注解注入属性

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

@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
   @Value("秦疆")
   // 相当于配置文件中 <property name="name" 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;
  }
}

注意:一般来说可以通过注解注入属性值,但如果是List数组等九不再适合,复杂的通过xml注入

3.衍生注解

我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!

@Component三个衍生注解

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

  • @Controller:web层

  • @Service:service层

  • @Repository:dao层

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

4.自动装配

在Bean的自动装配已经讲过了,可以回顾

5.作用域

@scope

之前在xml里面是有单例多例模式的,注解也有

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。

  • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

@Controller("user")
@Scope("prototype")
public class User {
   @Value("秦疆")
   public String name;
}

6.小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便

  • 注解不是自己提供的类使用不了,开发简单方便

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

  • xml管理Bean

  • 注解完成属性注入

  • 使用过程中, 可以不用扫描,扫描是为了类上的注解

 

 

<context:annotation-config/>  

作用:

  • 进行注解驱动注册,从而使注解生效

  • 用于**那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册

  • 如果不扫描包,就需要手动配置bean

  • 如果不加注解驱动,则注入的值为null!

6.java类配置(纯注解配置)

纯java注解开发,一点xml都不用

为了分层管理,我们也会创建一个包专门存放配置类

只需要加一个注解就能变成配置类,类似beans<bean的集合>

注解bean==bean

1、实体类User.java

//导入相应的包

//此类注册到容器中
@Component
public class User{
    private String name;
    
    public String getName(){
        return name;
    }
    
    //属性注入值,在set方法这里
    @Value("值")
    public void setName(String name){
        this.name=name;
    }
    
    @Override
    public String toString(){
        return "User{" +
            "name='" +name +'\'' +
            '}';
    }
}

2、配置类Config.java

//也是注册到容器,本质也是基于@Component
//@Configuration表示这是一个配置类,等价于beans.xml,实体类的@Component是xml的beans
@Configuration
//显示扫描包
@ComponentScan("com.kuang")
//引入其他配置类
@Import(Config2.class)
public class Config{
    
    //注册bean,相当于之前的bean
    //方法名==bean的id
    @bean
    public User getUser(){
        return new User();  //返回要注入的对象
    }
}

3、测试类

public class MyTest{
    public static void main(String[] args){
        //通过配置类的class对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class)
        User getUser = (User) context.getBean("getUser");
        System.out.print(getUser.getName);
    }
}

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!

7.动/静态代理模式

代理模式是SpringAOP的底层

1.静态代理

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

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

  • 公共的业务由代理来完成 . 实现了业务的分工 ,

  • 公共业务发生扩展时变得更加集中和方便 .

缺点 :

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

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

2.日志功能

需求:我们需要了解用到了什么方法。控制台输出具体的方法名。

实现思路:

1.抽象类:包含我们业务的操作,增删改查,通常我们可以理解为代理和被代理的共同目标

2.真实类:抽象类的实现,同时也是我们的被代理对象

3.代理类:切入的对象

具体实现

1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!

//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我们需要一个真实对象来完成这些增删改查操作

//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {

   public void add() {
       System.out.println("增加了一个用户");
  }

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

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

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

3、需求来了,现在我们需要增加一个日志功能,怎么实现!

  • 思路1 :在实现类上增加代码 【麻烦!】

  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!

4、设置一个代理类来处理日志!代理角色

/代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
   private UserServiceImpl userService;

   public void setUserService(UserServiceImpl userService) {
       this.userService = userService;
  }

   public void add() {
       log("add");
       userService.add();
  }

   public void delete() {
       log("delete");
       userService.delete();
  }

   public void update() {
       log("update");
       userService.update();
  }

   public void query() {
       log("query");
       userService.query();
  }

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

}

5、测试访问类:

public class Client {
   public static void main(String[] args) {
       //真实业务
       UserServiceImpl userService = new UserServiceImpl();
       //代理类
       UserServiceProxy proxy = new UserServiceProxy();
       //使用代理类实现日志功能!
       proxy.setUserService(userService);

       proxy.add();
  }
}

理解:

静态代理,其实就是将对象的使用率。

要明白,代理类方面,我们new了一个对象,代理类内部创建了一个空的对象,此对象是真实对象的引用,这个空的对象才是真正代理发挥作用的对象,因为new的代理对象传入了一个需要被代理对象的地址。所以原来的空对象可以通过set方法将空代理对象指向传来的被代理对象地址。这样的话代理空对象摇身一变可以直接调用真实对象的方法,也就是原本的业务,我们需要基于业务开发。这么说似乎没什么用,但是我们的目的在于基于业务,高于业务,多出来的部分就是代理加入的。

 

2.动态代理

  • 动态代理的角色和静态代理的一样 .

  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

    • 基于接口的动态代理----JDK动态代理

    • 基于类的动态代理--cglib

    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist

    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

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

核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看

Proxy 生成代理对象。

【InvocationHandler:调用处理程序】

InvocationHandler是一个接口,通过代理实例的名为“调用处理程序"的程序实现。

每个代理实例都有关联的“调用处理程序"。当我们通过代理实例去调用方法时,该方法调用被进行编码且分派到对应的“调用处理程序"的invoke方法。

初步理解:invoke方法是一个方法中间载体。供应调用执行。

 

0.原理分析

动态代理和静态代理有些相似,都是将真实对象(方法的主人)的地址set给空值对象引用。

不同点在于,set进去的真实对象地址会被运用,最终动态生成一个动态代理类对象,

为什么就得需要动态的代理对象呢?我们也可以每次都调用原来静态代理类实现的啊。

原因

我们的目的不是生成代理类,而是对业务进行加工处理,不同类大的业务都不相同,方法名也是不同的,因此,静态代理无法调用到真实的方法,因为静态代理调用的方法都是写死的。

我们的改造目的在于真实方法也可以作为公共方法的参数,那么代理类对象实现的同时,方法也是动态的。

所以,需要分层。

一部分:生成代理类对象

另一部分:执行方法<可以在执行方法中加入前后缀方法,比如日志>

生成代理类对象和静态差不多,也是set,只不过拿到真实对象的地址后,我们需要将这个地址交付给一段程序执行生成代理对象<多此一举的原因在于执行方法片段要受控于代理对象>

我们传入了一个真实对象的地址<唯一参数>

//生成代理类
public Object getProxy(){
   return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                 rent.getClass().getInterfaces(),this);
}

执行方法,我们会通过处理代理程序<自己实现>调用

@Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //前缀
       seeHouse();
       //执行方法
       //核心:本质利用反射实现!
       Object result = method.invoke(rent, args);
       //后缀
       fare();
       return result;
  }

 

1.租房例子

抽象角色和真实角色和之前的一样!

Rent . java 即抽象角色

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

Host . java 即真实角色

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

ProxyInvocationHandler. java 即代理角色

public class ProxyInvocationHandler implements InvocationHandler {
   private Rent rent;

   public void setRent(Rent rent) {
       this.rent = rent;
  }

   //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),this);
  }

   // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
   // 处理代理实例上的方法调用并返回结果
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       seeHouse();
       //核心:本质利用反射实现!
       Object result = method.invoke(rent, args);
       fare();
       return result;
  }

   //看房
   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();
       //代理实例的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setRent(host); //将真实角色放置进去!
       Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
       proxy.rent();
  }

}

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

2.动态代理万能用例

我们来使用动态代理实现代理我们后面写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

我们还可以将前缀和后缀剥离执行代码区。

public class ProxyInvocationHandler implements InvocationHandler {
   private Object target;

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

   //生成代理类
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               target.getClass().getInterfaces(),this);
  }

   // proxy : 代理类
   // method : 代理类的调用处理程序的方法对象.
   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 methodName){
       System.out.println("执行了"+methodName+"方法");
  }

}

测试

public class Test {
   public static void main(String[] args) {
       //真实对象
       UserServiceImpl userService = new UserServiceImpl();
       //代理对象的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setTarget(userService); //设置要代理的对象
       UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
       proxy.delete();
  }
}

 

3.动态代理优点

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

  • 公共的业务由代理来完成 . 实现了业务的分工 ,

  • 公共业务发生扩展时变得更加集中和方便 .

  • 一个动态代理 , 一般代理某一类业务

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

8.AOP学习

相关介绍:

AOP的目标是多出来的业务方法,而不是原本的,因此在思维上偏向”多余“方法。

比如相关的定义:

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知 Log类

(2)Advice(通知):AOP在特定的切入点上执行的增强处理,有 before,after,afterReturning,afterThrowing,around Log类中方法

(3)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

0.Spring实现AOP前提

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

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

2.通过 Spring API 实现

首先编写我们的业务接口和实现类

public interface UserService {

   public void add();

   public void delete();

   public void update();

   public void search();

}
public class UserServiceImpl implements UserService{

   @Override
   public void add() {
       System.out.println("增加用户");
  }

   @Override
   public void delete() {
       System.out.println("删除用户");
  }

   @Override
   public void update() {
       System.out.println("更新用户");
  }

   @Override
   public void search() {
       System.out.println("查询用户");
  }
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强。

至于方法怎么匹配都是spring解决的

public class Log implements MethodBeforeAdvice {

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

最后去spring的文件中注册 , 并实现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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

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

   <!--aop的配置-->
   <aop:config>
       <!--切入点 expression:表达式匹配要执行的方法-->
       <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
       <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
       <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
   </aop:config>

</beans>

测试

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.search();
  }
}

Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .

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

个人理解

在xml中,你只需要告诉spring,你写好的增强类,以及你想增强的作用范围就行了,spring会自动帮我们匹配执行。相当于调用方法。

3.自定义类

1.Spring Aspectj切入点语法定义

在使用spring框架配置AOP的时候,pointcut``"切入点"
例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
 ``整个表达式可以分为五个部分:
 ``1、execution(): 表达式主体。
 ``2、第一个*号:表示返回类型,*号表示所有的类型。 
 ``3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
 ``4、第二个*号:表示类名,*号表示所有的类。
 ``5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

2.实现

自定义类来实现Aop

目标业务类不变依旧是userServiceImpl

第一步 : 写我们自己的一个切入类

public class DiyPointcut {

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

去spring中配置

<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
   <!--第二种方式:使用AOP的标签实现-->
   <aop:aspect ref="diy">
       <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
       <aop:before pointcut-ref="diyPonitcut" method="before"/>
       <aop:after pointcut-ref="diyPonitcut" method="after"/>
   </aop:aspect>
</aop:config>

测试:

 public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
   }
 }

spring API实现的没有对增强部分分离,规则太多

才有了自定义的方式,自定义的基于aop标签

 

4.注解实现

和xml思想没啥区别

只不过翻译为注解,和之前的注解装配差不多思想

也有aop标签的代替

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

 package com.kuang.config;
 ​
 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.kuang.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
   }
 ​
    @After("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
   }
 ​
    @Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
   }
 }

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

 <!--第三种方式:注解实现-->
 <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
 <aop:aspectj-autoproxy/>

aop:aspectj-autoproxy:说明

实现动态代理的方式,spring默认jdk

 通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
 ​
 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy  poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。