查看原文
其他

IOC与AOP—>Spring

树莓蛋黄派 Java规途 2023-07-04


  • 一、Spring简介

    • (一)Spring的介绍及需要的依赖

    • (二)Spring的优点

  • 二、 Spring的组成及拓展

    • (一)什么是Spring Boot

    • (二)什么是Spring Cloud

  • 三、IOC(控制反转)

    • (一)IOC理论推导

    • (二)IOC的本质

    • (三)IOC实现

    • (四)手动封装简易IOC

    • (五)Spring IOC核心技术

    • (六)Spring IOC核心配置文件加载

  • 四、HelloSpring示例

  • 五、IOC创建对象的方式

    • (一)使用构造器来实例化

    • (二)使用静态工厂方法来实例化

    • (三)使用实例化工厂来实例化对象

    • (四)三种IOC创建对象(实例化对象的方式比较)

  • 六、Spring配置

    • (一)别名

    • (二)Bean的配置

    • (三)import

  • 七、DI依赖注入

    • (一)构造器注入

    • (二)通过set方式注入

    • (三)通过其他第三方方式注入

    • (四)Bean的作用域

  • 八、Bean的自动装配与生命周期

    • (一)测试(使用xml显式装配)

    • (二)通过ByName的形式装配

    • (三)通过ByType的形式装配

    • (四)使用注解实现自动装配

    • (五)Bean的生命周期

  • 九、使用注解开发

    • (一)常用注解

    • (二)属性注入

    • (三)SpringIOC扫描器

    • (四)作用域

    • (五)小结

  • 十、使用Java的方式配置Spring

  • 十一、两大代理模式

    • (一)代理模式

    • (二)静态代理模式

    • (三)动态代理模式

  • 十二、AOP面向切面编程

    • (一)什么是AOP

    • (二)AOP在Spring中的作用与特点

    • (三)使用Spring实现AOP

  • 往期内容回顾


Spring框架

一、Spring简介

(一)Spring的介绍及需要的依赖

  • Spring框架是一个开放源代码的J2EE应用程序框架,由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson/1423612)发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。
  • Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。
  • Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。
  • 因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。
  • Spring框架主要由七部分组成,分别是 「Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。」
  • Spring理念:使现有技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架
  • Spring官网:「https://spring.io/」
  • Spring的github下载地址:「https://github.com/spring-projects/spring-framework/archive/v5.0.2.RELEASE.zip」

「集成的第三方框架」

  • MyBatis、Hibernate(持久层框架)
  • Spring MVC
  • Spring Security权限
  • Quartz时钟框架(定时任务处理)

「自带服务」

  • Mail邮件发送
  • 定时任务处理,定时调度(定时短信、定时任务)
  • 消息处理(异步处理)

SSH框架:structs+Spring+Hibernate

SSM框架:SpringMVC+Spring+MyBatis

后续会使用到的Spring依赖(需要下载Sring web mvc)

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

后续可能会与数据库发生关系,需要导入JDBC的包

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

(二)Spring的优点

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

Spring就是一个轻量级的控制反转(IOC)和面向切面编程的框架。

二、 Spring的组成及拓展

Spring总共大约有20个模块,由1300多个文件组成,这些组件被分别整合在核心容器(Core Container)、AOP(Aspect Oriented Programming)和设备文件(Instrmentation)、数据访问及集成(Data Access)、Web、报文发送(Messaging)、测试6个模块集合中。

  1. 核心容器:Spring-beans和Spring-core模块是Spring框架的核心模块,包含控制反转(inversion of Control)和依赖注入(Dependency injection DI),核心容器提供Spring框架的基本功能,核心容器的主要组件是BeanFactory,工厂模式的实现,BeanFactory使用控制反转(IOC)思想将应用程序的配置和依赖性规范与实际的应用代码分开。

    Spring上下文Context:Spring上下文是一个配置文件,向Spring框架提供上下文信息,Spring上下文包括企业服务、例如:JNDI、EJB、电子邮件、国际化、检验和调度功能。

    Spring-Experssion模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等,它的语法类似于传统的EL,但是提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。

  2. Spring-AOP:Spring-AOP是Spring的另一个核心模块,在Spring中,它是以JVM的动态代理技术为基础,然后设计了一系列的AOP模板的实现,比如前置通知、返回通知、异常通知等,通过其配置管理特性,Spring-AOP模块直接将面向切面编程的功能集成到了Spring框架中,所以可以很容易地使用Spring框架管理的任何对象支持AOP。

  3. Spring Data Access(数据访问):由Spring-jdbc、Spring-tx、Spring-orm、Spring-jms和Spring-oxm5个模块组成,Spring-jdbc模块是Spring提供的JDBC抽象框架的主要实现,主要用于简化Spring-jdbc。

    Spring-tx模块是SpringJDBC事务控制模块,使用Spring框架,它对事务做了很好的封装,通过它的AOP配置,可以灵活的配置在任何一层。

    Spring-Orm模块是以ORM框架支持的,主要集成Hibernate、Java Persistence API(JPA)和Java Data Objects(JDO),用于资源管理、数据访问对象(Dao)的实现和事务策略。

    Spring-jms模块能够发送和接受信息(Java Messaging Service)

    SPring Oxm模块主要是提供一个抽象层以支撑OXM(Object-to-XML-Mapping,它是一个O/M-mapper,将Java对象映射成XML数据,或者将XML数据映射成Java对象)。例如:JAXB,Castor,XML Beans,JiBX和XStream等。

  4. Web模块:由Spring-Web、Spring-webmvc、Spring-websocket和Spring-webmvc-portlet4个模块组成,Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文,Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  5. 报文发送;即Spring-messaging模块,是Spring4新加入的一个模块,主要职责是为Spring框架集成一些基础的报文传送应用。

  6. 单元测试:即Spring-test模块,Spring-test模块主要为测试提供支持。

「拓展:学习路线」

现代化的开发都是基于Spring的开发

(一)什么是Spring Boot

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

(二)什么是Spring Cloud

  • Spring Cloud是基于Spring Boot实现的

现在大多数的公司都采用SpringBoot来实现快速开发,学习SpringBoot的前提是完全掌握Spring和SrpingMVC。

「弊端:Spring的发展,违背了原来的理念,配置十分繁琐,人称“配置地狱”」

三、IOC(控制反转)

(一)IOC理论推导

之前的架构

  • UserDao接口
  • UserDaoImpl实现类
  • UserService业务接口
  • UserServiceImpl业务实现类

在之前的业务中,用户的需求可能会影响原来的代码,我们需要根据用户的需求修改源代码。如果代码量十分大,修改一次的成本代码昂贵。

例如,我们使用了一个set接口来实现(已经发生了革命性变化)

   private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

之前程序主动创建对象,控制权在程序设计者的手上,使用了set注入,程序不再具有主动性,而是变成了被动的接受对象。

这种思想从本质上解决了问题,不需要再去管理对象的创建了。系统的耦合性大大降低,可以更加专注在业务的实现上。这是IOC的原型。

如图所示:

(二)IOC的本质

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

「原先高度耦合的对象」

「解耦的过程」

「理想的系统」

(三)IOC实现

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

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

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

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

(四)手动封装简易IOC

  • 定义一个Bean属性对象(用于接收配置文件中的bean标签的id和class属性值)
/**
 * @author lambda
 * Bean属性对象,存储bean标签对应的id和class
 */

public class MyBean {
    private String id;
    private String clazz;

    public MyBean(String id, String clazz) {
        this.id = id;
        this.clazz = clazz;
    }

    public MyBean() {
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }
}

  • 引入依赖(由于需要解析xml文件,所以需要dom4j等相关依赖)
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/jaxen/jaxen
 XPath-->

<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>

  • 创建对应的实体类和接口(需要配置的bean对象)
public interface UserDao {
    /**
     * Gets names.
     */

    void getNames();
}
public class UserDaoImpl implements UserDao {
    @Override
    public void getNames() {
        System.out.println("我实现了UserDao接口");
    }
}

public interface UserService {

    /**
     * Gets names.
     */

    void getNames();
}
public class UserServiceImpl implements UserService{
    @Override
    public void getNames() {
        System.out.println("我实现了UserService接口");
    }
}
  • 创建xml配置文件(配置对象的)
<?xml version="1.0" encoding="UTF-8" ?>
<!--自定义的bean标签-->
<beans>
    <!--设置JavaBean对应的bean标签-->
    <bean id="userDaoImpl" class="org.example.dao.UserDaoImpl"/>
    <bean id="userServiceImpl" class="org.example.service.UserServiceImpl"/>

