美文网首页
Spring 学习笔记

Spring 学习笔记

作者: rayma | 来源:发表于2020-01-11 17:32 被阅读0次

    Spring笔记

    1、Spring概况

    Spring雏形:interface21
    下载地址:https://repo.spring.io/release/org/springframework/spring/

    1.1、Spring的优点

    • ​ 一个轻量级、非入侵式的开源免费框架(容器)
    • ​ 控制反转(IoC)、面向切面编程(AOP)
    • ​ 支持事务处理,可对框架整合

    1.2、Spring的本质

    • 控制反转,依靠依赖注入的方式来实现。
      以一个servcie对象为例,即是service暴露注入接口(构造,set方法),由spring配置对象注入(设置)给该service对象,这样可以做到Service层专注业务,不需要改变自身代码,只需要在调用(注入)的时候改变对象,即可改变service的具体实现,service面向接口编程,由service主动构建对象到被动接收外部注入的对象。同时Spring作为容器,会自己构建对象,这些对象可以作为参数来注入。对象由Spring来创建,管理,装配。

    1.3、Spring Maven 依赖

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

    1.4、组成(七大模块)

    1.4、七大模块.png
    • Spring Core (核心容器):

      核心容器提供spring框架的基础功能。核心容器的主要组件是BeanFactory,它是工厂模式的实

      现。BeanFactory使用控制反转(IoC)模式将应用程序的配置和依赖性规范的应用程序代码分开。

    • Spring Context(上下文):

      spring上下文是一个配置文件,向spring提供上下文信息,spring上下文包括企业服务,例如

      JBDI、EJB、电子邮件、国际化、校验和调度功能。

    • Spring AOP:

    通过配置管理特性、AOP模块直接面向切面的棉城功能集成到了Spring框架中。所以,可以很容

    易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中

    的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务

    管理集成到应用程序中。

    • Spring DAO:

    JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应

    商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量

    (例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

    • Spring ORM:

    Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、

    Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

    • Spring Web:

    Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所

    以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求

    参数绑定到域对象的工作。

    • Spring MVC:

    MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为

    高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

    2、控制反转(IoC)

    2.1、IoC的本质

    ​ IoC (Inversion of Control 控制反转)是一种设计思想,通过描述 (XML或注解) 并通过第三方去生成或获取特定对象的方式,在Spring中实现控制反转的是IoC容器,DI (Dependency Injection 依赖注入)是实现IoC的一种方式。

    2.1_1.png

    IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
    ​ Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。

    2.1_2.png

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

    2.2、如何理解IoC控制反转

    • 谁控制谁?
      传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由IoC容器来控制对象的创建,所以是IoC容器控制对象。
    • 控制什么?
      主要控制外部资源的获取(不只是对象也包括文件等等)
    • 为何是反转?
      有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转,而反转则是 由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受
      依赖对象,所以是反转。
    • 哪些方面反转了?
      所依赖对象的获取被反转了。

    2.3、IoC能做什么

    ​ IoC不是一种技术,只是一种思想,一个重要的面对对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合、难测试,有了IOC容器后,把创建和查找依赖对象的控制权交给容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,也方便测试,利于功能复用,使得程序体系架构变得灵活。

    ​ 其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

    ​ IoC很好的体现了面向对象设计法则之一 ———— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

    2.4、实例与创建对象的方式

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

    以上是传统Java SE程序的写法,修改如下:

    增加一个Userdao的实现类 UserDaoMySqlImpl.java

    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 的实现类 UserDaoOracleImpl.java

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

    ​ 区别:在使用set方法之前,所有需要的对象都是由程序员主动创建,使用set方法注入之后,主动权由程序员转移到了调用者,程序员不用再理会对象的创建,可以更专注业务的实现,耦合性大大降低,这便是IoC 的原型。

    2.4.2、第一个Spring程序

    1、导入Maven依赖项

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

    2、编写User实体类 User.java

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

    3、编写spring配置文件 applicationContext.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 name="user" class="com.woitumi.springtest.User">
            <property name="name" value="woitumi"/>
        </bean>
    </beans>
    

    4、创建测试类

    public class UserTest {
        public static void main(String[] args){
            //解析applicationContext.xml文件,生成并管理相应的Bean对象
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            //getBean的参数为spring配置文件中bean的id
            User user = (User) applicationContext.getBean("user");
            System.out.println(user.toString());
        }
    }
    

    输出


    2.4.2_OUTPUT.png

    扩展
    在上述IoC原型实例中新增加Spring配置文件 applicationContext.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="UserDaoMySqlImpl"/>
        <bean id="OracleImpl" class="UserDaoOracleImpl"/>
        <bean id="ServiceImpl" class="UserServiceImpl">
            <!--注意: 这里的name并不是属性 , 而是set方法名字后半部分 , 首字母小写-->
            <!--引用另外一个bean , 不是用value 而是用 ref-->
            <property name="userDao" ref="OracleImpl"/>
        </bean>
    </beans>
    

    测试

    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
        serviceImpl.getUser();
    }
    

    如此一来就实现了解耦,要实现不同的操作,只需要在xml配置文件中修改配置。

    2.4.3、IoC创建对象的方式

    2.4.3.1、通过无参构造方法

    1、修改 2.4.2第一个spring程序 实例中 User 类,增加User显式无参构造方法,其他不变

    public class User {
        private String name;
        public User() {
            System.out.println("user无参构造方法");
        }
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" + "name='" + name + '\'' + '}';
        }
    }
    
    

    2、测试类

    public class UserTest {
        public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = (User) applicationContext.getBean("user");
            System.out.println(user.toString());
        }
    }
    

    输出

    2.4.3_OUTPUT.png

    结论:在执行 getBean 时,user对象已经创建好。(通过无参构造)

    2.4.3.2、通过有参构造方法

    1、修改 第一个spring程序 实例中 User 类,增加User有参构造方法

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

    2、修改applicationContext.xml配置文件,针对有参构造方法有三种配置方式

     <!-- 第一种根据index参数下标设置 -->  
        <bean name="user" class="com.woitumi.springtest.User">
             <!-- index指构造方法 , 下标从0开始 -->
            <constructor-arg index="0" value="woitumi"/>
        </bean>
    
    <!-- 第二种根据参数名字设置,name属性值必须与set方法后面的名字一样--> 
        <bean name="user" class="com.woitumi.springtest.User">
            <!-- name指参数名 -->
            <constructor-arg name="name" value="woitumi"/>
        </bean>
    
     <!-- 第三种根据参数类型设置,需确保参数的类型全局唯一 -->  
        <bean name="user" class="com.woitumi.springtest.User">
            <constructor-arg type="java.lang.String" value="woitumi"/>
        </bean>
    

    3、测试类

    public class UserTest {
        public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = (User) applicationContext.getBean("user");
            System.out.println(user.toString());
        }
    }
    

    输出


    2.5_OUTPUT.png

    结论:管理的对象在配置文件加载的时候初始化完成。

    2.5、依赖注入(DI)

    概念
    DI (Dependency Injection) 依赖注入:组件之间的依赖关系由容器在运行期决定,也就是说,由容器动态将某个依赖关系注入到组件中。依赖注入并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过注入机制,只需要通过简单的配置,无需任何代码就可以指定目标需要的资源,完成自身的业务逻辑,不需要关心具体的资源来自何处,由谁实现。

    ​IoC在系统运行中,会动态的向某个对象提供它(对象)所需的其他对象,这一点是通过DI(依赖注入)来实现的,比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

    如何理解DI?

    • 谁依赖谁?

      应用程序依赖IoC容器

    • 为什么需要依赖?

      应用程序需要IoC容器来提供对象需要的外部资源

    • 谁注入谁?

      IoC容器注入应用程序的某个对象

    • 注入了什么?

      注入某个对象所需要的外部资源(包括对象、资源、常量数据)

    IoC Service Provider 为被注入对象提供被依赖对象有如下几种方式

    • 构造器注入
    • set方法注入
    • 自动装配注入
    • 注解注入

    2.5.1、构造器注入

    <font color=red>(参考 2.4.3.1 跟 2.4.3.2 实例)</font>

    2.5.2、set方法注入

    要求被注入的属性必须有set方法,set方法的方法名由 set + 属性名首字母大写(如果属性的类型为boolean,则为 is + 属性名首字母大写)。

    实例:
    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> hobby;
        private Map<String, String> card;
        private Set<String> games;
        private String wife;
        private Properties info;
    
        /**省略 getter 跟 setter 方法**/
        
        public void show() {
            StringBuilder sb = new StringBuilder()
                    .append("name = ").append(name).append("\n")
                    .append("address = ").append(address.getAddress()).append("\n")
                    .append("hobby = ").append(hobby).append("\n")
                    .append("card = ").append(card).append("\n")
                    .append("games = ").append(games).append("\n")
                    .append("wife = ").append(wife).append("\n")
                    .append("info = ").append(info).append("\n")
                    .append("books = ");
            if (books != null)
                for (String book : books)
                    sb.append(String.format("《%s》 ", book));
            System.out.println(sb.toString());
        }
    }
    

    1、常量注入

        <bean name="student" class="com.woitumi.springtest.Student">
            <property name="name" value="张三"/>
        </bean>
    

    2、Bean注入 ( ref 引用 )

        <bean id="addr" class="com.woitumi.springtest.Address">
            <property name="address" value="广州"/>
        </bean>
        <bean name="student" class="com.woitumi.springtest.Student">
            <property name="name" value="张三"/>
            <property name="address" ref="addr"/>
        </bean>
    

    3、数组注入

     <property name="books">
          <array>
                <value>Java从入门到夺门而出</value>
                <value>Spring概述</value>
          </array>
     </property>
    

    4、List注入

    <property name="hobbys">
         <list>
              <value>敲代码</value>
              <value>看影视</value>
         </list>
     </property>
    

    5、Map注入

      <property name="card">
           <map>
               <entry key="CBC" value="54221457635512423"/>
               <entry key="BC" value="48526365765123651"/>
            </map>
      </property>
    

    6、Set注入

    <property name="games">
          <set>
              <value>LOL</value>
              <value>csgo</value>
          </set>
    </property>
    

    7、Null注入

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

    8、Properties注入

    <property name="info">
         <props>
              <prop key="学号">20191219001</prop>
              <prop key="性别">男</prop>
         </props>
     </property>
    

    测试类

       @Test
        public void diTest(){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Student student = (Student) context.getBean("student");
            student.show();
        }
    

    输出


    2.5.3_OUTPUT_1.png

    2.5.3、自动装配

    ​ 自动装配(autowire)是使用Spring满足bean依赖的一种方式,可以在applicationContext.xml配置文件的bean标签添加 autowire 属性,使得Spring可以自动在上下文给对象注入属性。

    自动装配做的两件事:

    • 组件扫描 (component scanning):Spring自动发现应用上下文中所创建的bean。
    • 按照规则装配 (autowiring):Spring自动满足bean之间的依赖。(IoC/DI)

    Spring中bean有三种装配机制:

    • 在xml配置文件中显式配置
    • 在java中显式配置
    • 隐式的bean发现机制和自动装配

    自动装配方式:

    • byName
    • byType
    • constructor

    实例环境

    public class Cat {
        public void shout() {
            System.out.println("miao~");
        }
    }
    
    public class Dog {
        public void shout() {
            System.out.println("wang~");
        }
    }
    
    public class User {
        private String name;
        private Cat cat;
        private Dog dog;
        /**省略 getter 跟 setter 方法**/
    }
    
        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User">
            <property name="name" value="woitumi"/>
            <property name="cat" ref="cat"/>
            <property name="dog" ref="dog"/>
        </bean>
    
    public class AutowireTest {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = (User) context.getBean("user");
            System.out.println(String.format("%s的宠物", user.getName()));
            user.getCat().shout();
            user.getDog().shout();
        }
    }
    

    输出


    2.5.3_OUTPUT_2.png
    2.5.3.1、byName

    1、修改applicationContext.xml配置文件中的bean如下:

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" autowire="byName">
            <property name="name" value="woitumi"/>
        </bean>
    

    2、测试输出


    2.5.3.1_OUTPUT_1.png

    3、继以上修改再将cat 的bean id 改为 catXXX

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat1122" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" autowire="byName">
            <property name="name" value="woitumi"/>
        </bean>
    

    4、测试输出 (空指针异常)


    2.5.3.1_OUTPUT_2.png

    使用byName小结:
    <font color="red">byName属性会自动在上下文里找和自己属性对应set方法后边的名字(首字母改为小写)一样的bean id。若bean id 跟set方法后面的名字不一样,则报空指针异常。(id必须唯一)</font>

    2.5.3.2、byType

    1、修改xml配置文件如下

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" autowire="byType">
            <property name="name" value="woitumi"/>
        </bean>
    

    2、测试输出(正常)

    3、继续修改xml配置文件如下:

       <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat" class="com.woitumi.autowire.Cat"/>
        <bean id="cat2" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" autowire="byType">
            <property name="name" value="woitumi"/>
        </bean>
    

    4、测试输出(NoUniqueBeanDefinitionException 异常)

    5、继续修改xml配置文件如下:

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat1122" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" autowire="byType">
            <property name="name" value="woitumi"/>
        </bean>
    

    6、测试输出 (正常)

    使用byType小结:
    <font color="red">byType会自动在上下文里找和自己属性类型相同的bean,类型要唯一,要被注入的bean没有id值也可以,class要唯一。</font>

    2.5.3.3、constructor

    1、修改xml配置文件如下:

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" autowire="constructor">
            <property name="name" value="woitumi"/>
        </bean>
    

    2、修改User.java,添加构造方法,如下:

    public class User {
        private String name;
        private Cat cat;
        private Dog dog;
    
        public User(Cat cat, Dog dog) {
            this.cat = cat;
            this.dog = dog;
        }
        /**省略 getter 跟 setter 方法**/
    }
    

    3、测试输出


    2.5.3.3_OUTPUT.png

    使用constructor小结:
    <font color="red">constructor会尝试把它的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些 bean,否则,会抛出异常。</font>

    2.5.4、注解注入

    以2.5.3实例继续

    2.5.4.1、@Autowired

    @Autowired 注解表示按照类型自动装配(属于Spring规范),不支持id匹配。可以写在字段上,也可以写在setter方法上(使用@Autowired 注解可以省略set方法)。默认情况下要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false,如:@Autowired(required=false) 。

    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/>
    

    3、修改后完整的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/context/spring-context-3.0.xsd">
    
        <context:annotation-config/> <!--开启属性注解支持-->
    
        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat" class="com.woitumi.autowire.Cat"/>
        <bean id="user" class="com.woitumi.autowire.User" >
            <property name="name" value="woitumi"/>
        </bean>
    </beans>
    

    4、修改User.java,将set方法去掉,添加 @Autowired 注解

    public class User {
        private String name;
        @Autowired
        private Cat cat;
        @Autowired(required = false) //如果允许对象为null,可以设置required为false,默认为true
        private Dog dog;
    
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Cat getCat() {
            return cat;
        }
        public Dog getDog() {
            return dog;
        }
    }
    

    5、测试输出


    2.5.4.1_OUTPUT.png
    2.5.4.2、@Qualifier

    @Qualifier 不能单独使用,需跟 @Autowired 配合使用,两者配合使用后可根据 byName 的方式来自动装配。

    1、修改xml配置文件中的bean id不为类的默认名字,如下:

     <bean id="dog11" class="com.woitumi.autowire.Dog"/>
     <bean id="cat22" class="com.woitumi.autowire.Cat"/>
    

    2、在User.java类中添加 @Qualifier 注解

        @Autowired
        @Qualifier(value = "cat22")
        private Cat cat;
        @Autowired(required = false) //如果允许对象为null,可以设置required为false,默认为true
        @Qualifier(value = "dog11")
    

    3、测试输出成功

    2.5.4.3、@Resource

    @Resource 注解(属于J2EE规范)为 @Autowired 和 @Qualifier 的结合版。先尝试以 byName 方式对属性进行查找装配,若不成功,则以 byType 的方式进行装配,若两者都不成功,则报异常。

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

    @Autowired先byType,@Resource先byName。

    1、修改xml配置文件,如下:

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat22" class="com.woitumi.autowire.Cat"/>
        <bean id="cat33" class="com.woitumi.autowire.Cat"/>
    

    2、修改User.java类

        @Resource(name = "cat22")
        private Cat cat;
        @Resource
        private Dog dog;
    

    3、测试输出成功
    4、再修改xml配置文件如下:

        <bean id="dog" class="com.woitumi.autowire.Dog"/>
        <bean id="cat22" class="com.woitumi.autowire.Cat"/>
    

    5、修改User.java类,只保留 @Resource 注解

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

    6、测试输出成功

    2.6、Bean部分配置说明

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

    <!--设置别名:在获取Bean的时候可以使用别名userNew获取-->
    <alias name="user" alias="userNew"/>
    
    <!--bean就是java对象,由Spring创建和管理-->
    <!--
        id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
        如果配置id,又配置了name,那么name是别名
        name可以设置多个别名,可以用逗号,分号,空格隔开
        如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
        class是bean的全限定名=包名+类名
    -->
    <bean id="user" name="user2 u2,u3;u4" class="com.woitumi.springtest.User">
        <property name="name" value="woitumi"/>
    </bean>
    

    2.7、Bean的作用域

    ​ 在Spring中,组成应用程序的主题及有Spring IoC容器所管理的对象,被称为Bean。换言之,Bean就是由IoC容器初始化、装配以及管理的对象。

    Bean的作用域如下表:


    2.7.png

    以上4种作用域中的request跟session只能用在基于web的Spring ApplicationContext环境中。

    2.7.1、Singleton

    • 当一个bean的作用域为Singleton,那么Spring IoC容器中值存在一个共性的bean对象,并且所有对bean的请求只要id跟bean定义的相匹配,就只会返回bean的同一实例。
    • Singleton是单例类型,在创建容器时就同时自动创建了bean的对象,每次获取到的对象都是同一个。
    • Singleton作用域是Spring中的缺省作用域。
    • 较适合单线程使用

    User.java

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

    在xml配置文件中将bean定义成Singleton

      <bean name="user" class="com.woitumi.springtest.User" scope="singleton">
           ... ...
      </bean>
    

    测试

    public class UserTest {
        public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = (User) applicationContext.getBean("user");
            User user2 = (User) applicationContext.getBean("user");
            System.out.println(user == user2);
        }
    }
    

    输出


    2.7.1_OUTPUT.png

    2.7.2、Prototype

    • 当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的 getBean() 方法) 时都会创建一个新的bean实例。
    • Prototype是原型类型,在创建容器时并没有实例化,而是在获取bean的时候才会创建一个对象,每次获取到对象都不是同一个对象。
    • 对有状态的bean应该使用prototype作用域,而对无状态的bean则应使用Singleton作用域。

    在xml配置文件中将bean定义成Prototype

      <bean name="user" class="com.woitumi.springtest.User" scope="prototype">
           ... ...
      </bean>
    
    public class UserTest {
        public static void main(String[] args){
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = (User) applicationContext.getBean("user");
            User user2 = (User) applicationContext.getBean("user");
            System.out.println(user == user2);
        }
    }
    

    测试输出


    2.7.2_OUTPUT.png

    2.7.3、Request

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

    在xml配置文件中将bean定义成Request

      <bean name="user" class="com.woitumi.springtest.User" scope="request">
           ... ...
      </bean>
    

    2.7.4、Session

    • 当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。
    • 该作用域仅在基于web的Spring ApplicationContext环境下有效。

    在xml配置文件中将bean定义成Session

      <bean name="user" class="com.woitumi.springtest.User" scope="session">
           ... ...
      </bean>
    

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

    3、Spring注解式开发

    Spring 在 4.0 版本后引入了全注解开发模式,去除了编写繁琐的配置文件,使用配置类即可进行bean的扫描和装配。

    3.1、简单使用和说明

    步骤

    1、使用注解模式必须先确保 aop包 的引入:


    3.1_aop包引入.png

    2、在配置文件引入context约束:

    <?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">
        
    </beans>
    

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

    <context:component-scan base-package="[包名]"/>
    

    4、使用和说明

    <?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:component-scan base-package="com.woitumi.springtest"/>
    </beans>
    
    /**
    @Component 注解将此类标注为Spring的一个组件,并导入到IoC容器中。
    此处相当于在xml配置文件中 <bean name="cat" class="com.woitumi.springtest.Cat">
    为了更好的分层,Spring可以使用其他三个注解(功能一样),分别是:
        @Controller     对Web层实现类标注
        @Service        对Service层实现类标注
        @Repository     对Dao层实现类标注
    */
    @Component("cat") 
    public class Cat {
        public void shout(){
            System.out.println("miao~");
        }
    }
    
    @Component("dog")
    public class Dog {
        public void shout(){
            System.out.println("wang~");
        }
    }
    
    @Component("user") //@Component("值") 注解将此类标注为Spring的一个组件,值相当于XML配置文件中的name属性。
    @Scope("Singleton") //可用 @Scope 注解标注 Singleton 或 Prototype 模式
    public class User {
       // @Value注解相当于配置文件中 <property name="name" value="woitumi">,使用@Value注解可以不需要set方法。
        @Value("woitumi") 
        private String name;
        private int count;
        @Autowired // @Autowired按照类型自动装配,也可使用 @Resource("cat")指定id 
        private Cat cat;
        @Autowired
        private Dog dog;
    
        public String getName() {
            return name;
        }
    
        public Cat getCat() {
            return cat;
        }
    
        public Dog getDog() {
            return dog;
        }
        
        @Value("2")  // 如果有set方法也可以将 @Value("值") 设在set方法上。
        public void setCount(int count) {
            this.count = count;
        }
    
        public int getCount() {
            return count;
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = (User) context.getBean("user");
            System.out.println(String.format("%s有%d只宠物", user.getName(), user.getCount()));
            user.getCat().shout();
            user.getDog().shout();
        }
    }
    

    输出


    3.1_OUTPUT.png

    XML配置文件与注解装配比较
    1、XML可以适用于任何场景,结构清晰,维护方便。
    2、注解开发简单方便,但在非自己写的类里难以使用。

    3.2、基于 JavaConfig 配置

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

    User.java类

    public class User {
        private String name;
        private Car car;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Car getCar() {
            return car;
        }
    
        public void setCar(Car car) {
            this.car = car;
        }
    }
    

    Car.java类 :

    public class Car {
        private String brand;
        private String type;
        //使用构造方法注入值
        public Car(String brand, String type) {
            this.brand = brand;
            this.type = type;
        }
    
        public String getBrand() {
            return brand;
        }
    
        public void setBrand(String brand) {
            this.brand = brand;
        }
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    }
    

    新建一个config配置包,编写一个MyConfig配置类,如下:

    @Configuration //代表这是配置类
    public class MyConfig {  
        @Bean("myCar")  //@Bean("myCar") 注解会向容器中注册一个叫 myCar 的对象
        public Car getCar() {
            return new Car("保时捷", "911");
        }
        
        //向容器中注册装配一个 user 对象,并通过byType的方式注入car,对象car需要有set方法
        @Bean(value = "user", autowire = Autowire.BY_TYPE)
        public User getUser() {
            User user = new User();
            user.setName("woitumi");
            return user;
        }
    }
    

    写法二:

    public class User {
        @Value("woitumi") //使用  @Value("值") 注解将值注入,相应的字段可以不需要set方法
        private String name;
        private Car car;
    
        public String getName() {
            return name;
        }
    
        public Car getCar() {
            return car;
        }
    
        public void setCar(Car car) {
            this.car = car;
        }
    }
    
    public class Car {
        @Value("保时捷") //使用  @Value("值") 注解将值注入,可以不需要set方法
        private String brand;
        @Value("911")
        private String type;
        
        public String getBrand() {
            return brand;
        }
        
        public String getType() {
            return type;
        } 
    }
    
    @Configuration //@Configuration 代表这是配置类
    // @ComponentScan("com.woitumi.springtest") //指定扫描包 (了解可以这么用)
    // @Import(MyConfig2.class) //融合多个配置类
    public class MyConfig {
        @Bean("myCar")  //@Bean("myCar") 注解会向容器中注册一个叫 myCar 的对象
        public Car getCar() {
            return new Car();
        }
     //向容器中注册装配一个 user 对象,并通过byType的方式注入car,对象car需要有set方法
        @Bean(value = "user", autowire = Autowire.BY_TYPE)
        public User getUser() {
            return new User();
        }
    }
    

    测试类

        @org.junit.Test
        public void test() {
            ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
            User user = context.getBean("user", User.class);
            System.out.println(String.format("%s的车是%s%s",
                    user.getName(),
                    user.getCar().getBrand(),
                    user.getCar().getType()
            ));
        }
    

    输出


    3.2_OUTPUT.png

    4、代理模式

    ​ 为什么要学习代理模式?因为AOP的底层机制是动态代理。

    ​ 代理模式作为23种经典设计模式之一,其比较官方的定义为“为其他对象提供一种代理以控制对这个对象的访问”,简单点说就是,之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。

    它的设计思路是:定义一个抽象角色,让代理角色和真实角色分别去实现它。

    代理模式的核心作用

    • 符合开闭原则,不用修改被代理者的任何代码就能扩展新的功能。
    • 方便项目的扩展和维护。

    角色分类

    • 抽象角色:一般使用接口或抽象类来实现。
    • 真实角色:被代理角色,实现抽象角色,定义真实角色所需要的业务逻辑,供代理角色调用。
    • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑来实现抽象方法,并可在前后添加新操作。
    • 客户:使用代理角色来实现一些操作。

    代理模式分为

    • 静态代理
    • 动态代理

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

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

    实例二

    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、如果需要增加一个日志功能,如何实现?
    思路一:在实现类上增加代码。(麻烦)
    思路二:使用代理,在不改变原有业务逻辑的情况下实现日志功能

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

    4.2、动态代理

    动态代理的特点

    • 在不修改原有类的基础上,为原有类添加新的功能。
    • 不需要依赖某个具体业务。

    动态代理的好处

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

    动态代理分为

    • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,需要指定一个类加载器,然后生成的代理对象实现类的接口或类的类型,接着处理额外功能。
    • CGLib动态代理:动态利用sam的开源包,对代理对象的Class文件加载进来,通过修改其字节码生成的子类来出来,CGLib是基于继承父类生成的代理类。

    JDK代理和CGLib代理的区别

    • JDK动态代理的被代理者必须实现任何接口。
    • CGLib动态代理不用实现接口,主要对指定的类生成一个子类,覆盖其中的方法,添加额外的功能,通过继承实现。所以该类方法不能用final来修饰。

    动态代理的实现步骤

    • 代理类需要实现 InvocationHandler 接口。
    • 实现 invoke 方法。
    • 通过 Proxy 类的 newProxyInstance 方法来创建代理对象

    JDK动态代理实例一 :

    以 4.1租房实例 继续

    //抽象角色:租房
    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();
        }
    }
    

    JDK动态代理实例二:

    //用户管理接口
    public interface UserManager {
        void addUser(String name, String password);
        void delUser(String name);
    }
    
     //用户管理实现类,实现用户管理接口
    public class UserManagerImpl implements UserManager {
        public void addUser(String name, String password) {
            System.out.println("addUser:" + name + ":" + password);
        }
    
        public void delUser(String name) {
            System.out.println("delUser:" + name);
        }
    }
    
    //JDK动态代理实现InvocationHandler接口
    public class JdkProxy implements InvocationHandler {
        private Object target;
        //定义获取代理对象方法
        public Object getJdkProxy(Object object) {
            target = object;
            //JDK动态代理只能针对实现了接口的类进行代理
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                          target.getClass().getInterfaces(), this);
        }
        
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("JDK动态代理,重写invoke方法调用被代理者的方法");
            Object result = method.invoke(target,args);
            System.out.println("JDK动态代理,可以执行附加操作");
            return result;
        }
    }
    
    
        @Test
        public void JdkProxy() {
            JdkProxy jdkProxy = new JdkProxy(); //实例化JdkProxy对象
            //获取代理对象
            UserManager userManager = (UserManager) jdkProxy.getJdkProxy(new UserManagerImpl());
            userManager.addUser("woitumi", "wanantumi");
     }
    
    4.2_OUTPUT_1.png

    CGLib动态代理实例

    首先需要导入asm跟cglib的 Maven依赖

            <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
            <dependency>
                <groupId>org.ow2.asm</groupId>
                <artifactId>asm</artifactId>
                <version>7.2</version>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/cglib/cglib -->
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>3.3.0</version>
            </dependency>
    
    public class CglibProxy implements MethodInterceptor {
        private Object target;
        //重写拦截方法
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
            throws Throwable {
            System.out.println("CGLib动态代理,重写invoke方法调用被代理者的方法");
            Object result = method.invoke(target, objects);
            System.out.println("CGLib动态代理,可以执行附加操作");
            return result;
        }
    
        //定义获取代理对象方法
        public Object getCglibProxy(Object object) {
            target = object;
            Enhancer enhancer = new Enhancer();
            //设置父类,Cglig是针对指定的类生成一个子类,所以需要指定父类
            enhancer.setSuperclass(object.getClass());
            enhancer.setCallback(this); //设置回调
            return enhancer.create(); //创建并返回代理对象
        }
    }
    
        @Test
        public void CglibProxy() {
            CglibProxy cglibProxy = new CglibProxy();
            UserManager userManager = (UserManager) cglibProxy.getCglibProxy(new UserManagerImpl());
            userManager.delUser("张三");
        }
    
    4.2_OUTPUT_2.png

    扩展
    实现JDK动态代理的工具类

    public class ProxyInvocationHandler implements InvocationHandler { 
        private Object object;
        /**
         * 创建代理对象
         * @param object 被代理者
         * @return 代理者
         */
        public Object createProxy(Object object) {
            this.object = object;
            //Proxy.newProxyInstance创建动态代理的对象,传入被代理对象的类加载器,接口,InvocationHandler对象
            //注意 Proxy 是 java.lang.reflect.Proxy 包下的 Proxy
            return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                                          object.getClass().getInterfaces(), this);
        }
    
        //调用被代理者方法,同时可以添加新操作
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //TODO 调用被代理者的方法
            Object result = method.invoke(object, args);
            //TODO 可添加新的操作
            return result;
        }
    }
    
    

    使用(如上述租房实例)

        @Test
        public void rentHost() {
            Host host = new Host();
            Rent proxy = (Rent) new ProxyInvocationHandler().createProxy(host);
            proxy.rent();
        }
    

    5、面向切面编程(AOP)

    5.1、AOP概述

    ​ AOP(Aspect Oriented Programming)意为:面向切面编程,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置上。面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码。

    AOP把软件的功能模块分为两个部分:

    • 核心关注点
    • 横切关注点

    ​ 业务处理的主要功能为核心关注点,需要拓展的功能为横切关注点,利用AOP可以对业务逻辑的各个关注点进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。

    5.png

    ​ 例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中,以下为用户登录功能切入到业务流程示意图:

    5_1.png

    AOP相关概念

    • 切面(Aspect):横切关注点,被模块化的特殊对象,是一个类。
    • 连接点(Joinpoint):程序执行过程中的某一行为。
    • 通知(Advice):切面 对于某个 连接点 所产生的动作。类中的方法。
    • 切入点(Pointcut):匹配连接点,在AOP中通知和一个切入点表达式关联。
    • 目标对象(Target Object):被一个或多个切面所通知的对象。
    • AOP代理(AOP Proxy):向目标对象应用通知之后创建的对象。
    5.1_2.png

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

    5.1_3.png
    • 前置通知(Before advice):在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛异常。
    • 正常返回通知(After returning advice):在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
    • 异常返回通知(After throwing advice):在连接点抛出异常后执行。
    • 返回通知(After finally advice):在连接点执行完成后执行,不管是正常还是抛异常,都会返回通知中的内容。
    • 环绕通知(Around advice):环绕通知围绕在连接点前后。这是一个强大的通知类型,能在方法调用前后自定义一些操作,环绕通知还需要负责决定是继续处理连接点(调用ProceedingJoinPoint的proceed方法)还是中断执行。

    5.2、OOP与AOP对比理解

    OOP (Object Oriented Programming) 面向对象编程,AOP (Aspect Oriented Programming) 面向切面编程。
    纵向关系OOP,横向角度AOP

    举个小例子:

    设计一个日志打印模块。按 OOP 思想,我们会设计一个打印日志 LogUtils 类,然后在需要打印的地方引用即可。

    public class ClassA {
        private void initView() {
            Log.d(TAG, "onInitView");
        }
    }
    
    public class ClassB {
        private void onDataComplete(Bean bean) {
            Log.d(TAG, bean.attribute);
        }
    }
    
    public class ClassC {
        private void onError() {
            Log.e(TAG, "onError");
        }
    }
    

    看起来没有任何问题是吧?

    但是这个类是横跨并嵌入众多模块里的,在各个模块里分散得很厉害,到处都能见到。从对象组织角度来讲,我们一般采用的分类方法都是使用类似生物学分类的方法,以「继承」关系为主线,我们称之为纵向,也就是 OOP。设计时只使用 OOP思想可能会带来两个问题:

    1. 对象设计的时候一般都是纵向思维,如果这个时候考虑这些不同类对象的共性,不仅会增加设计的难度和复杂性,还会造成类的接口过多而难以维护(共性越多,意味着接口契约越多)。
    2. 需要对现有的对象 动态增加 某种行为或责任时非常困难。

    而AOP就可以很好地解决以上的问题,怎么做到的?除了这种纵向分类之外,我们从横向的角度去观察这些对象,无需再去到处调用 Log 打印日志了,声明哪些地方需要打印日志,这个地方就是一个切面,AOP 会在适当的时机把打印语句插进切面。

    // 只需要声明哪些方法需要打印 log,打印什么内容
    public class ClassA {
        @Log(msg = "onInitView")
        private void initView() {
        }
    }
    
    public class ClassB {
        @Log(msg = "bean.attribute")
        private void onDataComplete(Bean bean) {
        }
    }
    
    public class ClassC {
        @Log(msg = "onError")
        private void onError() {
        }
    }
    

    ​ 如果说 OOP 是把问题划分到单个模块的话,那么 AOP 就是把涉及到众多模块的某一类问题进行统一管理。AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。利用 AOP 思想,这样对业务逻辑的各个部分进行了隔离,从而降低业务逻辑各部分之间的耦合,提高程序的可重用性,提高开发效率。


    5.2.png

    OOP 与 AOP 的区别

    • 面向目标不同:OOP面向名词领域,AOP面向动词领域。
    • 思想结构不同:OOP是纵向结构,AOP是横向结构。
    • 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程中的某个步骤或阶段。

    OOP 与 AOP 两者是一个相互补充和完善的关系。

    5.3、使用

    使用AOP织入,需要导入 aspectjweaver 依赖包

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

    在applicationContext.xml文件中配置AOP需要引入AOP约束,如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop.xsd">
    </beans>
    
    5.3.1、通过Spring API实现
    //业务接口
    public interface DataManager {
        public void add();
        public void delete();
        public void update();
        public void query();
    }
    
    //业务实现类
    public class DataManagerImpl implements DataManager {
        public void add() {
            System.out.println("添加");
        }
    
        public void delete() {
            System.out.println("删除");
        }
    
        public void update() {
            System.out.println("更新");
        }
    
        public void query() {
            System.out.println("查询");
        }
    }
    
    //前置增强类
    public class BeforeLog implements MethodBeforeAdvice {
        /**
         * @param method 要执行的目标对象的方法
         * @param objects 被调用的方法的参数
         * @param target 目标对象
         */
        public void before(Method method, Object[] objects, Object target) throws Throwable {
            System.out.println(String.format("执行 %s 类的 %s 方法前的日志",
                    target.getClass().getName(),
                    method.getName()
            ));
        }
    }
    
    //后置增强类
    public class AfterLog implements AfterReturningAdvice {
        /**
         * @param returnValue 返回值
         * @param method 被调用的方法
         * @param objects 被调用方法的对象参数
         * @param target 被调用的目标对象
         */
        public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) 
            throws Throwable {
            System.out.println(String.format("执行 %s 类的 %s 方法后的日志,返回值为 %s",
                    target.getClass().getName(),
                    method.getName(),
                    returnValue
            ));
        }
    }
    
    <?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
           https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!-- 注册bean -->
        <bean id="dataManager" class="com.woitumi.aoptest.DataManagerImpl"/>
        <bean id="beforeLog" class="com.woitumi.aoptest.BeforeLog"/>
        <bean id="afterLog" class="com.woitumi.aoptest.AfterLog"/>
        <!-- aop配置 -->
        <aop:config>
            <!-- 切入点,expression 表达式匹配要执行的方法 -->
            <aop:pointcut id="pointcut" expression="execution(* com.woitumi.aoptest.DataManagerImpl.*(..))"/>
            <!-- 执行环绕,advice-ref 表示执行方法,pointcut-ref 表示切入点 -->
            <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
            <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
        </aop:config>
    
    </beans>
    
       @Test
        public void aopTest() {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            DataManager manager = (DataManager) context.getBean("dataManager");
            manager.add();
        }
    
    5.3.2_PUTPUT.png

    附录:execution 表达式含义:

    execution(* com.woitumi.aoptest.DataManagerImpl.*(..))
    <!--
    1、execution() : 表达式主体
    2、第一个 * 号: 表示返回类型,* 号表示返回所有类型
    3、包名: 表示需要拦截的包名
    4、*(..) :* 号表示所有方法,括号内的点表示参数,两个点表示所有参数。
    -->
    
    5.3.2、自定义类实现AOP

    继上述例子 业务接口跟业务实现类不变,修改增加

    //自定义切入类
    public class CustomPointcut {
        public void before() {
            System.out.println("before");
        }
    
        public void after() {
            System.out.println("after");
        }
    }
    
    <bean id="custom" class="com.woitumi.aoptest.CustomPointcut"/>
     <aop:config>
        <!-- 使用AOP标签实现 -->
        <aop:aspect ref="custom">
            <aop:pointcut id="customPointcut" expression="execution(*com.woitumi.aoptest.DataManagerImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="customPointcut"/>
            <aop:after method="after" pointcut-ref="customPointcut"/>
        </aop:aspect>
    </aop:config>
    
    5.3.2_OUTPUT_2.png
    5.3.3、使用注解实现AOP

    继以上实例修改增加

    @Aspect
    public class AnnotationPointcut {
        @Before("execution(* com.woitumi.aoptest.DataManagerImpl.*(..))")
        public void before() {
            System.out.println("before");
        }
    
        @After("execution(* com.woitumi.aoptest.DataManagerImpl.*(..))")
        public void after() {
            System.out.println("after");
        }
    
        @Around("execution(* com.woitumi.aoptest.DataManagerImpl.*(..))")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("环绕前");
            System.out.println("签名:" + joinPoint.getSignature());
            //执行目标方法proceed
            Object proceed = joinPoint.proceed();
            System.out.println("环绕后");
            System.out.println(proceed);
        }
    }
    
     <bean id="annotationPointcut" class="com.woitumi.aoptest.AnnotationPointcut"/>
     <aop:aspectj-autoproxy/> 
    <!-- 开启注解支持 
    通过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动态代理。
    -->
    
    5.3.3_OUTPUT.png

    6、整合MyBatis

    (后续补充)

    7、声明式事务

    (后续补充)

    相关文章

      网友评论

          本文标题:Spring 学习笔记

          本文链接:https://www.haomeiwen.com/subject/dkqbactx.html