</beans>
  • 创建一个工厂接口(用来通过id获取bean对象的)
public interface MyFactory {

    /**
     * Gets bean.
     *
     * @param id the id
     * @return the bean
     */

    Object getBean(String id);

    /**
     * Gets bean.
     *
     * @param id    the id
     * @param clazz the clazz
     * @return the bean
     */

    Object getBean(String id,Class clazz);
}
  • 创建工厂实现类来实现获取bean对象的方法
public class MyClassPathForMyFactory implements MyFactory {   
/**定义一个List集合,存放从xml文件中读取到的bean标签的配置信息*/
private List<MyBean> beanList=new ArrayList<>();    
/**定义一个map集合,存放已经实例化好的对象,可以通过id获取到对应的对象*/  
private Map<String,Object> beanMap=new HashMap<>();  
/**获取到了带参的构造器*/  
public MyClassPathForMyFactory(String fileName) {     
//需要解析获取到的xml文件,返回List集合       
this.parseXml(fileName);      
//通过反射得到实例化对象,放置在Map集合中 
this.instanceBean();  
    }   
    /**通过`反射来获取实例化对象*/ 
    private void instanceBean() {   
    //1.判断对象集合List是否为空,如果不为空,则遍历(获取对应的id和class属性)        if (beanList!=null && beanList.size()>0){    
    for (MyBean myBean : beanList) {    
      String id = myBean.getId();     
      String clazz = myBean.getClazz();    
      //2.通过类的全路径名,反射得到实例化对象  
      try {           
      Object o = Class.forName(clazz).newInstance();                          //3.将id和实例化好的bean对象存入,Map集合中即可   
      beanMap.put(id,o);         
      } catch (InstantiationException e) {  
      e.printStackTrace();         
      } catch (IllegalAccessException e) {    
      e.printStackTrace();     
      } catch (ClassNotFoundException e) {    
      e.printStackTrace();     
            } 
          } 
        }
      }   
      /**创建一个解析xml文件的方法,通过dom4j来返回一个存有id和class的list集合*/    private void parseXml(String fileName) {     
      //1.获取解析器    
      SAXReader saxReader = new SAXReader();   
      //2.获取配置文件的url(通过类加载器获取资源文件的方式得到url) 
      URL resource = this.getClass().getClassLoader().getResource(fileName);       
      //3.通过解析器解析配置文件(xml文件)(获取到一个文档对象在内存中的)              try {         
      Document document = saxReader.read(resource);   
      //4.通过xPath语法,获取beans标签下的所有bean标签(这表示解析beans下的所有bean)          
      XPath xPath = document.createXPath("beans/bean");  
      //5.通过指定的解析语法解析文档对象,返回一个元素集合(解析语法是选择要解析的结点,传入文档对象)   
      List<Element> elementList = xPath.selectNodes(document);                //6.判断元素集合是否为空   
      if (elementList!=null && elementList.size()>0){      
      //7.如果元素集合不为空,遍历集合         
      for (Element element : elementList) {      
      //8.获取bean标签元素中的所有的属性id,class(通过attributeVale并传入属性名,返回属性值)           
      String id = element.attributeValue("id");
      String clazz = element.attributeValue("class");  
      //9.将获取MyBean对象,将id和class设置到对象中,再将该对象存入List集合中          MyBean myBean = new MyBean(id,clazz);      
      beanList.add(myBean);         
              }         
            }  
          } catch (DocumentException e) {      
          e.printStackTrace();    
          }
        }   
        /**通过id获取对应Map对象中的value值,value表示的就是已经实例化好的对象*/    @Override   
        public Object getBean(String id) {    
        return beanMap.get(id);  
        }
      }

此类用于模拟Spring的实现

  1. 通过带参构造器得到配置文件
  2. 通过dom4j解析配置文件,得到list集合(存放bean标签的id和class属性)
  3. 通过反射得到对应的实例化对象,放置在map对象中(遍历list集合,通过获取对应的class属性,利用class.forname(class).newinstance获取反射对象)
  4. 通过id属性获取指定的实例化对象

注意;首先是解析对应的xml文件得到document对象,再将document对象按照指定的语法解析,确定解析的内容和解析的对象(即为document),之后将获取到的结果放入element集合中,之后判断element集合是否为空,不为空的话可以进行遍历获取对应的id和class属性(class也就是类的全类名),之后在创建对应的MyBean对象赋予相应的属性值,并最后添加到MyBean的List集合中。其次,需要对List集合进行遍历获取对应的id和属性值,并通过反射得到实例化的对象,并将id和对应的实例化对象添加到Map集合中。最后通过调用getBean方法获取对应的实例。

另一种实现方式;

public class Packaging implements MyFactory {  
/**定义一个map集合,存放已经实例化好的对象,可以通过id获取到对应的对象*/  
private Map<String,Object> beanMap=new HashMap<>();  
/**获取到了带参的构造器*/  
public Packaging(String fileName) {     
//需要解析获取到的xml文件,返回List集合     
this.parseXml(fileName);  
  }    
  /**创建一个解析xml文件的方法,通过dom4j来返回一个存有id和class的list集合*/    private void parseXml(String fileName) {      
  //1.获取解析器       
  SAXReader saxReader = new SAXReader();       
  //2.获取配置文件的url(通过类加载器获取资源文件的方式得到url)  
  URL resource = this.getClass().getClassLoader().getResource(fileName);   //3.通过解析器解析配置文件(xml文件)(获取到一个文档对象在内存中的)    
  try {         
  Document document = saxReader.read(resource);       
  //4.通过xPath语法,获取beans标签下的所有bean标签(这表示解析beans下的所有bean)     XPath xPath = document.createXPath("beans/bean");       
  //5.通过指定的解析语法解析文档对象,返回一个元素集合(解析语法是选择要解析的结点,传入文档对象)          
  List<Element> elementList = xPath.selectNodes(document);  
  //6.判断元素集合是否为空        
  if (elementList!=null && elementList.size()>0){  
  //7.如果元素集合不为空,遍历集合         
  for (Element element : elementList) {    
  //8.获取bean标签元素中的所有的属性id,class(通过attributeVale并传入属性名,返回属性值)                
  String id = element.attributeValue("id"); 
  String clazz = element.attributeValue("class");     
  //通过对应的clazz类的全类名,获取对应的反射对象,再将其添加到map集合中             Object o = Class.forName(clazz).newInstance();   
  beanMap.put(id,o);      
          }           
      }      
  } catch (DocumentException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {      
  e.printStackTrace();  
      }
    }   
    /**通过id获取对应Map对象中的value值,value表示的就是已经实例化好的对象*/ 
    @Override  
    public Object getBean(String id) {   
    return beanMap.get(id);   
    }
}

此处去除了存放MyBean对象的List集合,在遍历从文档中获取到的元素集合的时候就利用id和class属性使用反射创建对应全类名下的实例对象。同样存入Map集合中,便于通过id获取对应的实例对象。

(五)Spring IOC核心技术

  1. 工厂设计模式(简单工厂、工厂方法、抽象工厂)
  2. XML解析(Dom4j)
  3. 反射技术(实例化对象,反射获取方法…)
  4. 策略模式(加载资源)
  5. 单例模式

(六)Spring IOC核心配置文件加载

核心配置文件内容

<?xml version="1.0" encoding="UTF-8" ?>
<!--自定义的bean标签-->
<beans> 
  <!--设置JavaBean对应的bean标签-->
  <bean id="userDaoImpl" class="org.example.dao.UserDaoImpl"/>   
  <bean id="userServiceImpl" class="org.example.service.UserServiceImpl"/>
</beans>
  • 根据相对路径加载(使用ClassPathXmlApplicationContext来实现)
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  • 根据绝对路径加载(使用FileSystemXmlApplicationContext类来实现)
   ApplicationContext context = new FileSystemXmlApplicationContext("/home/lambda/Spring知识点/Spring-Study/Spring-AOP2/src/main/resources/applicationContext.xml");
  • 复杂文件的加载

Spring框架启动时会加载多个配置文件到环境中,对于比较复杂的项目,可能对应的配置文件有多个,项目在启动部署时会将多个配置文件同时加载进来。

例如:此处有dao.xml和service.xml文件

1. 可变参数,传入多个文件名(ClassPathXmlApplicationContext可以传入可变参数)
 ApplicationContext context = new ClassPathXmlApplicationContext("dao.xml”,“service.xml");
  1. 通过总的配置文件import传入其他的配置文件(后续详述)

Spring.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                        https://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <bean id="Hello" class="com.apache.pojo.Hello">   
    <property name="str" value="name"/>  
  </bean>     
  <import resource="service.xml"/>  
  <import resource="dao.xml"/>
</beans>

四、HelloSpring示例

Spring ioc的网址「https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core」

提供给ApplicationContext构造函数的一个或多个位置路径是资源字符串,它允许容器从各种外部资源(例如本地文件系统、Java 等)加载配置元数据CLASSPATH

需要用到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        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="..." class="...">     
  <!-- collaborators and configuration for this bean go here -->  
  </bean>   
  <bean id="..." class="...">     
    <!-- collaborators and configuration for this bean go here -->  
  </bean>  
  <!-- more bean definitions go here -->
</beans>
  1. 首先创建一个普通的maven项目

  2. 在项目中创建一个Hello类,并定义属性str,建立对应的setter和getter方法

public class Hello {  
  private String str;  
  public String getStr() {    
      return str; 
    }   
  public void setStr(String str) {   
      this.str = str; 
    }   
    @Override  
    public String toString() {     
    return "Hello{" +         
    "str='" + str + '\'' +        
    '}';  
    }
}
  1. 在resources目录下编写对应的xml配置文件(例如:beans.xml)

需要注意的是bean代表一个Java对象,id代表创建对象的变量名,class中的内容表示new的对象,property表示的是属性值的配置。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"    
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans                        https://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <!--使用Spring来创建对象,在Spring中这些都被称为Bean   
     Bean对象   
     id 变量名 
    class new的对象
    property 相当于设置属性的值。 
   -->
  
  <bean id="Hello" class="com.apache.pojo.Hello">    
    <property name="str" value="name"/>    
  </bean>
</beans>
  1. 在测试类下进行测试
import com.apache.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {  
    public static void main(String[] args) {   
    //使用xml加载必须要使用的ClassPathXmlApplicationContext  
    //此处参数需要传入xml文件(配置文件) 
    //context即为Spring的上下文对象   
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");        
    //我们的对象都由spring来管理了,如果要使用,需要取出(根据id)
    Hello hello=(Hello)context.getBean("Hello");   
    System.out.println(hello.toString()); 
    }
}

需要注意的是:xml的加载必须要使用的ClassPathXmlApplicationContext,对象中传入的参数是需要的「配置文件名」,所有的对象交由spring来管理,通过上下文的getBean来获取所有的对象(根据xml文件中配置的id)。同时id需要注意大小写格式。

同时想要更换实现,只需要修改xml文件即可。不需要变动代码。

  • Hello对象是谁创建的?

hello对象是由Spring来创建的

  • Hello对象的属性是怎么设置的?

hello对象的属性是由Spring容器设置的

「这个过程就叫做控制反转。」

  • 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring之后,对象是由Spring来创建的。
  • 反转:程序本身并不创建对象,而是变成被动的接收对象。
  • 依赖注入:就是利用set方法来进行注入的

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

可以通过newClassPathXmlApplicationContext去浏览底层源码

「要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC就是对象由Spring来创建、管理、装配。」

五、IOC创建对象的方式

(一)使用构造器来实例化

  1. 使用默认无参数构造方法构造对象
public class User 
  private String name;  
  public void User(){    
    System.out.println("默认无参构造...."); 
    }  
  public String getName() 
  return name;
  }   
  public void setName(String name) {   
  this.name = name;  
  }   
  public void show(){    
  System.out.println("name="+name);
  }
}
<bean id="user" class="com.alibaba.pojo.User">  
  <property name="name" value="小明"/>
</bean>

测试:

public class MyTest 
    public static void main(String[] args) {    
      ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");   
      User user = (User)context.getBean("user");  
      user.show(); 
      }
  }
  1. 假设我们要使用有参构造方法来构造对象
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; 
      }
    public void show(){   
    System.out.println("name="+name);
      }
  }
  • 使用下标来给构造函数传参数,并且实现对象创建
 <!--1.使用下标来给构造函数传参-->
<bean id="user" class="com.alibaba.pojo.User">  
  <constructor-arg index="0" value="小明"/>
</bean>
  • 使用类型来给构造函数传参,并实现对象创建
 <!--2. 使用参数类型匹配来实现对象构造(不建议使用,因为会造成类型重复,如参数是两个string)-->  
<bean id="user" class="com.alibaba.pojo.User">  
  <constructor-arg type="java.lang.String" value="小明"/>  
</bean>
  • 通过参数名来给构造函数传参,并实现对象创建
  <!--3. 直接通过参数名来设置(之前类的参数名为name)--> 
<bean id="user" class="com.alibaba.pojo.User">  
  <constructor-arg name="name" value="小明"/> 
</bean>

通过参数名来创建对象,name属性,是仅针对基本类型参数和String类型参数,value依旧是传入参数的值,如果是其他引用类型参数,则需要使用ref属性来实现配置。

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

(二)使用静态工厂方法来实例化

  • 要有该工厂类及工厂方法
  • 工厂方法为静态的
  1. 定义具体的业务类
public class UserService {  
    public void example(){      
      System.out.println("这是一个UserService的测试方法.........");  
      }
  }
  1. 定义静态工厂类,其中含有静态工厂方法
public class StaticFactory {   
/**   
* 定义对应的静态方法   
* @return UserService 
*/
 
public static UserService getService()
      return new UserService(); 
      }
  }
  1. 编写实现静态工厂方法的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        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--静态工厂实例化    id:表示即将要创建对象名    class指向静态工厂全类名    factory-method:表示静态工厂中创建实例化对象的方法    -->  
  <bean id="userService" class="org.alibaba.factory.StaticFactory" factory-method="getService"/>
</beans>
  • id表示即将需要创建的对象的名字
  • class表示静态工厂全类名,表示指向了静态工厂
  • factory-method表示即将引用静态工厂类中的指定方法实现对象的实例化。

Bean的实例是由用户创建的静态工厂方法来创建。而不再由Spring来管理创建。

  1. 测试
public class AppTest {   
/**    
* Rigorous Test :-)   
*/
  
    @Test
  public void shouldAnswerWithTrue()    {  
    BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");    
    UserService userService = factory.getBean("userService", UserService.class);  
    userService.example(); 
    }
}

也可以使用BeanFactory来实现容器对象。

(三)使用实例化工厂来实例化对象

  1. 首先创建要实例化的类
public class UserDao {   
    public void test(){     
    System.out.println("这是一个UserDao的test方法.........");  
    }
  }
  1. 编写普通的实例化工厂类
public class InstanceFactory {    
/** 
* 定义实例化工厂的方法
*/
  
    public UserDao getInstanceBean(){     
      return new UserDao(); 
      }
  }
  1. 编写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        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置我们的实例化工厂    
   1. 配置所需要的工厂对象,id为对象名,class为实例工厂的全类名 
   2. 配置bean对象、工厂对象及实例化对象的实例化方法-->
 
  <bean id="instanceFactory" class="org.alibaba.factory.InstanceFactory"/>  
  <bean id="userDao" factory-bean="instanceFactory" factory-method="getInstanceBean"/>
</beans>

其中第一个bean的id表示的是实例化工厂的id属性值,指向对应的实例化工厂类

第二个bean的id表示的是我们需要实例化对象的id,也就是对象名,factory-bean表示的是引用哪个实例化工厂的id来帮助实现对象的实例化,factory-method表示对应的实例化工厂的实例化目标对象的实例化方法。

(四)三种IOC创建对象(实例化对象的方式比较)

  1. 方式一:「通过bean的构造函数创建」,当各个bean的业务逻辑相互比较独立的时候和外界关联比较少的时候可以使用。
  2. 方式二:利用静态factory方法创建,可以统一管理各个bean的创建,如各个bean在创建之前需要相同的初始化处理,则可以用这个factory方法进行统一的处理等等。
  3. 方式三:利用实例化factory方法创建,即将factory方法也作为了业务bean来控制,可以用于集成其他框架的bean创建管理方法,能够使bean和factory角色互换。

「开发项目中一般用一种方式实例化bean,项目开发基本采用第一种方式,交给Spring托管,使用时直接拿来使用即可,另外两种作为了解。」

六、Spring配置

(一)别名

以User类为例子

 <bean id="user" class="com.alibaba.pojo.User">  
   <constructor-arg name="name" value="小明"/>   
   <constructor-arg name="age" value="18"/>   
</bean>   
<!--设置user的别名,在获取user对象时依旧可以获取到--> 
<alias name="user" alias="userAlias"/>

(二)Bean的配置

<!--   
 id表示的该bean的唯一标识符,也就类似于变量名  
  class表示的该bean的全限定名  
  name表示的是该bean对象的别名,而且相比alias,name更高级,可以同时取多个别名    -->
   
<bean id="userTwo" class="com.alibaba.pojo.UserTwo" name="two,u2">       <!--注册新创建的类UserTwo-->   
  <property name="name" value="东部战区"/>   
</bean>

需要注意的是各个属性与值之间的关系。

(三)import

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

如果有多个xml配置文件,可以使用import来将多个xml配置文件合并为一个总的,使用的时候就可以直接使用合并的xml文件。

<!--此处使用import来导入其他的xml配置文件-->
<import resource="beans1.xml"/>
<import resource="beans.xml"/>  
<import resource="beans2.xml"/>

七、DI依赖注入

DI依赖注入的方式有三种:构造器注入、set注入、第三方方式注入

(一)构造器注入

会产生循环依赖问题,例如当A对象的实例化构造器中需要B对象支持,B对象的实例化构造器中需要A对象支持,在进行构造器注入时,会出现循环依赖问题,即实例化A的时候需要要求先实例化B,实例化B的时候又要求必须先实例化A

出现循环依赖问题,解决方法是使用set注入的方式来解决

<!--1.使用下标来给构造函数传参-->
<bean id="user" class="com.alibaba.pojo.User">   
  <constructor-arg index="0" value="小明"/>
</bean>   

<!--2. 使用参数类型匹配来实现对象构造(不建议使用,因为会造成类型重复,如参数是两个string)-->  
<bean id="user" class="com.alibaba.pojo.User">   
  <constructor-arg type="java.lang.String" value="小明"/> 
</bean>

<!--3. 直接通过参数名来设置(之前类的参数名为name)-->   
<bean id="user" class="com.alibaba.pojo.User">  
  <constructor-arg name="name" value="小明"/>  
</bean>            

(二)通过set方式注入

使用set方式注入

  • 依赖注入:本质上是set注入
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性,由容器来注入

环境搭建:

  1. 复杂类型
public class Address {  
    private String address; 
    public String getAddress() {  
      return address;  
      }   
    public void setAddress(String address) {   
      this.address = address;  
      }
    }
  1. 真实测试对象
public class Student {   
    private String name; 
    private Address address; 
    private String[] books;  
    private List<String> hobbies;
    private Map<String, String> card; 
    private Set<String> games; 
    private Properties info;
    private String boyOrGirlFriend;
  1. applicationCopntext.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                        https://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="student" class="com.apache.pojo.Student"> 
    <!--普通值注入-->
    <property name="name" value="李白"/>
  </bean>
</beans>
  1. 测试类
  @Test
public void testGetName(){  
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
  Student student = (Student)context.getBean("student"); 
  System.out.println(student.getName()); 
}

完善注入信息

 <!--由于Student的address属性需要address类对象存在,需要首先注入address类-->   <bean id="address" class="com.apache.pojo.Address">   
   <property name="address" value="广州"/> 
</bean>

<bean id="student" class="com.apache.pojo.Student">  
  <!--1.普通值注入-->  
  <property name="name" value="李白"/> 
  
  <!--2.注入普通类型的对象即bean注入--> 
  <property name="address" ref="address"/> 
  
  <!--3.数组注入-->  
  <property name="books"> 
    <array>
      <value>红楼梦</value>
      <value>西游记</value>
      <value>水浒传</value>
      <value>三国演义</value>
    </array> 
  </property> 
  
  <!--4.List集合注入-->    
  <property name="hobbies">   
      <list>   
        <value>听歌</value> 
        <value>敲代码</value> 
        <value>看电影</value>   
    </list>  
  </property>  
  
  <!--5Map集合注入--> 
  <property name="card">  
      <map>       
        <entry key="身份证" value="1234567891012"/>  
        <entry key="银行卡" value="152465456"/>     
    </map>    
  </property>   
  
  <!--6set注入-->  
  <property name="games" > 
      <set>   
        <value>LOL</value> 
        <value>CF</value> 
        <value>COC</value>  
    </set>   
  </property>  
  
  <!--7.NULL值注入(首先是空值注入)-->  
  <!--<property name="boyOrGirlFriend" value=""/>-->   
  <property name="boyOrGirlFriend">    
    <null/>   
  </property>  
  
  <!--8.properties注入(key-value)--> 
  <property name="info">     
    <props>        
      <prop key="学号">24541212</prop>   
      <prop key="性别"></prop>   
    </props>   
  </property>
</bean>

(三)通过其他第三方方式注入

我们可以使用「P命名空间和C命名空间」进行注入

  • p-namespace 允许您使用bean元素的属性(而不是嵌套 <property/>元素)来描述协作 bean 的属性值,或两者兼而有之。但是p 命名空间不如标准 XML 格式灵活。
  • Spring 3.1 中引入的 c-namespace 允许内联属性来配置构造函数参数而不是嵌套constructor-arg元素。
<?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:p="http://www.springframework.org/schema/p" 
       xmlns:c="http://www.springframework.org/schema/c"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans                        https://www.springframework.org/schema/beans/spring-beans.xsd">
  
  <!--使用p标签来实现注入 格式:p:属性名=属性值-->   
  <bean id="user" class="com.apache.pojo.User" p:name="杜甫" p:age="25"/>  
  
  <!--使用C命名标签实现注入。格式 主要用于构造函数的赋值-->   
  <bean id="user2" class="com.apache.pojo.User" c:name="王维" c:age="39"/></beans>
  @Test  
public void testUsePNameSpace(){  
    ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");    
    //如果显式声明了类型就不需要强制类型转换了 
    User user = context.getBean("user", User.class);
      System.out.println(user); 
      }  
      
  @Test 
public void testUseCNameSpace(){   
    ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");     
    //如果显式声明了类型就不需要强制类型转换了
    User user = context.getBean("user2", User.class);   
    System.out.println(user);  
  }

注意点:

p和c命名空间不能够直接使用,需要导入xml的约束

(四)Bean的作用域

1. 单例singleton

单例模式是Spring的「默认机制」(对于bean来说,默认就是单例的)

 <bean id="user2" class="com.apache.pojo.User" c:name="王维" c:age="39" scope="singleton"/>
<bean id="user3" class="com.xxx.dao" lazy-init="true"/>

注意:lazy-init是懒加载,如果等于true时,指Spring容器启动的时候不会去实例化这个bean,而是在程序调用时实现实例化,默认是false,即Spring容器启动时就会实现实例化。

为什么默认不实现懒加载?

  1. 可以提前发现潜在的配置问题
  2. bean对象在启动时就会设置在单例缓存池中,使用时不再需要实例化bean,提高运行效率。

什么对象适合交给IOC容器实例化(适合作为单例)

无状态对象,即不存在会改变当前对象状态的成员变量,实际上对象状态的变化往往均是由于属性值变化而引起的。

适合对象:Controller、Service层、Dao层

默认情况下,被管理的bean只会在IOC容器中存在一个实例,对于所有获取该Bean的操作,Spring容器只返回一个bean

「容器在启动的情况下,就实例化所有singleton对象,并缓存于容器中」

2. 原型模式prototype

prototype表示原型模式,即每一个bean创建都是单独的对象

每次从容器中获取的时候,会有多个新的对象

即Spring IOC容器在启动的时候,不会将bean对象实例化设置到单例缓存池中。

<bean id="user2" class="com.apache.pojo.User" c:name="王维" c:age="39" scope="prototype"/>

3.Web应用中的作用域

  • 「Request作用域」:表示每个请求需要容器创建一个全新的bean,比如提交表单的数据必须是对每次请求新建一个Bean来保持这些表单数据,请求结束释放这些数据。
  • 「session作用域」:表示每个会话都会创建一个全新的Bean,比如对于每个用户一般会有一个会话,该用户的用户信息需要存储到会话中,此时可以将该Bean作用域配置为session级别。
  • 「globalSession作用域」:类似于session作用域,其用于portlet(Portlet是基于Java的Web组件,由Protlet容器管理,并由容器处理请求,生产动态内容。)环境的Web应用,如果在非portlet环境将视为session作用域。

配置方式和基本的作用域相同,只是必须要有web环境的支持,并配置相应的容器监听器或拦截器从而能应用这些作用域。

八、Bean的自动装配与生命周期

自动装配是Spring满足bean依赖的一种方式

Spring会在上下文中自动寻找,并自动给bean装配属性

在Spring中有三种装配的方式:

  1. 在xml中显式装配
  2. 在java中显式装配
  3. 隐式的自动装配bean

(一)测试(使用xml显式装配)

public class Cat {   
    public void shout(){  
      System.out.println("miao~");  
      }
   }
    
public class Dog {   
    public void shout(){     
      System.out.println("wang~");  
      }
  }
  
public class Person {   
    private Cat cat;
    private Dog dog;  
    private String name;
    public Cat getCat() {        
        return cat;  
      }  
    public void setCat(Cat cat) {  
        this.cat = cat; 
      }   
    public Dog getDog() {   
        return dog;  
      }  
    public void setDog(Dog dog) {      
        this.dog = dog;  
      }  
    public String getName() {       
        return name;
      }   
    public void setName(String name) {       
        this.name = name; 
      }   
      @Override  
    public String toString() {   
        return "Person{" +    
        "cat=" + cat +   
        ", dog=" + dog +   
        ", name='" + name + '\'' +   
        '}';  
        }
    }
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"    
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--分别配置不同的对象--> 
  <bean id="cat" class="com.alibaba.pojo.Cat"/>  
  <bean id="dog" class="com.alibaba.pojo.Dog"/>  
  <bean id="person" class="com.alibaba.pojo.Person">  
    <property name="name" value="小明"/>    
    <property name="cat" ref="cat"/>  
    <property name="dog" ref="dog"/>  
  </bean>
</beans>

(二)通过ByName的形式装配

 <!--使用ByName实现自动装配   
     会自动在容器上下文中查找与和自己对象set方法之后的值对应的bean的id 
       例如会自动寻找名称为cat和dog的bean-->
   
<bean id="person" class="com.alibaba.pojo.Person" autowire="byName">        <property name="name" value="小明"/>   
</bean>

(三)通过ByType的形式装配

  <!--使用ByType实现自动装配  
  会自动在容器上下文中查找与自己对象的set方法之后的类型对应的bean
-->
   
<bean id="person" class="com.alibaba.pojo.Person" autowire="byType">        <property name="name" value="小明"/>  
</bean>

小结:

  • byName的时候需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致才行
  • byType的时候需要保证所有bean的class唯一,并且这个bean需要保证和自动注入的属性的类型一致。

(四)使用注解实现自动装配

JDK1.5支持注解,Spring2.5支持注解

The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.

要使用注解需要了解:

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

@「Autowired」

  • 默认通过类型(Class类型)查找bean对象,与属性字段的名称无关。
  • 属性可以提供set方法也可以不提供set方法
  • 注解可以声明在属性级别或者set方法级别。
  • 可以添加@Qualifiler结合使用,通过value属性值查找对象(value属性值必须要设置,且值要与bean标签的id属性值相对应)

直接在属性上使用即可,也可以在set方法上使用

使用自动装配首先是通过ByType来匹配,当类型相同时,可以通过ByName来匹配。

使用AutoWired我们可以不用编写set方法了,前提是需要保证自动装配的属性在IOC(Spring)容器中存在,且符合名字ByName

public @interface Autowired { 
    boolean required() default true;
}

如果显式地定义了Autowired的required属性为false,则说明这个属性可以为null,否则不允许为null

@Nullable

字段标记了这个注解,说明了这个字段可以为null

//测试
public class Person {   
  /**此处表示属性的值可以为null,需要设置autowired的required属性为false*/   
  @Autowired(required = false)
  private Cat cat;
  
  @Autowired  
  private Dog dog;  
  private String name;
}

如果自动装配属性的环境比较复杂,自动装配无法通过一个注解@Autowired完成,我们可以使用@Qualifier(value = "xxx")来配合Autowired的使用指定唯一的bean

  @Autowired 
  @Qualifier(value = "dog1")   
  private Dog dog;

@Resource

该注解可以指定bean

  • 根据默认提供的属性字段名称查找对应的bean对象(属性字段名称与bean标签的id属性值相等)
  • 如果属性字段名称未找到,则会通过类型(Class类型)查找
  • 属性可以提供set方法,也可以不提供set方法
  • 注解可以声明在属性级别或set方法级别
  • 可以设置name属性,name属性值必须与bean标签的id属性值一致,如果设置了name属性值,就只会按照name属性值查找bean对象(例如设置@Resource(name=“ud”),那么一定会去查找是否存在id为“ud”的bean对象,如果没有则会报错。)
  • 当注入接口时,如果接口只有一个实现,则正常实例化,如果接口存在多个实现,则需要使用name属性指定需要被实例化的bean对象。
public @interface Resource {   
    String name() default "";  
    Class type() default Object.class
    Resource.AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER
    boolean shareable() default true
    String mappedName() default "";  
    String description() default "";  
    public static enum AuthenticationType {     
      CONTAINER,
      APPLICATION;
      private AuthenticationType() {    
      }
    }
  }

小结:

@Autowired和@Resource

  • 都是用来自动装配的,都可以放在属性字段上

  • @Autowired-默认通过ByType属性方式来实现,如果找不到类型,就通过ByName的形式(必须要保证bean对象的存在。)

  • @Resource默认通过ByName的形式来实现,如果找不到,则使用ByType形式

(五)Bean的生命周期

「在Spring中,Bean的生命周期包括Bean的定义、Bean的初始化、使用和销毁四个阶段。」

1. Bean的定义

在Spring中,通常是通过配置文档的方式来定义Bean的,在一个配置文档中,可以定义多个Bean。

2.Bean的初始化

默认在IOC容器加载时,实例化对象。

Spring bean初始化有两种方式:

  • 方式一:在配置文档中通过指定init-method属性来完成
//要实例化的bean对象
/**
* @author lambda 
*/

public class StudentService {    
    public void test(){     
      System.out.println("StudentService test........"); 
      }   
    public void initBean(){      
      System.out.println("StudentService被初始化了.......");
      }
    }
   <!--Bean的初始化   
 1. 使用init-method方法实现
-->
    
<bean id="studentService" class="com.alibaba.service.StudentService" init-method="initBean"/>

此处指定init-method为initBean的方法

 ApplicationContext context = new ClassPathXmlApplicationContext("lazyInit.xml");

此时会将Bean对象直接初始化。

  • 方式二:实现org.springframework.beans.factory.InitializingBean接口来实现Bean的初始化
public class StudentService implements InitializingBean {    
    public void test(){       
    System.out.println("StudentService test........");  
    }   
    /**
    * 查看初始化的方法   
    * @throws Exception 
    */
   
    @Override   
  public void afterPropertiesSet() throws Exception {    
  System.out.println("StudentService 正在被初始化........-----------》afterPropertiesSet()");  
  }
}

实现org.springframework.beans.factory.InitializingBean接口并重写bean的初始化方法

 <!--2.实现org.springframework.beans.factory.InitializingBean接口来实现bean的初始化-->  
<bean id="studentService" class="com.alibaba.service.StudentService"/>

只需要在xm文件中建立bean标签即可,不可以加入初始化方法选项。

3.Bean对象的使用

  • 方式一:使用ApplicationContext
  ApplicationContext context = new ClassPathXmlApplicationContext("lazyInit.xml");
  RoleService roleService = context.getBean("roleService", RoleService.class);
  • 方式二:使用BeanFactory
  BeanFactory context = new ClassPathXmlApplicationContext("lazyInit.xml"); 
  RoleService roleService = context.getBean("roleService", RoleService.class);

4.Bean的销毁

实现销毁的方式(Spring容器会维护bean对象的管理,可以指定bean对象的销毁所要执行的方法)

  • 步骤一:在对应的要销毁的bean对象的类中创建对应的销毁方法
  • 步骤二:在对应的测试文件中,通过AbstractApplicationContext类对象,实例化对应的bean。
  • 步骤三:在xml文件中设置destory-method属性值,设置为要销毁bean对象的对应的方法即可
public class StudentService implements InitializingBean {   
    public void test(){     
      System.out.println("StudentService test........"); 
      }  
      /**
      * 查看初始化的方法  
      * @throws Exception
      */
  
      @Override
    public void afterPropertiesSet() throws Exception {  
    System.out.println("StudentService 正在被初始化........-----------》afterPropertiesSet()");  
    } 
    /**
    * bean对象所对应的销毁方法   
    */
  
    public void destory(){  
    System.out.println("StudentService 销毁了......"); 
    }
  }

创建AbstractApplicationContext类对象,实例化所有的bean对象

AbstractApplicationContext ap=new ClassPathXmlApplicationContext("lazyInit.xml");

在xml文件中设置属性值

<!--bean对象的销毁-->   
<bean id="studentService" class="com.alibaba.service.StudentService" destroy-method="destory"/>

最后bean对象会被完全销毁,实现了bean对象的销毁。

九、使用注解开发

在Spring4之后,要使用注解开发,必须要保证aop的包导入

使用注解必须要导入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        https://www.springframework.org/schema/beans/spring-beans.xsd            http://www.springframework.org/schema/context     
     https://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/></beans>

(一)常用注解

  • @Autowired
  • @Nullable
  • @Resource
  • @Component:组件,放在类上,说明这个类被Spring管理了,就是bean
  • @Value:放在属性或者set方法上,表示给属性赋值

(二)属性注入

@Component
public class User {  
/**Component注解等价于 
<bean id="user" class="com.apache.pojo.User"/>
*/
   
/**Value相当于 
*  <bean id="user" class="com.apache.pojo.User"> 
*         <property name="name" value="李白"/>    
*   </bean>    
* */
  
  @Value("李白")  
  private String name; 
  public String getName() {  
    return name;
  } 
  /**Value注解也可以放在set方法之上*/ 
  @Value("李白"
  public void setName(String name) {
    this.name = name;
   }
  }

(三)SpringIOC扫描器

在实际的开发中,bean的数量非常多,采用手动配置bean的方式已无法满足生产需求,Spring提供了扫描的方式,对扫描到的bean对象进行统一管理,简化开发配置,提高开发效率。通过注解替代原先需要写在配置文件中的bean标签。

  1. Spring IOC扫描器的配置
Spring IOC 扫描器 作用:bean对象统一进行管理,简化开发配置,提高开发效率 a、设置自动化扫描的范围  如果bean对象未在指定包范围内,即使声明了注解,也无法实例化b、使用指定的注解(声明在类级别),bean对象的id属性默认是类的首字母小写 Dao层:@Repository Service层:@Service Controller层:@Controller 任意类: @Component注:开发过程中建议按照规则声明注解 
  • 设置自动化扫描的范围
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
       xmlns:context="http://www.springframework.org/schema/context"            xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd            http://www.springframework.org/schema/context 
     https://www.springframework.org/schema/context/spring-context.xsd">
   <!--使用Spring IOC的自动化扫描   设置自动化扫描的范围   -->
  <context:component-scan base-package="com.xxx"/></beans>

需要注意的是此处仍旧需要使用到content的命名规范,因为IOC扫描需要用到注解来支持。

<context:component-scan base-package="com.xxx"/> </beans>表示IOC扫描到指定包下的所有内容,如果xxx包下有已经装配好的bean则不需要在xml文件中另行配置bean

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

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

这四个注解功能都是一致的,都是代表将某个类注册到Spring中,并装配。

(四)作用域

@Component
@Scope("singleton")
//此处注解表示该类的作用域是单例
public class User 
/**Component注解等价于 <bean id="user" class="com.apache.pojo.User"/>*/    /**Value相当于     
*<bean id="user" class="com.apache.pojo.User">   
*         <property name="name" value="李白"/>     
*     </bean>     * */
  
  @Value("李白")  
  private String name; 
  public String getName() {  
      return name;
    }
    /**Value注解也可以放在set方法之上*/   
  @Value("李白"
  public void setName(String name) {   
  this.name = name;
  }
}

(五)小结

xml与注解:

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

最佳实践:

  • xml用来管理bean
  • 注解只负责完成属性的注入
  • 我们在使用过程中只需要注意一个问题,必须让注解生效就需要开启注解的支持

十、使用Java的方式配置Spring

可以完全不使用Spring的xml配置,全权交给了Java来做

javaConfig是Spring的的一个子项目,在Spring4之后,它成为了一个核心功能。

  • 实体类
@Component
/**此处表示该类已经被Spring接管了,并注册到容器中*/
public class User {    
    private String name; 
    public String getName() 
        return name;
        } 
    @Value("Jack")
    public void setName(String name) 
        this.name = name;
      }    
    @Override
    public String toString() 
        return "User{" +     
        "name='" + name + '\'' +   
        '}';   
        }
  }
  • 配置类(代替applicationContext.xml文件)
@Configuration 
/**这个也会被Spring容器托管,因为他本质上也是一个@Component。该注解表示自己是一个配置类 和之前的beans.xml一样*/@ComponentScan("com.apache.pojo")*/
/**此处表示扫描指定包*/@Import(MyConfig2.class)*/
/**表示引入另一个配置类的信息*/
public class MyConfig 
{    
/**@Configuration*/  
/**注册一个bean,就相当于之前在xml文件中写的一个bean标签   
* 这个方法的名字相当于bean中的id ,这个方法的返回值就相当于bean标签中的class属性    * 这个return的对象就是我们需要的bean对象*/
 
  @Bean
  public User getUser(){   
      return new User();
      }
    }
  • 测试类
public class MyTest {   
  @Test 
public void test1(){  
//通过注解的方式来获取上下文   
//如果完全使用了配置类的形式来完成配置,就需要使用AnnotationConfigApplicationContext来获取容器, 
//通过配置类的class对象来加载   
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User getUser = context.getBean("getUser", User.class);     
System.out.println(getUser.getName()); 
    }
}

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

十一、两大代理模式

(一)代理模式

为什么要学习代理模式?

因为Spring AOP的底层就是代理模式

代理模式在Java开发中是一种比较常见的设计模式,设计的目的在于为服务类和客户类之间插入其他的功能,插入的功能对于调用者是透明的,起到伪装控制的作用。

为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问,委托类和代理类有一个共同的父类或者接口,代理类会对请求做预处理,过滤,将请求分配给指定的对象。

代理模式的两大基本原则:

  • 代理类与委托类具有相似的行为(共同)
  • 代理类增强委托类的行为

1.分类

  • 静态代理
  • 动态代理

(二)静态代理模式

某个对象提供一个代理,代理角色固定,以控制对这个对象的访问,代理类和委托类有共同父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代,代理类负责请求的预处理、过滤、将请求分派给委托类处理,以及委托类执行完请求后的后续处理

1. 角色分析:

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

2.好处

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

3.缺点

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

4. 实现步骤

以房东中介和真实的人租房子为例子

  • 接口
/** 
* The interface Rent.
*
* @author lambda  
表示租房的角色 */

public interface Rent {   
/**
* Rent.
*/
 
void rent();
}
  • 真实角色
/**
* @author lambda 
* 表示房东,要把房子租出去(要实现将房子租出去的接口) 
*/

public class Host implements Rent {   
  @Override
  public void rent() {  
      System.out.println("房东要将出租房子");  
      }
  }
  • 代理角色
/** 
* @author lambda
* 表示租房子与出租房子之间的中介 
*本质上来说房东也需要实现租房子的接口才能出租房子 
*/

public class Proxy implements Rent {   
  /**要帮房东出租房子,所以需要获取房东对象,建立联系*/  
  private Host host;
  public Proxy() {    }
  public Proxy(Host host) {   
    this.host = host;
    }   
  @Override  
  public void rent() {    
  seeHouse();  
  host.rent(); 
  free();    
  writeFile(); 
  }   
  /**代理还可以在原来的业务上进行增强*/
  public void seeHouse(){
      System.out.println("中介带你看房子.........");
      }
  public void free(){   
      System.out.println("中介收取中介费.......");  
      }  
  public void writeFile(){    
      System.out.println("中介签合同");  
      }
}
  • 客户访问代理角色
/** 
* @author lambda
* 表示真实的要租房子的人 
*/

public class Client {  
    public static void main(String[] args) {      
    //Proxy表示代理,是将房东的房子代理租出去,
    Proxy proxy = new Proxy(new Host());
    proxy.rent();  
    }
}

5.AOP

AOP的实现机制

6. 静态代理的特点

  • 目标角色固定
  • 在应用程序之前就得知目标角色
  • 代理对象会增强目标对象的行为
  • 有可能产生多个代理

(三)动态代理模式

1.动态代理实现

动态代理和静态代理的角色是一样的。

动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生,它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象。无需程序员手动编写它的源代码,动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意的动态代理类,代理的行为可以代理多个方法,「即满足生产需要的同时又达到代码通用的目的。」

  • 动态代理的代理类是动态生成的,不是直接写好的
  • 动态代理可以分为两大类,基于接口的动态代理,基于类的动态代理
    • 基于接口的——JDK的动态代理
    • 基于类的:cglib
    • Java字节码实现:javssist

需要了解两个类:Proxy(代理)和InvocationHandler(调用处理程序)

  • 编写接口
public interface Rent {   
/**   
* Rent.
*/
  
void rent();}
  • 编写接口的实现类(真实角色)
public class Host implements Rent {   
  @Override
  public void rent() {   
      System.out.println("房东要将出租房子"); 
      }
  }
  • 编写处理代理类的处理程序
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); 
      } 
/**处理代理结果并返回实例*/   
  @Override 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {       
  //动态代理的本质就是使用反射   
  Object invoke = method.invoke(rent, args);  
  return invoke;  
  }
}

这里需要注意的有以下几点:

  1. 需要设置被代理的对象,本质上就是设置共同实现的接口
  2. 需要设置一个方法获取动态生成的代理类Proxy.newProxyInstance。参数分别需要传入类加载器(由于代理类实例处理程序和代理类都属于同一个类加载器,所以可以使用this来获取,第二个是接口,第三个代理类实例处理程序。)
  3. invoke方法表示处理代理实例方法的程序,参数为代理对象,Method对象,要执行代理方法的参数(其中method.invoke方法中的第一个参数是需要代理的接口实例)
  4. invoke方法不需要显式调用,当代理对象调用指定的代理方法时候就会自动调用该方法
  5. 动态代理的实现依赖于反射机制
  • 编写测试
public class Client {   
    public static void main(String[] args) 
      //实现真实角色Host 
      Host host=new Host(); 
      //代理角色(现在没有),创建调用处理程序,用其生成代理类角色  
      ProxyInvocationHandler pih = new ProxyInvocationHandler();              //通过调用程序处理角色来处理我们的接口   
      //由于host实现了Rent接口,所以利用多态实现 
      pih.setRent(host);     
      //这里的proxy就是动态生成的代理类对象  
      Rent proxy = (Rent)pih.getProxy();   
      proxy.rent();
      }
  }

需要注意的是:

  1. 无论是什么代理,真实角色都必不可少
  2. 我们需要利用代理类实例处理程序的实例来设置要代理的真实角色(真实角色与代理角色都实现了某个接口,该接口在代理类实例处理程序中设置过。)
  3. 通过代理类实例处理程序的实例可以获取代理对象(此时必须转成接口类型的对象,因为处理程序中使用接口来处理)
  4. 通过代理对象调用代理接口中的方法即可实现真实角色的动态代理

在这一过程中,没有手动创建任何的代理角色,都是自动生成的。

「扩展」

将代理处理程序抽象出来

public class ProxyInvocationHandlerUtil  implements InvocationHandler {    /**建立要代理的接口类*/  
  private  Object target;  
  /**利用set注入到该类中*/  
  public void setTarget(Object target) {      
      this.target = target;  
      }    
  /**创建一个方法在处理程序中创建代理对象实例*/ 
  public Object getProxy(){   
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this); 
    }   
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        
  Object invoke = method.invoke(target, args);
  return invoke;   
  }
}

2.动态代理的好处

  • 可以使真实角色的操作更加纯粹,不用关注一些公共的业务。
  • 公共的业务全部交给了代理角色,实现了业务的分工
  • 公共业务发生扩展时,方便集中管理
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务。
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。

3.动态代理的特点

  • 目标对象不固定
  • 在应用程序执行时动态创建目标对象
  • 代理对象会增强目标对象的行为

4.动态代理的两种实现方式

a、JDK动态代理
  • 「newProxyInstance」

Proxy类

Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法

/**返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序(返回代理对象)
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

Interfaces:一个Interface对象的数组,表示的是将要给需要代理的对象提供一组什么接口,如果提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法

h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口,每个代理实例都具有一个关联的调用处理程序,对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入InvocationHandler接口的子类)
*/

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){  
    xxxxx
}
b、CGLIB动态代理

「实现原理:继承思想」

代理类会继承目标类,并重写目标类中的方法

JDK的动态代理机制「只能代理实现了接口的类」,而不能实现接口的类就不能使用JDK动态代理,cglib则是针对类来实现代理,它的原理是对指定的目标生成一个子类,并覆盖其中方法实现增强,「但因为使用的是继承,所以不能对final修饰的类进行代理。」

  • 添加依赖

在pom.xml文件中引入cglib的相关依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency> 
  <groupId>cglib</groupId>   
  <artifactId>cglib</artifactId>
  <version>2.2.2</version>
</dependency>
  • 创建实现动态代理的类
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author lambda 
*/

public class Clibs implements MethodInterceptor {   
/**设置一个目标对象,Object类*/  
  private Object target;   
  /**通过构造器为目标类实现赋值*/   
  public Clibs(Object target){    
    this.target=target; 
    } 
  /**    
  * 获取代理对象  
  * @return
  */
   
  public Object getProxyInstance(){      
    Enhancer enhancer = new Enhancer();  
    enhancer.setSuperclass(target.getClass()); 
    enhancer.setCallback(this);  
    return enhancer.create();   
    }
    /**    
    *拦截器  
    * 目标对象方法调用与行为增强     
    * @param o cglib动态生成的代理类的实例   
    * @param method 实体类中所调用的被代理的方法的引用   
    * @param objects 参数列表 
    * @param methodProxy 生成的代理类对方法的代理引用---代理对象  
    * @return     
    * @throws Throwable  
    */
   
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {    
      System.out.println("before.........");   
      Object invoke = methodProxy.invoke(target, objects);  
      return invoke;  
      }
    }

5.CGLIB和JDK动态代理的区别

  • JDK动态代理实现接口,Cglib动态代理通过继承实现
  • JDK动态代理(目标对象存在接口时),执行效率高于Cglib
  • 如果目标对象有接口实现,选择JDK代理,如果没有接口实现,选择Cglib代理

十二、AOP面向切面编程

(一)什么是AOP

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

AOP关注的不再是程序代码中的某个类或者某些方法,考虑的更多的是一种面到面的切入。即层与层之间的一种切入,所以被称为切面。

AOP图示如下;

(二)AOP在Spring中的作用与特点

1. AOP作用及相关概念

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

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

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

通知类型连接点实现接口
前置通知方法前org.springframework.aop.MethodBeforeAdvice
后置通知方法后org.springframework.aop.AfterReturningAdvice
环绕通知方法前后org.aopalliance.intercept.Methodinterceptor
异常抛出通知方法抛出异常org.springframework.aop.ThrowsAdvice
引介通知类中增加新的方法属性org.springframework.aop.IntroductionInterceptor

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

拦截到的每一个连接点(每一个方法)后所要做的操作

  • 前置通知(前置增强)——before()执行方法的通知
  • 返回通知(返回增强)——afterReturn()方法正常结束返回后的通知
  • 异常抛出通知(异常抛出增强)——afterThrow()
  • 最终通知——after无论方法是否发生异常,均会执行该通知
  • 环绕通知——around包围一个连接点(join point)的通知,如方法调用,这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

2. AOP的功能

主要用于日志记录,性能统计、安全控制、事务处理等方面,「实现公共功能性的重复使用。」

3. AOP的特点

  • 降低模块与模块之间的耦合度,提高业务代码的聚合度(高内聚、低耦合)
  • 提高了代码的复用性
  • 提高系统的扩展性(高版本兼容低版本)
  • 可以在不影响原有功能的基础上添加新的功能

4.AOP的底层实现

底层实现主要基于动态代理(JDK+Cglib)

5.AOP中切入点表达式(execution)详解

例如:execution(* com.xxx.service.* .* (..))

  1. 第一个参数,表示执行所有的方法

代表的是方法的修饰范围,通常可以包括public,private和protected.如果取值为*,则表示所有方法,如果取值为public,则表示执行所有的公共方法。

  1. 第二个参数表示指定切入的范围,在哪个包中,一般为service包
  2. 第三个参数表示指定包下的哪些类,*表示指定包下的所有类
  3. 第四个参数表示指定包指定类下的方法,*表示指定所有的方法
  4. 第五个参数表示指定包下指定类下的方法的参数

6. AOP中常见切入点表达式

  • 表示执行所有的公共方法

execution(public *(..))

  • 表示执行任意的set或者get方法

execution(* set*(..))

  • 设置指定包下的任意类的任意方法

execution(* com.xxx.service .*.*(..))

  • 设置指定包及「子包」下的任意类的方法

execution(* com.xxx.service..*.*(..))

(三)使用Spring实现AOP

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

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

同时需要在xml配置文件中添加相应的命名空间

 xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd

方式一

使用Spring的API接口

  • 首先引入aop的约束(在配置文件中)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:aop="http://www.springframework.org/schema/aop" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd            http://www.springframework.org/schema/aop  
     https://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>
  • 编写接口和实现类
public interface UserService {   
/**
* Add.  
*/
 
void add();

/** 
* Delete. 
*/

void delete()

/**
* Update.
*/
    
void update();  

/**    
* Query.
*/
   
void query();
}

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 query() {   
      System.out.println("查询了一个用户");  
      }
  }
  • 编写日志类(包括前置日志和后置日志)
public class BeforeLog implements MethodBeforeAdvice {  
/**   
* method:表示即将要执行的目标对象的方法  
* objects:表示参数,args
* o:即target,表示目标对象*/
 
  @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 
  /**o表示返回的结果   
  * method表示目标对象要执行的方法  
  * objects表示参数
  * o1表示目标对象    
  * */
   
  @Override
  public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {   
    System.out.println("执行了"+method.getName()+",返回的结果为:"+o);
    }
 }

这里需要注意的是需要实现两个不同的接口,并且方法所代表的含义大致相同。

  • 编写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:aop="http://www.springframework.org/schema/aop"   
       xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd            http://www.springframework.org/schema/aop    
      https://www.springframework.org/schema/aop/spring-aop.xsd">
  
  <!--注册bean-->   
  <bean id="userService" class="com.alibaba.service.UserServiceImpl"/>    <bean id="beforeLog" class="com.alibaba.log.BeforeLog"/>  
  <bean id="afterLog" class="com.alibaba.log.AfterLog"/>  
  
  <!--使用原生的API接口--> 
  <!--配置AOP:导入aop的约束-->  
  <aop:config> 
    <!--1.首先配置切入点-->  
    <!--id随便写,expression表达式表示要执行的地点=execution(* +完全限定类名+.*(*表示所有方法),也可以指定具体方法)-->   
    <aop:pointcut id="pointCut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/>      
    <!--2.执行环绕        此处表示执行环绕advisor,前一个advice表示执行哪个?后端的pointCut-ref表示选择切入点-->   
    <aop:advisor advice-ref="beforeLog" pointcut-ref="pointCut"/>           <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
  </aop:config>
</beans>

这里需要注意几点:

  • 首先注册各个实体类,注册bean为对象

  • 其次,要使用aop来实现切面编程,需要导入aop的相关约束

  • 配置aop的时候要先配置aop的切入点,即在何时调用指定的相关方法,execution(* com.xxx.service.UserServiceImpl.*(..))此处表达式是个公式,*表示匹配所有,加上类的完全限定名.+ *,后面的(..)不可以省略,表示需要配置的其他内容,如果有就可以继续配置。

  • 表达式的内容为:修饰词+返回值+类名+方法名+参数

  • 最后执行环绕,advisor输入相对的应用,并引用相应的切入点,并且切入点不唯一。

  • 测试类

public class MyTest {    
    public static void main(String[] args) {  
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");      
    //动态代理代理的是接口    
    UserService userService = context.getBean("userService", UserService.class);    
    userService.add();
    }
  }

此处需要注意的是在使用getBean创建对象的时候需要使用接口类型来创建,因为代理的是接口(动态代理)

方式二

使用自定义类来实现AOP(主要是切面定义)

  • 首先在模块中添加相应的AOP依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->    <dependency>     
  <groupId>org.aspectj</groupId>    
  <artifactId>aspectjweaver</artifactId>    
  <version>1.9.6</version>   
</dependency>
  • 编写一个类来作为切面(将要操作的模块抽象成为一个类),并定义相应的方法
public class DiyPointCut {  
    public void beforeAdvice(){    
      System.out.println("执行了一个前置方法"); 
    }   
    public void afterAdvice(){      
      System.out.println("执行了一个后置方法"); 
    }
}
  • 配置项目的xml文件,为每个类注册相应的bean对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd            http://www.springframework.org/schema/aop
     https://www.springframework.org/schema/aop/spring-aop.xsd">
 
  <!--注册bean-->   
  <bean id="userService" class="com.alibaba.service.UserServiceImpl"/>    <bean id="beforeLog" class="com.alibaba.log.BeforeLog"/> 
  <bean id="afterLog" class="com.alibaba.log.AfterLog"/>   
  <!--使用自定义的切面-->   
  <!--1.注册自定义的类--> 
    <bean id="diyPointCut" class="com.alibaba.diy.DiyPointCut"/> 
  <!--2.使用aop config配置切面-->  
  <aop:config>  
    <!--3.aspect表示模块化的切面的类,ref表示要引用的实际的类-->   
      <aop:aspect ref="diyPointCut">   
        <!--4.表示切入点:修饰词、返回值、类名、方法名、参数-->    
        <aop:pointcut id="point" expression="execution(* com.alibaba.service.UserServiceImpl .*(..))"/>  
        <!--5.通知,表示的是类中的一个方法-->   
        <aop:before method="beforeAdvice" pointcut-ref="point"/>   
        <aop:after method="afterAdvice" pointcut-ref="point"/>   
    </aop:aspect>   
  </aop:config>
</beans>

需要注意的是需要为切面注册bean对象,并使用aop:config进行相关的配置。

  1. 首先配置了相应的切面,指向定义好的bean对象实例(ref)
  2. 配置切入点,使用execution表达式
  3. 配置相应的通知,即在类中需要使用的方法(如前置、后置、环绕等)
  • 编写测试程序
import com.alibaba.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理代理的是接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

方式三

使用「注解」实现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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/>
  • 编写注解切面类
@Aspect  //标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.alibaba.service.UserServiceImpl.*(..))")
        public void before(){
            System.out.println("方法执行前......");
        }
    @After("execution(* com.alibaba.service.UserServiceImpl .*(..))")
        public void after(){
            System.out.println("方法执行后.......");

        }

        /**在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点*/
    @Around("execution(* com.alibaba.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint joinPoint){
        //传入一个连接点,可以从执行点处获取信息
        System.out.println("环绕前........");

        //获取签名(打印签名)
        System.out.println(joinPoint.getSignature());

        Object proceed=null;
        //使用连接点执行方法
        try {
             proceed = joinPoint.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }

        System.out.println("环绕后..........");

        System.out.println(proceed);
    }
}

需要注意的是可以使用注解来标识

  • 编写接口与实体类
public interface UserService {  
/**   
* Add.
*/
 
void add();

/** 
* Delete.
*/
   
void delete();

/**   
* Update.
*/
    
void update();

/**  
* Query.
*/

void query();
}

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 query() 
    System.out.println("查询了一个用户"); 
    }
  }
  • 完善配置文件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:aop="http://www.springframework.org/schema/aop"  
       xmlns:context="http://www.springframework.org/schema/context"           xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd            http://www.springframework.org/schema/aop     
     https://www.springframework.org/schema/aop/spring-aop.xsd
     http://www.springframework.org/schema/context 
     https://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启注解支持--> 
  <aop:aspectj-autoproxy/>   
  <!--注册bean--> 
  <bean id="userService" class="com.alibaba.service.UserServiceImpl"/>    <!--方式三:使用注解实现AOP-->
  <bean id="annotationPointCut" class="com.alibaba.diy.AnnotationPointCut"/>
</beans>
  • 测试类
public class MyTest {  
    public static void main(String[] args) {   
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
    UserService userService = context.getBean("userService", UserService.class);     
    userService.add();   
    }
  }

往期内容回顾

你不可不知的语言---JAVA

Java基本程序设计结构——数据类型

变量+运算=?

字符串详解

输入输出与流程

数组与大数

类初识

自定义类与时间类

方法参数与对象构造

包、注释及JAR文件

继承及其子类

Object类及其方法

泛型数组列表及包装类

IO流概述

XML文档与解析

Java多线程之JUC

JDBC初识

一文带你了解Maven

MyBatis竟如此简单?

JavaWeb-教你如何实现交互



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存