万字长文之spring的整合

陈年旧事

一、JSP + Java Bean(Model1)

1、模型的结构

  • 2、产生的问题

    如果有好几千个jsp, 这些jsp互相调用(通过GET/POST), 到了最后调用关系无人能搞懂。(随后演变出了Model2)

二、JSP和JavaBean+Servlet(Model2)

1.模型的结构

2、说明

JavaBean作为Model层,定义bean来表示数据和封装业务逻辑

  • 定义数据Bean来表示需要显示给用户的结果

  • 定义业务Bean来封装业务逻辑,也就是DAO

  • 使用Servlet处理用户请求

    • 对输入数据的检查和转换
    • 通过JavaBean访问数据库
    • 初始化JSP页面中要用到的JavaBean或对象,保存在作用域中
    • 根据处理中不同的分支和结果,决定转向那个JSP等

    JSP作为View层,负责生成交互后返回的界面

    • 它主要通过信息共享,获取Servlet生成的对象或JavaBean,从中取出数据,插入到HTML页面中

三、EJB(Enterprise Java bean)

1、什么EJB到底是

商务软件的核心部分是它的业务逻辑

2、目的

EJB是sun的JavaEE服务器端组件模型,设计目标与核心应用是部署分布式应用程序

简单来说就是把已经编写好的程序(即:类)打包放在服务器上执行。

凭借java跨平台的优势,用EJB技术部署的分布式系统可以不限于特定的平台。

EJB (Enterprise JavaBean)是J2EE(javaEE)的一部分,定义了一个用于开发基于组件的企业多重应用程序的标准。

其特点包括网络服务支持和核心开发工具(SDK)。

3、EJB要解决的问题

  • 数据存储(Persistent)
  • 分布式 (Distributed)
  • 事务处理(Transactional)
  • 安全性 (Secure)
  • 高可用性
  • 可扩展 (Scalable)

4、相关技术

JDBC

Java 数据库连接, 没有数据库的支持怎么能叫企业级应用

JNDI

Java命名和目录接口, 通过一个名称就可以定位到一个数据源, 连jdbc连接都不用了

RMI

远程方法调用(Remote Method Invoke ), 让一个机器上的java 对象可以调用另外一个机器上的java对象

注释:要实现远程调用必须序列化
把对象转换为字节序列的过程称为对象的序列化。

把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  • 在网络上传送对象的字节序列。

JTA

Java Transaction 事务管理, 支持分布式事务, 能在访问、更新多个数据库的时候,仍然保证事务, 还是分布式。

JMS

Java Message Service 各个软件模块收发消息的消息中间件,在各个软件模块之间收发消息

Java mail

收发邮件也是必不可少的啊。

名词解释

什么分布式?

一个业务分拆多个子业务,部署在不同的服务器上

强调 机器间的协作,其重点是任务可拆分, 如 某个任务需要一个机器运行10个小时, 将该该任务用10台机器的分布式跑,可能2个小时就跑完了 (主要是解决计算机内存,io,瓶颈)

什么是服务集群?

同一个业务,部署在多个服务器上

例如:某个任务需要一个机器运行10个小时,那任务放到 处理该任务的集群上 还是需要10个小时。 假如有10个这样的任务, 放到同一个集群上, 仍然需要10个小时

5、EJB的缺点

  1. 巨大而复杂的规格说明:对于一个复杂的分布式系统,有一个文档进行说明是一件很合理的事情。但是,并不是所有的文档信息都是真正必需的,EJB的文档反而成了一个非常不便利的工具。
  2. 庞大的文件:在你开发一个项目之前,你通常需要阅读1000多页的文档。这是部署EJB时非常痛苦的事情。
  3. 增加程序调试时间:制定EJB解决方案比远比使用普通的Java代码所需的时间长,调试EJB程序也比调试普通的Java代码所需要的时间也长。主要原因是我们不知道bug是出在代码本身还是出在容器上。
  4. EJB代码更加复杂:举个例子,为了实现一个session bean,你必须写三个类,为了实现一个实体bean,你需写四个类。增加几个部署描述语句,比如说最简单的"Hello world"程序需要10个文件,而不是一个文件。
  5. 重复设计的危险:导致这种结果的原因是复杂的文档所致。。如果你没有完全理解EJB的概念,你就不会很好的驾奴它,让它你所用,反之为你所累。
  6. 维护困难:EJB是一个不断更新的技术,在新技术不断推出的时候,代码需要升级,这就就需要额外的努力和成本来使你的程序和新的EJB容器兼容。

6、总结

  1. EJB实现原理: 就是把原来放到客户端实现的代码放到服务器端,并依靠RMI进行通信。
  2. RMI实现原理 :就是通过Java对象可序列化机制实现分布计算。
  3. 服务器集群: 就是通过RMI的通信,连接不同功能模块的服务器,以实现一个完整的功能。

四、Spring

1、前提概要

  1. 很快EJB中用起来极为繁琐和笨重, 性能也不好, 为了获得所谓的分布式,反而背上了沉重的枷锁。
  2. 实体Bean很快没人用了, 就连简单的无状态Session bean 也被大家所诟病, 更致命的就是“代码的侵入性” 。
  3. 2002年,Rod Johnson写的一本书
  4. 2003年2月,基于这本出版了极其叫卖的一本书 Spring开源项目开启
  5. 2004,出版了 基于一个易懂的,轻量级的框架开发,已经是后EJB时代的趋势( Expert One-On-One J2EE Development Without EJB )

2、核心思想

  1. 依赖注入(Dependnecy Injection)
  2. 面向切面编程(AOP)

3、简介

Spring框架是澳大利亚计算机专家Rod Johnson(罗德·约翰逊)于2002年发布,诞生以来发布了很多版本,目前最新版是5.X

Spring是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

4、版本发展史

版本 日期
Spring 0.9 2002 年
Spring 1.0 2004 年 03 月
Spring 2.0 2006 年 10 月
Spring 2.5 2007 年 11 月
Spring 3.0 2009 年 12 月
Spring 4.0 2013 年 12 月
Spring 5.0 2017 年 09 月
版本 时间 备注
Spring Boot 1.0 2014 年 4 月
Spring boot 1.1 2014 年 6 月 改进的模板支持,elasticsearch 和 apache solr 的自动配置
Spring Boot 1.2 2015 年 3 月 升级到 servlet 3.1 / tomcat 8 / jetty 9,spring 4.1 升级,支持 banner / jms / SpringBootApplication 注解
Spring Boot 1.3 2016 年 12 月 Spring 4.2 升级,新的 spring-boot-devtools,用于缓存技术(ehcache,hazelcast,redis 和 infinispan)的自动配置以及完全可执行的 jar 支持。
Spring boot 1.4 2017年1月 spring 4.3 升级,支持 couchbase / neo4j,分析启动失败和RestTemplateBuilder。
Spring boot 1.5 2017年2月 支持 kafka / ldap,第三方库升级
Spring boot 2.0 2018 年 03 月 基于 Java 8,支持 Java 9,支持 Quartz ,调度程序大大简化了安全自动配置,支持嵌入式 Netty

5、为什么要用Spring

5.1轻量

Spring 是轻量的,基本的版本大约2MB。

5.2控制反转(IOC)

Spring 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。

5.3面向切面的编程(AOP)

Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开。

5.4容器

Spring 包含并管理应用中对象的生命周期和配置。

5.5MVC框架

Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。

5.6事务管理

Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)

5.7异常处理

Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。

5.8框架

Spring可以将简单的组件配置、组合成为复杂的应用。

在Spring中,应用对象被声明式地组合,典型的是在一个XML文件里。

Spring也提供了很多基础功能(事务管理、持久化框架集成等),将应用逻辑的开发留给开发者。

5.9其它

简化开发,降低Java API的使⽤难度(针对数据层做了各种封装)。

6.spring 的核心模块(进阶)

6.1、 说明

​ Spring框架包含的功能大约由20个模块组成。这些模块按组可分为核心容器、数据访问/集成,Web,AOP(面向切面编程)、设备、消息和测试

6.2、结构图

万字长文之spring的整合_第1张图片

6.3、模块介绍

GroupId ArtifactId 说明
org.springframework spring-beans(重点) Beans 支持,包含 Groovy
org.springframework spring-aop(重点) 基于代理的AOP支持
org.springframework spring-aspects(重点) 基于AspectJ 的切面
org.springframework spring-context(重点) 应用上下文运行时,包括调度和远程抽象
org.springframework spring-context-support 支持将常见的第三方类库集成到 Spring 应用上下文
org.springframework spring-core(重点) 其他模块所依赖的核心模块
org.springframework spring-expression Spring 表达式语言,SpEL
org.springframework spring-instrument JVM 引导的仪表(监测器)代理
org.springframework spring-instrument-tomcat Tomcat 的仪表(监测器)代理
org.springframework spring-jdbc 支持包括数据源设置和 JDBC 访问支持
org.springframework spring-jms 支持包括发送/接收JMS消息的助手类
org.springframework spring-messaging 对消息架构和协议的支持
org.springframework spring-orm(重点) 对象/关系映射,包括对 JPA 和 Hibernate 的支持
org.springframework spring-oxm 对象/XML 映射(Object/XML Mapping,OXM)
org.springframework spring-test 单元测试和集成测试支持组件
org.springframework spring-tx(重点) 事务基础组件,包括对 DAO 的支持及 JCA 的集成
org.springframework spring-web(重点) web支持包,包括客户端及web远程调用
org.springframework spring-webmvc(重点) REST web 服务及 web 应用的 MVC 实现
org.springframework spring-webmvc-portlet 用于 Portlet 环境的MVC实现
org.springframework spring-websocket WebSocket 和 SockJS 实现,包括对 STOMP 的支持

6.4 spring的模块详解

6.4.1模块详解
  1. spring-core:依赖注入IoC与DI的最基本实现
  2. spring-beans:Bean工厂与bean的装配
  3. spring-context:spring的context上下文即IoC容器
  4. spring-context-support
  5. spring-expression:spring表达式语言
6.4.2 模块详解——面试考
  1. spring-core

    这个jar文件包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类

  2. spring-beans 模块

    这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI支持,引入spring-core.jar及spring- beans.jar文件就可以了

  3. spring-context 模块

    Spring核心提供了大量扩展,这样使得由 Core 和 Beans 提供的基础功能增强:这意味着Spring 工程能以框架模式访问对象。Context 模块继承了Beans 模块的特性并增加了对国际化(例如资源绑定)、事件传播、资源加载和context 透明化(例如 Servlet container)。同时,也支持JAVA EE 特性,例如 EJB、 JMX 和 基本的远程访问。Context 模块的关键是 ApplicationContext 接口。spring-context-support 则提供了对第三方库集成到 Spring-context 的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。

  4. spring-expression 模块

    为在运行时查询和操作对象图提供了强大的表达式语言。它是JSP2.1规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。

6.4.3Data Access/Integration - 数据访问与集成
6.4.3.1、概要

​ 数据访问与集成层包含 JDBC、ORM、OXM、JMS和事务模块。

6…4.3.2、详细说明
  1. spring-jdbc 模块

    提供了 JDBC抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。

  2. spring-tx 模块

    支持编程式事务和声明式事务,可用于实现了特定接口的类和所有的 POJO 对象。编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细。

  3. spring-orm 模块

    提供了对流行的对象关系映射 API的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring 的其它功能整合,比如前面提及的事务管理。

  4. spring-oxm

    模块提供了对 OXM 实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。

  5. spring-jms

    模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了 spring-messaging 模块

7 aop详解

7.1 spring-aop 模块

提供了面向切面编程(AOP)的实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦。使用源码级的元数据。

7.2 spring-aspects 模块

提供了对 AspectJ 的集成

8.Instrumentation

8.1spring-instrument

模块提供了对检测类的支持和用于特定的应用服务器的类加载器的实现。

8.2spring-instrument-tomcat

模块包含了用于 Tomcat 的Spring 检测代理。

9.Messaging - 消息处理

spring-messaging 模块

从 Spring 4 开始集成,从一些 Spring 集成项目的关键抽象中提取出来的,这些项目包括 Message、MessageChannel、MessageHandler 和其它服务于消息处理的项目。这个模块也包含一系列的注解用于映射消息到方法

10.web

10.1概要

​ Web 层包括 spring-web、spring-webmvc、spring-websocket、spring-webmvc-portlet 等模块。

10.2 详细说明

10.2.1 spring-web 模块

提供面向 web 的基本功能和面向 web 的应用上下文,比如 multipart 文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分

10.2.2 spring-webmvc 模块

为 web 应用提供了模型视图控制(MVC)和 REST Web 服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成

10.2.3 spring-webmvc-portlet 模块

(即Web-Portlet模块)提供了用于 Portlet 环境的 MVC 实现,并反映了 pring-webmvc 模块的功能

11.Test

11.1spring-test 模块

通过 JUnit 和 TestNG 组件支持单元测试和集成测试。它提供了一致性地加载和缓存 Spring 上下文,也提供了用于单独测试代码的模拟对象(mock object)

五、Spring 包依赖说明

  1. spring-core.jar依赖commons-collections.jar,spring-core.jar。
  2. spring-beans.jar依赖 spring-core.jar,cglib-nodep.jar
  3. spring-aop.jar依赖spring-core.jar,spring-beans.jar,cglib-nodep.jar,aopalliance.jar
  4. spring-context.jar依赖spring-core.jar,spring-beans.jar,spring-aop.jar,commons-collections.jar,aopalliance.jar
  5. spring-dao.jar依赖spring-core.jar,spring-beans.jar,spring-aop.jar,spring-context.jar
  6. spring-jdbc.jar依赖spring-core.jar,spring-beans.jar,spring-dao.jar
  7. spring-web.jar依赖spring-core.jar,spring-beans.jar,spring-context.jar
  8. spring-webmvc.jar依赖spring-core.jar/spring-beans.jar/spring-context.jar/spring-web.jar
  9. spring -hibernate.jar依赖spring-core.jar,spring-beans.jar,spring-aop.jar,spring- dao.jar,spring-jdbc.jar,spring-orm.jar,spring-web.jar,spring-webmvc.jar
  10. spring-orm.jar依赖spring-core.jar,spring-beans.jar,spring-aop.jar,spring- dao.jar,spring-jdbc.jar,spring-web.jar,spring-webmvc.jar
  11. spring -remoting.jar依赖spring-core.jar,spring-beans.jar,spring-aop.jar,spring- dao.jar,spring-context.jar,spring-web.jar,spring-webmvc.jar
  12. spring-support.jar依赖spring-core.jar,spring-beans.jar,spring-aop.jar,spring-dao.jar,spring-context.jar,spring-jdbc.jar
  13. spring-mock.jar依赖spring-core.jar,spring-beans.jar,spring-dao.jar,spring-context.jar,spring-jdbc.jar

下载地址:http://repo.spring.io/release/org/springframework/spring

maven仓库下载

六.Idear创建Spring工程

一、步骤

  1. 创建一个maven项目
  2. 添加spring库依赖
  3. 创建JavaBean类
  4. 添加applicationContext.xml配置文件
  5. 在配置文件中注册第三步的JavaBean
  6. 创建入口函数类

二、详解

1、创建一个maven项目

参照idea+maven创建工程

说明

  • src – 源码目录
    • main – 代码
      • java – Java代码目录
    • resources – 配置文件等资源目录
    • test – 测试代码
      • java – 测试Java代码目录
  • target – 编译后的输出目录

2、添加spring库依赖

修改跟目录下的pom.xml文件


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>com.vipgroupId>
    <artifactId>spring-exampleartifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>warpackaging>
    <name>spring-example Maven Webappname>
    <url>http://www.example.comurl>
    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
    properties>
    <dependencies>
         ...
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.1.9.RELEASEversion>
        dependency>
    dependencies>
	   ...
project>

3、创建JavaBean类

万字长文之spring的整合_第2张图片

/**
 * @author zhangwei
 */
public class User implements Serializable {
    private Integer uid;
    private String name;
    public User() {
    }
    public Integer getUid() {
        return uid;
    }
    public void setUid(Integer uid) {
        this.uid = uid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

4、添加配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-necoLWWy-1655729408699)(https://zhangwei-imgs.oss-cn-beijing.aliyuncs.com/superbed/2019/08/11/5d502cad451253d178413abb.png)]

5、注册javaBean


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.vip.shop.bean.User">
        <property name="name" value="hello"/>
    bean>
beans>

6、创建入口函数类测试

public class App {
    public static void main(String[] args) {
        /*
         * 初始化ApplicationContext, 参数为上一步的配置文件名字
         */
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        /* 从容器中获取对象 */
        User user = context.getBean("user", User.class);
        System.out.println(user);
    }
}

七 Spring的IOC (控制反转) 和DI(依赖注入)的详解

1.产生的原因

在采用面对对象方法设计的软件系统中,底层实现都是由N个对象组成的所有的对象通过彼此的合作,最终实现系统的业务逻辑。即软件系统中对象之间的耦合,对象A和对象B之间有关联,对象B又和对象C有依赖关系,这样对象和对象之间有着复杂的依赖关系,所以才有了Ioc这个思想。

2.什么是Ioc

1.ioc :Inversion of Control,翻译即控制反转,是一种设计思想

  1. 即把复杂系统分解成相互合作的对象,这些对象类通过封装之后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活的被重用和扩展。Ioc理论提出的观点大体是:借助 “第三方” ,实现具有依赖关系的对象之间的解耦
  2. 控制反转:即应用的本身不负责依赖对象的创建和维护,依赖对象的创建和维护是由外部容器负责的。即把控制权从应用转移到了外部容器,控制权的转移就是反转。

3.什么是依赖注入(DI)

1、概念

1.(谁被反转?):Martin Fowler(马丁·福勒)得出了答案:“获得依赖对象的过程被反转了”。即控制被反转之后,获得依赖对象的过程由自身管理对象变为由IoC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection,DI)”。他的这个答案,实际上给出了实现IoC的方法:注入。

2.依赖注入:就是Ioc容器在运行期间,动态的将某种依赖关系注入到对象中

3.所以,DI和Ioc是从不同角度描述,应用通过引入Ioc容器,利用依赖关系注入的方式,实现对象之间的解耦。

2.依赖关系的四种情况

  1. 说明

    对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系

  2. 四种情况

    • ClassA中某个方法的参数类型是ClassB; 这种情况成为耦合;
    • ClassA中某个方法的参数类型是ClassB的一个属性; 这种情况成为紧耦合;
    • ClassA中某个方法的实现实例化ClassB;
    • ClassA中某个方法的返回值的类型是ClassB;
  3. 如果出现了上述四种情况之一,两个类很有可能就是“依赖”关系。 依赖关系(Dependency):是类与类之间的连接,依赖总是单向的。依赖关系代表一个类依赖于另一个类的定义

3.总结

DI(依赖注入) 和 Ioc(控制反转)是同一个概念:

例子:

​ 当某个java实例需要另一个java实例协助时在传统的程序设计过程中,通常由调用者来穿件被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此成为控制反转;创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也成为依赖注入。

4.使用ioc的好处

  1. 可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。代码中的每一个Class都可以单独测试,彼此之间互不影响,只要保证自身的功能无误即可,这就是组件之间低耦合或者无耦合带来的好处。
  2. 降低对象之间的耦合,每个开发团队的成员都只需要关注自己要实现的业务逻辑,完全不用去关心其他人的工作进展,因为你的任务跟别人没有任何关系,你的任务可以单独测试,你的任务也不用依赖于别人的组件,再也不用扯不清责任了。所以,在一个大中型项目中,团队成员分工明确、责任明晰,很容易将一个大的任务划分为细小的任务,开发效率和产品质量必将得到大幅度的提高。
  3. 可复用性好,我们可以把具有普遍性的常用组件独立出来,反复应用到项目中的其它部分,或者是其它项目,当然这也是面向对象的基本特征。显然,IoC更好地贯彻了这个原则,提高了模块的可复用性。符合接口标准的实现,都可以插接到支持此标准的模块中。
  4. IoC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性

5.ioc原理

  • 创建bean实例
  • 根据配置文件装配bean
  • 为bean设置初始化参数
  • 管理bean的生命周期

八、Spring的xml的配置

一、概要

配置方式

Spring支持三种方式配置Bean,Spring1.0仅支持基于XML的配置,Spring2.0新增基于注解配置的支持,Spring3.0新增基于Java类配置的支持,Spring4.0则新增给予Groovy动态语言配置的支持。

  1. Explicit configuration in XML:显示的XML配置
  2. Explicit configuration in Java:显示的JavaConfig,基于java类配置
  3. Implicit bean discovery and automatic wiring:隐式的bean扫描,基于java注解配置,自动注入
  4. Explicit configuration in Groovy DSL:Groovy动态语言配置

在实际开发中前面三种方式都会用到,后面一种相对用的比较少,因为需要学习Groovy语言

使用原则

  1. 基于注解自动注入(优先使用)

    **优点:**在class文件中,降低维护成本。不需要第三方解析工具,利用java反射机制。编辑期就可以检验正确性,提高开发效率。

    **缺点:**如果需要对注解进行修改,那么要重新编译整个工程。Bean之间的关系不好把控。

  2. Java 接口和类中配置实现配置(Spring Boot的推荐方式)

    同隐式的bean扫描,基于注解

  3. XML配置(第三方框架,无法修改源代码的情况下)

    **优点:**简单易懂,配置文件修改不需要重新编译,扩展起来相对容易,比较典型的应用场景就是第三方的库中,一般都采用此种配置方式

    **缺点:**配置文件过多的时候难以管理

二、XML配置

使用 XML 装配 Bean 需要定义对应的 XML,这里需要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素,当我们在 IDEA 中创建 XML 文件时,会有友好的提示

三、通过XML配置加载Bean

整体结构

<beans>
    <bean id="" class="">
    	 <property name="" value=""/>
    </bean>
    <alias alias="alias" name="alias" />
    <import resource="xxx.xml" />
</beans>

配置文件

 
  
    
    <constructor-arg type="int" value="1"/>
    
    <constructor-arg name="xx" value="2"/>
    
    <constructor-arg index="0" value="3"/>
    
    <property name="xx">
        <ref bean="另外一个Bean的名字"/>
    property>
    
    <property name="xx" ref="另外一个Bean的名字"/>
    
    <property name="integerProperty" value="1"/>       
bean>

四、bean配置说明

id

  1. 说明

    • Bean 的名称,在 IOC 容器中必须是唯一的 , 代码中通过BeanFactory获取JavaBean实例时需以此作为索引名称
    • 命名必须以字母开头,可以使用*字母、数字、连字符、下划线、句号、冒号、 /
    • 若id没有指定,Spring 自动将**全限定名#{number}**作为 Bean 的名字,com.wener.example.bean.User#0
    • id 可以指定多个名字,名字之间可用逗号、分号、或空格分隔
  2. 栗子

    // java 代码
    public class User {
        private String name;
        private  String password;
    }    
    
    
     <bean id="user,userId" class="com.wener.example.bean.User"/>
    

class

  1. 说明

    一个类的全限定名(类的完整路径)

  2. 栗子

    
     <bean class="com.wener.example.bean.User"/>
    

lazy-init

  1. 说明

    默认false,懒加载,让Spring容器在Bean首次用到时创建Bean实例,而不是在应用一启动就创建

  2. 栗子

     <bean class="com.wener.example.bean.User" lazy-init="true"/>
    

九、Spring-xml的作用域

一、概要

scope 用来配置 spring bean 的作用域。

  • 在spring2.0之前,bean只有2种作用域:singleton(单例)、non-singleton(也称 prototype)。
  • Spring2.0以后,增加了session、request、global session三种专用于Web应用程序上下文的Bean。

二、可选值

类型 说明
singleton 默认值,在Spring IoC容器中仅存在一个Bean实例,Bean以单实例的方式存在
prototype 每次从容器中调用Bean时,都返回一个新的实例
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP session共享一个Bean,不同的HTTP session使用不同的Bean,该作用域仅适用于WebApplicationContext环境
globalSession 同一个全局Session共享一个Bean,一般用于Portlet环境,该作用域仅适用于WebApplicationContext环境

三、可选值详解

singleton(单例)

说明

当一个bean的 作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把 一个bean定义设置为singleton作用域时,Spring IOC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都 将返回被缓存的对象实例,这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中 只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时候,spring的IOC容器中只会存在一个该bean,如果不希望在容器启动时提前实例化singleton的Bean,可以使用lazy-init属性进行控制

栗子

<bean id="user" class="com.wener.example.bean.User" scope="singleton"/>
// 或者
<bean id="user" class="com.wener.example.bean.User" singleton="true"/>
private static void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    UserDao dao = context.getBean("userDao", UserDao.class);
    UserDao dao1 = (UserDao) context.getBean("userDao");
    System.out.println(dao);
    System.out.println(dao1);
}
// 两次输入的内存地址一致,说明是同一个对象
// com.wener.example.dao.impl.UserDaoImpl@46fa7c39
// com.wener.example.dao.impl.UserDaoImpl@46fa7c39

prototype

说明

设置为scope=”prototype”之后,每次调用getBean()都会返回一个新的实例

默认情况下,容器在启动时不会实例化prototype的Bean

Spring容器将prototype的Bean交给调用者后就不再管理它的生命周期

栗子

<bean id="user"
      class="com.wener.example.bean.User"
      scope="prototype">
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
User user = context.getBean("user", User.class);
User user1 = context.getBean("user",User.class);
System.out.println(user);
System.out.println(user1);
// 两次输入的内存地址不一样
// com.wener.example.bean.User@46fa7c39
// com.wener.example.bean.User@1fb700ee

十:Spring-xml 的生命周期

一、概要

Spring容器负责管理Bean的生命周期

  1. 创建bean实例
  2. 设置bean的属性值
  3. 调用初始化回调方法
  4. 使用Bean
  5. 调用销毁回调方法
  6. 销毁Bean

Bean在创建时,需要执行一些资源(数据库、套接字、文件)申请等初始化工作,可以在Bean的初始化回调方法中处理,此方法由Spring容器调用。

同样Bean在销毁时,需要执行一些资源(数据库、套接字、文件)申请等销毁工作,可以在Bean的销毁回调方法中处理,此方法由Spring容器调用。

二、核心属性

init-method

  1. 说明

    初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引用之前执行。一般用于一些资源的初始化工作。

  2. 示例代码

    public class User {
        public void init() {
            System.out.println("初始化");
        }
    }   
    
    <bean class="com.werner.di.User" 
          name="user"
          init-method="init"/>
    

destroy-method

  1. 说明:

    销毁方法,此方法将在BeanFactory销毁的时候执行,一般用于资源释放。

  2. 示例代码

    public class User {
        public void destroy() {
          System.out.println("销毁...");
        }
    }
    
    <bean class="com.wener.example.bean.User" 
          id="user"
       	  init-method="init" destroy-method="destroy"/>
    

十一:spring-xml-依赖注入

一、概要

XML配置文件中,在bean的定义中可配置该bean的依赖项,通常使用的配置方式有2种

  • 构造函数注入
  • Setter方法注入

二、构造函数注入

说明

constructor-arg 属性,根据XML中的配置,Spring容器首先创建所依赖Bean实例,然后传递给类的构造函数。通过指定构造方法的参数来实例化Bean 。

可选属性

属性 说明
type 根据参数的类型,避免构造方法冲突
value 用于指定字符串类型、基本类型的属性值
name 属性的名称
ref 关联其它类型
index 对应于构造函数的多个参数,index属性的值从0开始

栗子

public class Shop {
    private ShopDetail detail;
    private int shopId;
    private String title;
    private String name;
    public Shop() {
    }
  	// 构造方法传入 ShopDetail detail
  	public Shop(ShopDetail detail) {
      this.detail = detail
    }
    public Shop(int shopId, String title) {
        this.shopId = shopId;
        this.title = title;
    }

    public Shop(String title, String name) {
        this.title = title;
        this.name = name;
    }

    public Shop(int shopId, String title, String name) {
        this.shopId = shopId;
        this.title = title;
        this.name = name;
    }
    public int getShopId() {
        return shopId;
    }
    public void setShopId(int shopId) {
        this.shopId = shopId;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Shop{" +
                "shopId=" + shopId +
                ", title='" + title + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
<bean id="shop" class="com.wener.example.bean.Shop">
    
    <constructor-arg type="int" value="1"/>
    <constructor-arg type="java.lang.String" value="iPhone X"/>
bean>


<bean id="shop" class="com.wener.example.bean.Shop">
    
   <constructor-arg index="0" value="1"/>
   <constructor-arg index='title' value="手机"/>
   <constructor-arg index='2' value="iPhone X"/>
bean>


<bean id="shop" class="com.wener.example.bean.Shop">
    
   <constructor-arg name="id" value="1"/>
   <constructor-arg index='1' value="手机"/>
   <constructor-arg index='name' value="iPhone X"/>
bean>
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
Shop shop = context.getBean("shop", Shop.class);
System.out.println(shop.toString());

三、Setter方法注入

说明

property属性,根据XML中的配置,Spring容器调用类的Setter方法注入依赖项。

可选属性

属性 说明
name 属性的名称
value 主要是配置基本类型的属性值,
ref 但是如果我们需要为Bean设置属性值是另一个Bean实例时,这个时候需要使用元素。使用元素可以指定如下两个属性。bean:引用不在同一份XML配置文件中的其他Bean实例的id属性值。local:引用同一份XML配置文件中的其他Bean实例的id属性值

栗子

public class Shop {
    private ShopDetail detail;
    public void setDetail(ShopDetail detail) {
        this.detail = detail;
    }
    public ShopDetail getDetail() {
        return detail;
    }
}

public class ShopDetail {
    private String desc;
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
}
<bean id="shop" class="com.wener.example.bean.Shop">
			<property name="detail" ref="detail"/>
bean>

<bean id="detail" class="com.wener.example.bean.ShopDetail">bean>

Spring容器根据name调用setter方法:name对应“set”关键字后面的属性名,name="detail"对应于setDetail。

十二、spring的依赖注入的方式

public class Person {
private Hand hand;
private Footer footer;
private Head head;

public Person(Hand hand, Footer footer, Head head) {
    this.hand = hand;
    this.footer = footer;
    this.head = head;
  }

}

十三、Spring-xml的自动装配

一、概要

通过bean标签上autowire属性;或者在beans标签上通过default-autowire属性实现自动装配

二、可选值

可选值 说明
no 不使用自动装配。Bean的引用必须通过ref元素定义。
byName 根据属性名自动装配。BeanFactory查找容器中的全部Bean,找出其中id属性与属性同名的Bean来完成注入。如果没有找到匹配的Bean实例,则Spring不会进行任何注入
byType 如果BeanFactory中正好有一个同属性类型一样的bean,就自动装配这个属性。如果有多于一个这样的bean,就抛出一个致命异常,它指出你可能不能对那个bean使用byType的自动装配。如果没有匹配的bean,则什么都不会发生,属性不会被设置。如果这是你不想要的情况(什么都不发生),通过设置dependency-check="objects"属性值来指定在这种情况下应该抛出错误。
constructor 这个同byType类似,不过是应用于构造函数的参数。如果在BeanFactory中不是恰好有一个bean与构造函数参数相同类型,则一个致命的错误会产生。

三 栗子

java对象

public class Application { 
    private User user;
    
    public Application(User user) {
        this.user = user;
    }
    public User getUser() {
        return user;
    }
  
    public void setUser(User user) {
        this.user = user;
    }
}

 public class User implements Serializable {
      private String id;
      private String name;
      private String sex;
      private Integer age;
     
      public void destroy() {
      	  System.out.println("销毁!");
      }
      public void init() {
          System.out.println("初始化!");
} 

根据属性名来加载

 
<bean id="application" class="com.wener.example.bean.Application" autowire="byName"/> 

<bean id="user" class="com.wener.example.bean.User" />  

根据类型来加载


<bean id="user" class="com.wener.example.bean.User" /> 

<bean id="app" class="com.wener.example.bean.Application" autowire="byType"/>

根据构造方法来加载

// java代码
public Application(User user) {
        this.user = user;
}
// XML配置
<bean id="app" class="com.wener.example.bean.Application" autowire="constructor">
bean>

十四、spring-注解注入声明

一、概要

前期说明

注解本身没有功能的,就和xml一样。注解和xml都是一种元数据,元数据即解释数据的数据,这就是所谓配置。

Spring3的基于注解实现Bean依赖注入支持如下三种注解:

  • **Spring自带依赖注入注解:**Spring自带的一套依赖注入注解;
  • **JSR-250注解:**Java平台的公共注解,是Java EE 5规范之一,在JDK6中默认包含这些注解,从Spring2.5开始支持。
  • JSR-330注解:Java 依赖注入标准,Java EE 6规范之一,从Spring3开始支持;

注解配置对比XML配置

  • 注解配置比XML配置更简洁,Spring注解方式减少了配置文件内容,更加便于管理,并且使用注解可以大大提高了开发效率!
  • XML注入会在注解注入之后执行,所以XML配置将覆盖注解配置。

启用注解配置

默认情况下,Spring容器没有启用注解配置。需要在Bean的XML配置文件里打开组件扫描功能,启用注解配置。


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       
<context:component-scan base-package="com.wener.example"/>
beans>

二、注册bean的注解

Spring容器扫描指定包路径下的所有类,每当找到1个@Component注解,就会注册Bean,同时设置Bean ID。

**注: **默认情况下Bean ID就是类名,但首字母小写。如果类名以连续几个大写字母开头,首字母不小写。(即HELLOService -> HELLOService)

常用Spring的声明的注解,

注解 作用域 说明
@Component 注解在类上,可以作用在任何层次。 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。是一个泛化的概念,仅仅表示一个组件 (Bean) ,将一个实体类,放入Spring中。
@ Service 注解在类上 用于标注业务层组件
@ Controller 注解在类上 用于标注控制层组件
@ Repository 注解在类上 用于标注数据访问组件,即DAO组件。

1、Component

说明

表示 Spring IoC 会把这个类扫描成一个 bean实例,而其中的 value 属性代表这个类在 Spring 中的 id,这就相当于在 XML 中定义的 Bean 的 id,甚至直接写成 @Component,对于不写的,Spring IoC 容器就默认以类名来命名作为 id,只不过首字母小写,配置到容器中。

栗子

import org.springframework.stereotype.Component;
// 方式一 设置bean的id,理论上只要符合java的命名规范的名字都可以,但是必须是唯一的
@Component(value = "user")
public class User {
}
// 方式二 注解中value命名的属性可以省略不写
@Component("user")
public class User {
}
// 方式三 甚至可以直接什么都不加
@Component
public class User {
}

2、Controller

说明

当一个组件代表业务层时,可以使用@Service进行注解,bean 的ID 默认为类名称开头字母小写

栗子

@Controller('accountController')
public class AccountController {
    
}

//或者,
@Controller
public class AccountController {
}

3、Service

说明

通常用于注解Service类,也就是服务层

栗子

@Service
public class AccountServiceImpl implements AccountService {
}

4、Repository

说明

当一个组件代表数据访问层(DAO)的时候,使用@Repository进行注解 ,bean 的ID默认为类名称开头字母小写

栗子

@Repository
public class UserDaoImpl implements UserDao {
	...
}
//或者
@Repository("userDao")
public class UserDaoImpl implements UserDao {
	...
}

三、总结

  1. 被注解的java类当做Bean实例,Bean实例的名称默认是Bean类的首字母小写,其他部分不变。

  2. Controller 、@Repository、@Controller、 @Service可以自定义Bean名称,理论上只要符合java的命名规范的名字都可以,但是必须是唯一的,

  3. 尽量使用对应组件注解的类替换@Component注解,在spring未来的版本中,@Controller,@Service,@Repository会携带更多语义。并且便于开发和维护!

  4. 指定了某些类可作为Spring Bean类使用后,在Spring配置文件加入如下配置

    <context:component-scan base-package="自动扫描指定包及其子包下的所有Bean类"/>
    

十五、spring-注解的依赖注入

一、概要

自动装配是指Spring 在装配 Bean 的时候,根据指定的自动装配规则,将某个 Bean 所需要引用类型的 Bean 注入进来。可以在类的成员变量上,构造方法,setter方法使用,常用的主要有以下三种

  • **@Autowired:**属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值
  • **@Resource:**不属于spring的注解,而是来自于JSR-250位于java.annotation包下
  • **@Inject:**属于由JSR-330提供

二、@Autowired

1、说明

这个注解相当于我们之前在xml文件中配置的autowire=“constructor/byName/byType”,只不过我们这里使用@Autowired方式注解方式,且默认是通过类型判断,意思就是不使用byName,和construtor。通过@Autowired注解,spring会自动去容器中查找对应的类型,注入到该属性中,且bean类中,使用@Autowired注解其属性,我们可以不用提供getter,setter方法

默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如果我们想使用按照名称装配,可以结合@Qualifier注解一起使用

2、属性注入(个人喜欢)

  1. 说明

    将Autowired注解声明在属性上面,

  2. 示例代码

    @Component
    public class User {
        private String name;
        private String password;
        @Autowired
        //@Autowired(required = false)
        private Address address;
        
    @Component
    public class Address {
        private String province;
        private String city;
    }
        
    // 测试代码
    private static void testUser() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.getAddress().toString());
        }   
    
  3. 优点

    • 代码简洁
  4. 缺点

    • 对于IOC容器以外的环境,无法复用该实现类

3、构造注入(官方推荐)

  1. 说明

    将Autowired注解声明在构造方法上面,在Spring4.x版本中推荐的注入方式

  2. 示例代码

    @Component
    public class User {
        private String name;
        private String password;
        private final Address address;
        
        @Autowired
        public User(Address address) {
            this.address = address;
        }
    
    @Component
    public class Address {
        private String province;
        private String city;
    }
    
    
  3. 优点

    • **能确保依赖组件不可变:**主要是属性通过final修饰
    • **能确保依赖不为空:**当要实例化Bean的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,当我们Spring容器中有该种类型的参数直接传入,没有该种类型的参数直接报错,无需判断依赖对象是否null
    • **保证返回客户端(调用)的代码的时候是完全初始化的状态:**向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法)所以返回来的都是初始化之后的状态
    • 保证必要属性在Bean实例化时就得到设置
  4. 缺点

    • **可读性较差:**当注入参数较多时,代码臃肿。
    • **灵活性不强:**在有些属性是可选的情况下,如果通过构造函数注入,也需要为可选的参数提供一个null值
    • **不利于类的继承和拓展:**因为子类需要引用父类复杂的构造函数
  5. 备注(官方说明)

    The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.

    Spring团队通常提倡构造函数注入,因为它能够保证注入的组件不可变,并且确保需要的依赖不为null。此外。此外,构造器注入的组件总是以完全初始化的状态返回给客户机(调用)代码。

4、方法注入(不推荐)

  1. 说明

    将Autowired注解声明在方法上面,Spring3.x的时候,官方推荐使用的注入

  2. 示例代码

    @Component
    public class User {
        private String name;
        private String password;
        private  Address address;
        
        @Autowired
        public void setAddress(Address address) {
            this.address = address;
        }
    
    @Component
    public class Address {
        private String province;
        private String city;
    }
    
  3. 优点

    • 相比构造器注入,当注入参数太多或存在非必须注入的参数时,不会显得太笨重,Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题
    • 允许在类构造完成后重新注入
  4. 缺点

    • 对一些必要参数需要做代码检查
    • 开发的效率相对来说比较低(增加了代码量)
    • 可读性不是很好

5、@Qualifier

1、说明

当Spring容器中存在多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。

该注解可以使用字段、方法、参数、注解上

2、基础使用
  1. DataSource

    public interface DataSource {
         void connection();
    }
    
  2. MysqlDataSource

    @Component("mysql")
    public class MysqlDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("mysql database connecting");
        }
    }
    
  3. OracleDataSource

    @Component("oracle")
    public class OracleDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("oracle database connecting");
        }
    }
    
  4. DataSourceManager

    // 在属性上使用
    @Component
    public class DataSourceManager {
        @Autowired
        @Qualifier("oracle")
        private DataSource dataSource;
    	
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    
    // 或者在参数上使用
    @Component
    public class DataSourceManager {
        private DataSource dataSource;
    	
        @Autowired
        public DataSourceManager(@Qualifier("oracle") DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    
    // 在方法上使用
    @Component
    public class DataSourceManager {
        private DataSource dataSource;
     
        @Autowired
        @Qualifier(value = "oracle")
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    	
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    
    
3、自定义Qualifier
  1. 说明

    对@Qualifier的扩展来提供细粒度选择候选者;具体使用方式就是自定义一个注解并使用@Qualifier注解其即可使用

  2. 示例代码

    import org.springframework.beans.factory.annotation.Qualifier;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    // 定义两个自定义注解类一个OracleQualifier 一个MysqlQualifier
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface OracleQualifier {
    }
    
    
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface MysqlQualifier {
    }
    
    
    // 在实现类上使用自定义Qualifier
    @Component("mysql")
    @MysqlQualifier
    public class MysqlDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("mysql database connecting");
        }
    }
    
    @Component("oracle")
    @OracleQualifier
    public class OracleDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("oracle database connecting");
        }
    }
    
    
    
    @Component
    public class DataSourceManager {
        @Autowired()
        @MysqlQualifier
        private DataSource mysqlDataSource;
    }
    
    
    

    其它方式自行参考其它资料

6、循环引入的问题

  1. 属性注入

    @Component
    public class User {
        private String name;
        private String password;
        @Autowired
        private Address address;
        
        public Address getAddress() {
            return address;
        }
    }
    
    @Component
    public class Address {
        private String province;
        private String city;
        @Autowired
        private User user;
        public User getUser() {
            return user;
        }
    }
    
     // 测试代码 
        private static void testUser() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.getAddress());
        }
    //异常信息 BeanCurrentlyInCreationException
    
    
    
  2. 构造方法注入

    @Component
    public class User {
        private String name;
        private String password;
        private Address address;
        
        @Autowired
        public User(Address address) {
            this.address = address;
        }
    }    
    
    @Component
    public class Address {
        private String province;
        private String city;
        private User user;
    
        @Autowired
        public Address(User user) {
            this.user = user;
        }
    
    // 测试代码
    private static void testUser() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.toString());
        }  
     // 异常信息 BeanCurrentlyInCreationException
    
    
    
  3. 区别

    如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException从而提醒你避免循环依赖,如果是属性注入的话,启动的时候不会报错,在使用那个bean的时候才会报错

7、总结

@Autowired的三种用法其实没有所谓的孰优孰劣,存在即是合理。个人建议,对于依赖关系无需变化的注入,可以采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入,不过个人比较喜欢用属性多点

三、@Resource(推荐)

1、说明

  • @Resource是JSR250标准中的一个注解,Spring2.5+对其提供了支持。
  • @Resource的作用相当于 @Autowired,只不过 @Autowired 按 byType 自动注入,而@Resource 默认按 byName 自动注入罢了。
  • @Resource可以使用在类,属性,set方法上,也可以是普通的非set方法上,注意对应方法只允许接收一个参数
  • @Resource有两个属性是比较重要的,分是name和type

2、属性注入

说明

将@Resource注解声明在属性上面

栗子

import org.springframework.stereotype.Component;
import javax.annotation.Resource;

@Component
public class User {
    private String name;
    private String password;
    // 会如果什么都不写 会根据属性的名字来查找
    // 如果找不到与名称匹配的bean时才按照类型进行装配,找不到直接报错
    @Resource
    // 如果写了name,那只会按着name来查找,找不到就直接报错
    // @Resource(name = "address")
    // @Resource(name = "address", type = Address.class)
    private Address address;
 }   

@Component
public class Address {
    private String province;
    private String city;
}

//测试代码
private static void testUser() {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user.getAddress().toString());
}


3、方法注入

说明

在方法上使用@Resource

栗子

@Component
public class User {
    private String name;
    private String password;
    private Address address;

    public Address getAddress() {
        return address;
    }
    
	// 当注解写在方法上时,默认取字段名进行安装名称查找
    @Resource
    // @Resource(name = "address")
    // @Resource(name = "address", type = Address.class)
    public void setAddress(Address address) {
        this.address = address;
    }
    
//    或者
//    @Resource
//    @Resource(name = "address")
//    @Resource(name = "address", type = Address.class)
//    public void initAddress(Address address) {
//        this.address = address;
//    }
    
    
   // 错误的案例
   // 注意修饰方法的时候,有且只能有一个参数,多余一个参数直接报错
   @Resource
    public void initAddress(Address address, String name) {
        this.address = address;
        this.name = name;
    }
}
// @Resource annotation requires a single-arg method


4、重要属性说明

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则按类型进行匹配,如果匹配则自动装配
  5. 如果既指定了name又指定了type,则按照名字和类型装配,任何一个不匹配都将报错
  6. 如果 @Resource用于方法中,默认使用方法名作为beanName,指定名字则使用名字

四、@Inject(了解)

1、说明

@Inject是JSR-330的一部分。在Spring3中开始支持JSR-330的注解

@Inject支持构造函数、方法和字段注解,也可能使用于静态属性。与@Autowired不同的是强制要求示例必须存在

注意: 需要导入第三方的jar包

<dependency>
 <groupId>javax.injectgroupId>
 <artifactId>javax.injectartifactId>
 <version>1version>
dependency>

2、属性注入

  1. 说明

    在属性上声明,注意属性不能是final的

  2. 示例代码

    @Component
    public class User {
        @Inject
        private Address address;
    }  
    
    
    

3、构造方法注入

  1. 说明

    在构造方法上声明,构造函数可以是无参或多个参数的构造函数,@Inject每个类中最多注解一个构造函数。

  2. 示例代码

    @Component
    public class User {
        private Address address;
    
        @Inject
        public User(Address address) {
            this.address = address;
        }
    }    
    
    
    

4、方法注入

  1. 说明

    在方法上声明,注意不能是抽象方法,可以有0个或多个参数。

  2. 示例代码

    @Component
    public class User {
        private Address address;
        public Address getAddress() {
            return address;
        }
    
        @Inject
        public void setAddress(Address address) {
            this.address = address;
        }
    
    
    

5、配合@Name使用

  1. 说明

    @Inject默认按类型匹配,如果你想按着Bean的名字来使用,可以使用@Name属性使用,一般用来类上面声明Bean的名字@Component的作用,如何在注入的时候在属性上声明相当于@Qualifier

  2. 示例代码

    // 在类上声明 
    @Named("address1")
    public class Address {
        
    }  
    
    // 配合@Inject一起使用
    @Component
    public class User {
        @Inject
        @Named("address")
        private Address address1;
    }
    
    
    

6、与@Autowire的区别

  1. @Autowire 有@required标签,允许对象为空
  2. @Inject没有@required标签,强制要求对象不能为空

7、其它

  1. @Inject 与 @Autowired等效(作用上)
  2. @Named 与 @Compenet等效(类上声明时)

五、三种注解区别

  1. 条件

    注解类型 所在包 版本支持 作用域
    @AutoWired Spring自带的方式 Spring 2.5+ 可以用在构造器、方法、属性、参数、注解上面
    @Resource JSR-250标准,JDK6以上自带, Spring版本要求2.5以上 可以用在方法、属性、类上
    @Inject JSR-303标准, Spring版本3以上。需要导入外部依赖 可以用在方法、属性、构造器上
  2. 使用上

    1、@Autowired、@Inject用法基本一样,不同的是@Autowired有一个required属性

    2、@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的

    3、@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Name一起使用

    4、@Autowired可以允许对象为空,而@Resource与@Inject不允许对象为空

六、另外两种注解

@Qualifier

当引入的对象存在多个实例配合使用与@Autowired一起使用的。@Qualifier可以被用在单个构造器或者方法的参数上。当上下文有几个相同类型的bean, 使用@Autowired则无法区分要绑定的bean,此时可以使用@Qualifier来指定名称。

@Primary

就是当Spring容器扫描到某个接口的多个 bean 时,如果某个bean上加了@Primary 注解 ,则这个bean会被优先选用

十六、 spring-javaconfig

一、概要

前面介绍了Bean的XML配置方法,从Spring 3.0开始,可以使用java代码配置Bean,替代XML配置。Java配置与注解配置不同,Java配置是把Java代码文件当作配置文件,注解配置是在实际Java类中使用注解设置依赖关系。Java配置也会用到一些注解,主要有:@Configuration@ComponentScan@Bean

Spring Boot中彻底抛弃了xml配置 后期推荐使用此种方式

二、通过@Configuration注解创建Java配置类

@Configuration注解标注的类是配置类,用于配置Bean之间依赖关系。

@Import注解允许从另一个配置Java/XML文件加载bean定义。

栗子

// 表明这是个Bean的Java配置类
@Configuration 
public class DruidConfig {
}

三、 通过@Bean注解定义Bean

要定义一个Bean,可以通过:

  1. 给一个方法加@Bean注解
  2. 方法返回Bean实例

Spring容器会注册这个Bean,并将方法名作为Bean ID。

栗子

@Configuration 
public class SpringConfig {

 // 定义 App Bean
 // 指定初始化回调,销毁回调
 @Bean(initMethod = "init", destroyMethod = "close" ) 
 // 设置Bean作用域
 @Scope("prototype")
 public App app() { 
 // 返回App Bean
  return new App();
 }
}

四、注入Bean依赖关系

可以通过让一个Bean方法调用另一个Bean方法注入依赖项。

栗子

@Configuration
public class SpringConfig {

  // 定义 App Bean
  @Bean
  public App app() 
    // 调用Bean方法logger()注入Logger Bean实例
    return new App(); 
  }
}

五、 读取配置类

可以使用AnnotationConfigApplicationContext读取配置类。

示例:Test.java

public class Test {
public static void main(String[] args) {
  // 使用`AnnotationConfigApplicationContext`读取配置类
  ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
 }
}

六、通过Spring容器获取bean

一、概要

前面介绍了Bean的XML配置方法,从Spring 3.0开始,可以使用java代码配置Bean,替代XML配置。Java配置与注解配置不同,Java配置是把Java代码文件当作配置文件,注解配置是在实际Java类中使用注解设置依赖关系。Java配置也会用到一些注解,主要有:@Configuration@ComponentScan@Bean

Spring Boot中彻底抛弃了xml配置 后期推荐使用此种方式

二、通过@Configuration注解创建Java配置类

@Configuration注解标注的类是配置类,用于配置Bean之间依赖关系。

@Import注解允许从另一个配置Java/XML文件加载bean定义。

栗子

// 表明这是个Bean的Java配置类
@Configuration 
public class DruidConfig {
}

三、 通过@Bean注解定义Bean

要定义一个Bean,可以通过:

  1. 给一个方法加@Bean注解
  2. 方法返回Bean实例

Spring容器会注册这个Bean,并将方法名作为Bean ID。

栗子

@Configuration 
public class SpringConfig {

 // 定义 App Bean
 // 指定初始化回调,销毁回调
 @Bean(initMethod = "init", destroyMethod = "close" ) 
 // 设置Bean作用域
 @Scope("prototype")
 public App app() { 
 // 返回App Bean
  return new App();
 }
}

四、注入Bean依赖关系

可以通过让一个Bean方法调用另一个Bean方法注入依赖项。

栗子

@Configuration
public class SpringConfig {

  // 定义 App Bean
  @Bean
  public App app() 
    // 调用Bean方法logger()注入Logger Bean实例
    return new App(); 
  }
}

五、 读取配置类

可以使用AnnotationConfigApplicationContext读取配置类。

示例:Test.java

public class Test {
public static void main(String[] args) {
  // 使用`AnnotationConfigApplicationContext`读取配置类
  ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
 }
}

六、通过Spring容器获取bean

6.1概要

前面介绍了Bean的XML配置方法,从Spring 3.0开始,可以使用java代码配置Bean,替代XML配置。Java配置与注解配置不同,Java配置是把Java代码文件当作配置文件,注解配置是在实际Java类中使用注解设置依赖关系。Java配置也会用到一些注解,主要有:@Configuration@ComponentScan@Bean

Spring Boot中彻底抛弃了xml配置 后期推荐使用此种方式

6.2 通过@Configuration注解创建Java配置类

@Configuration注解标注的类是配置类,用于配置Bean之间依赖关系。

@Import注解允许从另一个配置Java/XML文件加载bean定义。

栗子

// 表明这是个Bean的Java配置类
@Configuration 
public class DruidConfig {
}

6.3通过@Bean注解定义Bean

要定义一个Bean,可以通过:

  1. 给一个方法加@Bean注解
  2. 方法返回Bean实例

Spring容器会注册这个Bean,并将方法名作为Bean ID。

栗子


@Configuration 
public class SpringConfig {

 // 定义 App Bean
 // 指定初始化回调,销毁回调
 @Bean(initMethod = "init", destroyMethod = "close" ) 
 // 设置Bean作用域
 @Scope("prototype")
 public App app() { 
 // 返回App Bean
  return new App();
 }
}

6.4 注入Bean依赖关系

可以通过让一个Bean方法调用另一个Bean方法注入依赖项。

栗子

@Configuration
public class SpringConfig {

  // 定义 App Bean
  @Bean
  public App app() 
    // 调用Bean方法logger()注入Logger Bean实例
    return new App(); 
  }
}

6.5 读取配置类

可以使用AnnotationConfigApplicationContext读取配置类。

示例:Test.java

public class Test {
public static void main(String[] args) {
  // 使用`AnnotationConfigApplicationContext`读取配置类
  ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
 }
}

6.6通过Spring容器获取bean

App app = context.getBean("app", App.class);

十七、Spring-aop面向切面编程Spring-AOP切面编程(1)

一、概要

软件开发一直在寻求一种高效、护展、维护的方式。

面向对象的特点是封装继承、多态。而封装的核心就是将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类的复用性增加。但是新的问题又来了,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。

也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程,我们把**切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。**有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

参考资料

二、面向切面编程

1、什么切面编程

面向切面编程(也叫面向方面):Aspect Oriented Programming(AOP)。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP是OOP的补充和完善

2、主要解决的问题

  1. 解决代码复用的问题
  2. 解决关注点分离
    • 水平分离: 例如 控制层—服务层—数据库
    • 垂直分离: 例如 用户模块—商品模块
    • 关注点分离(功能): 业务和非业务

三、实例讲解

1、原始代码

  1. 需求

    比如我们要写一个UserService,在调用保存用户和删除用户的时候需要去打开数据库和开启事务

  2. 示例代码

    万字长文之spring的整合_第3张图片

  3. 说明

    通过上面的代码我们发现打开数据,开启事务,提交事务,关闭数据库是重复的代码,有重复的代码我们怎么办?

    通过类封装成方法提取重复的代码啊!(抽取成类的方式我们称之为:纵向抽取)

2、提取类封装成方法

  1. 说明

    这个时候我们新建一个DBManager类 定义四个方法

  2. 示例代码

    public class DBManager {
        public void open() {
            System.out.println("打开数据库...");
        }
        public void colse() {
            System.out.println("关闭数据库...");
        }
        public void begin() {
            System.out.println("开启事务");
        }
        public void commit() {
            System.out.println("提交事务");
        }
    }
    
    public class UserService {
        private DBManager manager = new DBManager();
    
        public void save() {
            manager.open();
            manager.begin();
            System.out.println("保存用户信息");
            manager.commit();
            manager.colse();
        }
        
        public void delete() {
            manager.open();
            manager.begin();
            System.out.println("删除用户");
            manager.commit();
            manager.colse();
        }
    }
    
  3. 说明

    通过上面的案例解决了代码重复性的问题,但同时也会带来另外两个问题:

    • 耦合度:会造成封装类和业务类的耦合,
    • 侵入性强:被我们提取的逻辑代码还是融合到业务逻辑中

    但是,这种做法相比最原始的代码写法,已经有了一些的改进,那么有没有一种方案能解决耦合度,侵入性强的问题,

    答案就是代理模式

四、代理模式

1、代理模式概要

代理模式是一种非常好理解的一种设计模式,举几个简单的代理的例子

  • 比如玩游戏升级太麻烦了,这个是时候我们可以去请代练帮我们升级,那这个代练其实就是一个代理
  • 比如我们回家过年买不到火车票,通常加价请第三方的一些黄牛帮我们去买
  • 歌星或者明星都有一个自己的经纪人,这个经纪人就是他们的代理人,当我们需要找明星表演时,不能直接找到该明星,只能是找明星的经纪人

让代练帮我们升级,我们可能就想下副本,让黄牛帮我们买票,我们就的目的就是想回家,明星让经纪人接拍电影,明星只是需要去拍电影就行了。无论是游戏代练,黄牛,还是经纪人其它他们其实都是在帮我们在做事(做一些我们不想做,或者做不来的事情),但并不能把所有的事情都帮我们做了,比如黄牛帮我们买点票了,回家你还的自己回吧,

2、分类

  1. 静态

    由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

  2. 动态

    在程序运行时运用反射机制动态创建而成。

3、作用

代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但必须,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法

五、静态代理

1、说明

静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了

步骤

1:将业务抽象为接口

2:代理对象和被代理对象都实现该接口

3:代理对象持有被代理对象的引用,在执行具体核心业务时交由被代理对象完成。

2、示例代码

  1. 业务代码

    /**
     * 简单业务层接口
     */
    public interface UserService{
        public void saveUser();
    }
    
    /**
     * 业务层实现类,实现save方法
     */
    public class UserServiceImpl implements UserService{
    
        @Override
        public void saveUser() {
            System.out.println("2:保存用户信息");
        }
    }
    
    
  2. 代理类

    /**
     * 代理类
     */
    public class UserServiceProxy implements UserService{
    
        private UserService userService;
        
        public UserServiceProxy(UserService userService) {
            super();
            this.userService = userService;
        }
    
        public void open(){
            System.out.println("1:打开数据库连接");
        }
        public void close(){
            System.out.println("3:关闭数据库连接");
        }
        @Override
        public void saveUser() {
            this.open();
            userService.saveUser();
            this.close();
        }
        
    }
    
  3. 测试代码

    /**
     * 测试类
     */
    public class TestProxy {
        
        public static void main(String[] args) {
            UserService userService =new UserServiceProxy(new UserServiceImpl());
            userService.saveUser();
        }
    }
    

3、分析

3.1、优点

  1. 代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)

3.2、缺点

  1. 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度
  2. 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了

由于每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类

所以我们就会想能不能通过一个代理类完成全部的代理功能?,那么我们就需要用动态代理

六、JDK动态代理

1、概念

**JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象 以及方法名 ,动态地创建了一个代理类并执行,然后通过该代理类对象进行方法调用。**我们需要做的,只需指定代理类的预处理、调用后操作即可

目前Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。 其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

  • Proxy类主要用来获取动态代理对象,
  • InvocationHandler接口用来约束调用者实现

2、重要类介绍

2.1、Proxy

  1. 说明

    这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象

  2. 核心方法

    返回值 方法 说明
    static InvocationHandler getInvocationHandler(Object proxy) 方法返回指定接口的代理类的实例,这些接口将调用方法调用到指定的调用处理程序。
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static boolean isProxyClass(Class cls) 该方法用于判断指定类对象是否是一个动态代理类
    staticObject newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 用于为指定类装载器、一组接口及调用处理器生成动态代理类实例

2.2、InvocationHandler

  1. 说明

    这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问

  2. 语法

    // 该方法负责集中处理动态代理类上的所有方法调用。,第二个参数是被调用的方法对象
    // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    Object invoke(Object proxy, Method method, Object[] args)
    
  3. 参数说明

    • proxy

      第一个参数既是代理类实例

    • method

      被调用的方法对象

    • args

      调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行

3、示例代码

3.1、声明代理类

  1. 说明

    动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类

  2. 示例代码

     
    
    // 第一步  定义代理类,实现InvocationHandler接口
     public class DynamicProxy implements InvocationHandler {
        //代理目标对象
        private Object target;
        public Object newProxyInstance(Object object) {
      	this.target = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader()
                , object.getClass().getInterfaces(),
                this);
     }
     
        /**
         * 关联的这个实现类的方法被调用时将被执行
         * @param proxy  定义代理类的类的实例
         * @param method 代理类要实现的接口列表
         * @param args   指派方法调用的调用处理程序
         * @return 返回执行的目标对象
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("====start====");
            /**
             * 打印方法的参数
             */
            if (args != null && 0 != args.length) {
                for (Object arg : args) {
                    System.out.println(arg);
                }
            }
            System.out.println("====方法被执行前====");
            Object invoke = null;
            try {
                String name = method.getName();
                if (name.equals("add") || name.equals("delete")) {
                    // 核心方法被执行
                    invoke = method.invoke(target, args);
                    System.out.println("====方法被执行后====");
                }
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                System.out.println("====方法出错误了====");
            }
            System.out.println("====end====");
            return invoke;
        }
    
     }
    

3.2 声明代理接口与代理类

  1. 说明

    jdk动态代理只能代理接口,所以第一步先声明接口,然后在定义个实现类

  2. 示例代码

     //代理接口类
     public interface UserDao {
       void add(String name);
       void delete(String uid);
     }
    
    public interface ShopDao {
        boolean addShop();
    }
    
    
     //代理接口的实现类
     public class UserDaoImpl implements UserDao {
        @Override
        public void add(String name) {
            System.out.println("add:===" + name);
        }
        @Override
        public void delete(String uid) {
            System.out.println("delete" + uid);
     }
         
         
    public class ShopDaoImpl implements ShopDao {
        @Override
        public boolean addShop() {
            System.out.println("核心方法====添加商品信息");
            return false;
        }
    }     
         
    
  3. 测试代码

    public class TestProxy {
        public static void main(String[] args) {
            DynamicProxy proxy = new DynamicProxy();
    
            UserDao userDao = (UserDao) proxy.newProxyInstance(new UserDaoImpl());
            userDao.add();
            userDao.delete();
    
            ShopDao shopDao = (ShopDao) proxy.newProxyInstance(new ShopDaoImpl());
            shopDao.addShop();
    
        }
    }
    

3.3、总结

可以看到,我们可以通过DynamicProxy代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作

七、CGLib动态代理

1、说明

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

因为是第三方的所有需要导入第三方的jar包

 
    <bean id="target" class="com.wener.example.AOP.base.UserDaoImpl"/>
    
    <bean id="afterAdvice" class="com.wener.example.AOP.base.TestAfterAdvice"/>
    <bean id="beforeAdvice" class="com.wener.example.AOP.base.TestBeforeAdvice"/>
    <bean id="surroundAdvice" class="com.wener.example.AOP.base.TestSurroundAdvice"/>
    <bean id="throwingAdvice" class="com.wener.example.AOP.base.TestThrowingAdvice"/>
    
    
    
    
    <bean id="proxy"
          class="org.springframework.AOP.framework.ProxyFactoryBean"
          p:interceptorNames-ref="advices"
          p:target-ref="target"
          p:proxyTargetClass="fasle">
    bean>
    
    <util:list id="advices">
        <value>afterAdvicevalue>
        <value>surroundAdvicevalue>
        <value>surroundAdvicevalue>
        <value>throwingAdvicevalue>
    util:list>
beans>

  • 测试代码

    private static void testAop() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop-base.xml");
            UserDao dao = (UserDao) context.getBean("proxy");
        	// 获取FactoryBean本身
            //ProxyFactoryBean bean = (ProxyFactoryBean) context.getBean("$proxy");
            dao.save(new User());
        }
    
    
  • 注意

    • ProxyFactoryBean,就是一个bean对象,不要被前面的Factory扰乱误导,也是要放入BeanFactory被spring管理。

    • ProxyFactoryBean特殊在通过常规的ApplicationContext.getBean(bean Id) 获取的不是FactoryBean这个直接对象,而是调用FactoryBean.getObject()生成的对象,返回给你。

    • ApplicationContext.getBean(&bean Id) ,加上&才能取得FactoryBean这个对象。

    • FactoryBean这样的过程,就是为了方便你定义生成【复杂bean】对象,就是这个bean对象不是简单的new ,设置几个参数,有其他初始化才能完整被使用,比如ProxyFactoryBean。具体执行代码如下:

      ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop-base.xml");
      ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
      proxyFactoryBean.setBeanFactory(ac.getBeanFactory());
      //AOP拦截处理类
      proxyFactoryBean.setInterceptorNames("afterAdvice");
      //代理的接口
      proxyFactoryBean.setInterfaces(UserDao.class);
      //被代理对象
      proxyFactoryBean.setTarget(ac.getBean(UserDao.class));
      //放入bean工厂,实际开发是在config下使用注解,设置多个proxyFactoryBean代理,设置不同bean id
      ac.getBeanFactory().registerSingleton("proxy",proxyFactoryBean);
      UserDao userDao = (UserDao) ac.getBean("proxy");
      userDao.save();
      //获取直接的ProxyFactoryBean对象,加&
      System.out.println(ac.getBean("&proxy"));
      
      
  • 五、纯POJO切面编程(了解)

    1、说明

    开发步骤

    1. 创建目标类:定义接口和和接口实现类(jdk动态代理)或者定义类(cglib)
    2. 定义通知
    3. 配置Spring IOC容器

    2、示例代码

    1. 添加引用spring-aspects包

      
      <dependency>
          <groupId>org.springframeworkgroupId>
          <artifactId>spring-aspectsartifactId>
          <version>5.1.4.RELEASEversion>
      dependency>
      
      
    2. 定义接口

      public interface PojoDao {
          public void test();
      }
      
      
    3. 定义接口实现类

      public class PojoDaoImpl implements PojoDao {
          @Override
          public void test() {
              System.out.println("核心测试方法");
          }
      }
      
      
    4. 定义切面类(横切处理类)

      package com.wener.example.aop.pojo;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      /**
       * 普通的java bean 该类不再需要实现任何接口或继承抽象类
       */
      public class PojoAspect {
      
          /**
           * 前置通知
           * @param jp
           */
          public void before(JoinPoint jp) {
              System.out.println("前置通知");
              System.out.println("方法名:" + jp.getSignature());
              System.out.println(",参数:" + jp.getArgs().length);
              System.out.println("代理对象:" + jp.getTarget());
          }
      
          /**
           * 后置通知
           * @param jp
           */
          public void after(JoinPoint jp) {
              System.out.println("后置通知");
          }
      
          /**
           * 返回值通知
           * @param joinPoint
           */
          public void afterReturning(JoinPoint joinPoint) {
              System.out.println(joinPoint);
          }
      
          /**
           * 抛出异常通知
           *
           * @param joinPoint
           */
          public void afterThrowing(JoinPoint joinPoint) {
      		
          }
          /**
           * 环绕通知
           *
           * @param pjd ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
           *            而且环绕通知必须有返回值,返回值即为目标方法的返回值
           * @return
           */
          public Object surround(ProceedingJoinPoint pjd) {
              return null;
          }
      
      }
      
      
      
    5. 在spring配置文件中配置

       aop:advisor,是有顺序的,必须放在aop:pointcut之后
       
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
          
          
          <bean id="pojoDao" class="com.wener.example.aop.pojo.PojoDaoImpl"/>
          
          <bean id="aspect" class="com.wener.example.aop.pojo.PojoAspect"/>
          
          
          <aop:config>
              
              <aop:pointcut id="pointcut" expression="execution(* com.wener.example.aop.pojo.*.*(..))"/>
              <aop:aspect ref="aspect">
                  <aop:after method="after" pointcut-ref="pointcut"/>
                  <aop:around method="surround" pointcut-ref="pointcut"/>
                  <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
                  <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/>
                  <aop:before method="before" pointcut-ref="pointcut"/>
              aop:aspect>
          aop:config>
      beans>
      
      
    6. 举例说明

      1>在spring配置文件中注册
      <bean id="sleepHelper" class="com.werner.webapp.aop.base.SleepHelper">
      bean>
      2>第二步配置切入点
      Spring使用org.springframework.aop.support.JdkRegexpMethodPointcut来定义正则表达式切点
      1.使用正则表达式 
      2.使用AspectJ表达式
      <bean id="spleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*sleep"/>
        //pattern属性指定了正则表达式,它匹配所有的sleep方法
      bean>
      3>配置通知
      org.springframework.aop.support.DefaultPointcutAdvisor
      <bean id="sleepHelperAdvisor" 		class="org.springframework.aop.support.DefaultPointcutAdvisor">
      	<property name="advice" ref="sleepHelper"/>
      	<property name="pointcut" ref="sleepPointcut"/>
       bean>
      4>切点仅仅是定义了故事发生的地点,还有故事发生的时间以及最重要的故事的内容,就是通知了,我们需要把通知跟切点结合起来,我们要使用的通知者是:
      org.springframework.aop.support.DefaultPointcutAdvisor
         <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
              <property name="advice" ref="sleepHelper"/>
              <property name="pointcut" ref="sleepPointcut"/>
         bean>
      5> 切入点和通知都配置完成,接下来该调用ProxyFactoryBean产生代理对象了
       <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
              <property name="target" ref="human"/>
              <property name="interceptorNames" value="sleepHelperAdvisor" />
              <property name="proxyInterfaces" value="test.spring.AOP.bean.Sleepable" />
      bean>
       
      
      

    六、基于@Aspect注解编程(重点)

    1、说明

    Spring 使用了和AspectJ 一样的注解并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)(编译器与织入器暂时不要管)

    2、启用@AspectJ支持

    1. 说明

      为了在Spring中使用@AspectJ切面,你首先必须启用Spring对@AspectJ切面配置的支持,并确保开启自动代理。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行

    2. 新建spring-aspect.xml配置文件

      
      <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
             http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
          <context:component-scan base-package="com.wener.example.aop.aspect"/>
      	
              
          <aop:aspectj-autoproxy/>
      beans>
      
      

    2、声明一个切面

    1. 说明

      在代码中定义一个类任意在类上使用@Aspect注解

    2. 示例代码

      import org.aspectj.lang.annotation.Aspect;
      @Aspect
      public class LogAspect {
      }
      
      

    3、声明一个切入点

    1. 说明

      切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:

      • **包含名字和任意参数的签名:**一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void 类型)
      • **切入点表达式:**切入点表达式决定了我们关注哪些方法的执行,详细表达式语法后面在说。
    2. 语法格式

      @Pointcut(value="", argNames = "")
      
      
    3. 参数说明

      • value

        指定切入点表达式

      • argNames

        指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数

    4. 示例代码

      @Aspect
      public class LogAspect {
          // 也可以在通知上定义,当需要复用切入点的时候
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")  
          // 返回值 必须是void类型
          public void log() {
          }
      }
      
      
    5. 备注

      切入点的定义是非必要的,也可以直接在通知上使用切入点表达式

    4、声明通知

    4.1、说明

    通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式,通知的类型就是我们前面提到过的类型

    4.2、前置通知

    1. 说明

      在关注点执行前运行的方法,切面里使用 @Before 注解声明前置通知

    2. 语法格式

      @Before(value = "", argNames = "")
      
      
    3. 参数说明

      • **value *指定切入点表达式或切入点名字;
      • argNames: 用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.Before;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
          /**
           * @Before 前置通知
           * value:指定切入点表达式或命名切入点;
           * argNames:与Schema方式配置中的同义;
           */
          @Before("logPointcut()")
          public void before() {
              System.out.println("前置通知");
          }
      }
      
      

    4.3、后置通知(最终通知)

    1. 说明

      不论一个方法是如何结束的,最终通知都会运行。使用@After 注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。相当于异常处理里finally的代码

    2. 语法格式

      @After(value = "", argNames = "")
      
      
    3. 参数

      • **value *指定切入点表达式或切入点名字;
      • **argNames: **用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
          /**
           * @After 后置通知 
           */
          @After(value = "logPointcut()")
          public void after() {
              System.out.println("后置通知");
          }
      }
      
      

    4.4、返回通知

    1. 说明

      返回后通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning 注解来声明

    2. 语法格式

      @AfterReturning(value="",pointcut="",returning="",argNames="")
      
      
    3. 参数说明

      • value:指定切入点表达式或切入点名字;
      • pointcut:指定切入点表达式或命名切入点,如果指定了将覆盖value属性的,pointcut具有高优先级;
      • returning:如果你想获取方法的返回值可以使用该参数,在通知方法中定义参数就可以了
      • argNames:用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
      	/**
      	 * 不获取方法的返回值
           */
          @AfterReturning(value = "logPointcut()")
          public void AfterReturning1() {
              System.out.println("异常通知");
          }
          /**
           * 获取方法的返回值
           * returning的赋值的名字,必须跟通知方法中参数的名字保持一致
           */
          @AfterReturning(value = "logPointcut()", returning = "val")
          public Object afterReturning(Object val) {
              System.out.println("返回后通知");
              return val;
          }
      
      }
      
      

    4.5、异常通知

    1. 说明

      抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing注解来声明

    2. 语法格式

      @AfterThrowing(value="",pointcut="",throwing="",argNames="")
      
      
    3. 参数说明

      • value:指定切入点表达式或命名切入点;
      • pointcut:指定切入点表达式或命名切入点,如果指定了将覆盖value属性的,pointcut具有高优先级;
      • throwing:异常类型;并且在通知方法中定义异常参数;
      • argNames:用来接收AspectJ表达式中的参数,并指定通知方法中的参数;
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      		
          }
          /**
           * @AfterThrowing 异常通知 
           * 	value:指定切入点表达式或命名切入点;
           * 	throwing:异常类型。
           */
          @AfterThrowing("logPointcut()")
          public void afterThrowing() {
              System.out.println("异常通知");
          }
          /**
           * 如果想要限制通知只在某种特定的异常被抛出的时候匹配,同时还想知道异常的一些信息。 
           * 那我们就需要使用throwing属性声明响应
           */
      	@AfterThrowing(value = "logPointcut()", throwing = "exception")
          public void afterThrowing(Exception exception) {
              System.out.println("异常通知");
          }
      }
      
      

    4.6、环绕通知

    1. 说明

      环绕通知在一个方法执行之前和之后执行。`

      • 使用@Around注解;
      • 环绕通知需要携带ProceedingJoinPoint类型的参数;
      • 且环绕通知必须有返回值,返回值即为有目标方法的返回值。
    2. 语法格式

      @Around(value = "", argNames = "")
      
      
    3. 参数

      • **value *指定切入点表达式或切入点名字;
      • **argNames: **用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
          /**
           * @Around 环绕通知
           * 比如 缓存切面,如果缓存中有值,就返回该值,否则调用proceed()方法
           * value:指定切入点表达式或命名切入点;
           * 注意 第一个参数必须是 ProceedingJoinPoint对象 具体这个类的更多详细使用看附录:
           */
          @Around(value = "logPointcut()")
          public Object around(ProceedingJoinPoint pjp) throws Throwable {
              System.out.println("环绕通知1");
              Object obj = pjp.proceed();
              System.out.println("环绕通知2");
              return obj;
          }
      }
      
      

    4.7、通知参数

    1. 说明

      若想要在通知方法获取被通知方法的参数共有两种方式:自动获取、手动指定

      • 自动获取参数:通知类型可以通过参数JoinPoint或者 ProceedingJoinPoint 自动获取被通知方法的参数值并调用该方法
      • 手动指定参数:即在配置切面时,需在切面的通知与切面的切点中明确指定参数。
    2. 手动指定

      • 在@pointcut中切入表达式中使用args声明匹配的参数,注意使用&&连接args

      • 在@pointcut中切入表达式中使用参数argNames用来接收AspectJ表达式中的参数,

        argNames属性是用于指定在表达式中应用的参数名与Advice方法参数是如何对应的

      • 在通知方法中定义参数

    3. 手动获取指定参数

      import org.aspectj.lang.annotation.*;
      import org.springframework.stereotype.Component;
      @Aspect
      @Component
      public class LogAdviceParamsAspect {
      	// 注意参数的个数必须一致,否则匹配不到
          @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name")
          public void testArgs(Object id, Object name) {
              System.out.println(id);
              System.out.println(name);
          }
      }
      
      
    4. 混用使用

      当同时采用自动获取参数与手动指定参数时,自动获取参数必须是第一个参数,即ProceedingJoinPoint 等参数并需是通知方法定义的第一个参数

      import org.aopalliance.intercept.Joinpoint;
      import org.aspectj.lang.annotation.*;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LogAdviceParamsAspect {
        // args、argNames的参数名与testArgs()方法中参数名 保持一致
          @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name")
          public void testArgs(Object id, Object name) {
              System.out.println(id);
              System.out.println(name);
          }
      	// 也可以不用argNames
          @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)")
          public void testArgs(Object id, Object name) {
              System.out.println(id);
              System.out.println(name);
          }
          
          @Around(value = "execution(* com.wener.example.aop.aspect.*.*(..))&&(args(id,name,..))", argNames = "pjp,id,name")
          public Object testAroundArgs(ProceedingJoinPoint pjp, Object id, Object name) throws Throwable {
              System.out.println("Around之前");
              Object obj = pjp.proceed();
              System.out.println();
              return obj;
          }
      }
      
      

    4.8 、引入

    1. 说明

      有时候有一组共享公共行为类。在OOP中,它们必须扩展相同的基类或者实现相同的接口。此外,Java的单继承机制仅允许一个类最多扩展一个基类。所以,不能同时从多个实现类中继承行为。

      解决方案:引入是AOP中的一种特殊的通知。它允许为一个接口提供实现类,使对象动态的实现接口。就像对象在运行时扩展了实现类。而且,可以用多个实现类将多个接口同时引入对象。这可以实现与多重继承相同的效果。

    2. 在开发中用的不是很多,所以不做过多的分析

    5、声明代理类

    1. 说明

      被代理的对象,跟前面说的一样,代理接口或者类都可以

    2. 示例代码

      public interface AspectDao {
          public void test();
          public void testParams(int id, String name);
          public void testParams(Joinpoint jp, int id, String name);
      }
      
      @Component("aspectDao")
      public class AspectDaoImpl implements AspectDao {
          @Override
          public void test() {
              System.out.println("核心测试方法");
          }
          @Override
          public void testParams(int id, String name) {
              System.out.println("带参数的方法:" + "ID:" + id + "name:" + name);
          }
      }
      
      

    6、测试

    1. 示例代码

      ApplicationContext context = new ClassPathXmlApplicationContext("spring-aspect.xml");
      AspectDao dao = (AspectDao) context.getBean("aspectDao");
      dao.test();
      dao.testParams(1,"hello");
      
      

    7、总结

    1. 使用@Aspect将POJO声明为切面;
    2. 在切面类中使用@Pointcut进行命名切入点声明;
    3. 定义通知方法,使用5中注解声明,其中value用于定义切入点表达式或引用命名切入点;
    4. 配置文件需要使用来开启注解风格的@AspectJ支持;
    5. 将切面类和POJO类注册到Spring容器中

    执行流程

    AOP定义了一个切面(Aspect),一个切面包含了切入点,通知,引入,这个切面上定义了许多的切入点(Pointcut),一旦访问过程中有对象的方法跟切入点匹配那么就会被AOP拦截。此时该对象就是目标对象(Target Object)而匹配的方法就是连接点(Join Point)。紧接着AOP会用过JDK动态代理或者CGLIB生成一个目标对象的代理对象(AOP proxy),这个过程就是织入(Weaving)。这个时候我们就可以按照我们的需求对连接点进行一些拦截处理。可以看到,我们可以引入(Introduction)一个新的接口,让代理对象来实现这个接口来,以实现额外的方法和字段。也可以在连接点上进行通知(Advice),通知的类型包括了前置通知,返回后通知,抛出异常后通知,后置通知,环绕通知。最后也是最骚的是整个过程不会改变代码原有的逻辑

    十九、基于@Aspect注解编程(重点)

    1、说明

    Spring 使用了和AspectJ 一样的注解并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)(编译器与织入器暂时不要管)

    2、启用@AspectJ支持

    1. 说明

      为了在Spring中使用@AspectJ切面,你首先必须启用Spring对@AspectJ切面配置的支持,并确保开启自动代理。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行

    2. 新建spring-aspect.xml配置文件

    2、声明一个切面

    1. 说明

      在代码中定义一个类任意在类上使用@Aspect注解

    2. 示例代码

      import org.aspectj.lang.annotation.Aspect;
      @Aspect
      public class LogAspect {
      }
      

    3、声明一个切入点

    1. 说明

      切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:

      • **包含名字和任意参数的签名:**一个切入点签名通过一个普通的方法定义来提供,并且切入点表达式使用@Pointcut注解来表示(作为切入点签名的方法必须返回void 类型)
      • **切入点表达式:**切入点表达式决定了我们关注哪些方法的执行,详细表达式语法后面在说。
    2. 语法格式

      @Pointcut(value="", argNames = "")
      
    3. 参数说明

      • value

        指定切入点表达式

      • argNames

        指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数

    4. 示例代码

      @Aspect
      public class LogAspect {
          // 也可以在通知上定义,当需要复用切入点的时候
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")  
          // 返回值 必须是void类型
          public void log() {
          }
      }
      
    5. 备注

      切入点的定义是非必要的,也可以直接在通知上使用切入点表达式

    4、声明通知

    4.1、说明

    通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式,通知的类型就是我们前面提到过的类型

    4.2、前置通知

    1. 说明

      在关注点执行前运行的方法,切面里使用 @Before 注解声明前置通知

    2. 语法格式

      @Before(value = "", argNames = "")
      
    3. 参数说明

      • **value *指定切入点表达式或切入点名字;
      • argNames: 用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.Before;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
          /**
           * @Before 前置通知
           * value:指定切入点表达式或命名切入点;
           * argNames:与Schema方式配置中的同义;
           */
          @Before("logPointcut()")
          public void before() {
              System.out.println("前置通知");
          }
      }
      

    4.3、后置通知(最终通知)

    1. 说明

      不论一个方法是如何结束的,最终通知都会运行。使用@After 注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。相当于异常处理里finally的代码

    2. 语法格式

      @After(value = "", argNames = "")
      
    3. 参数

      • **value *指定切入点表达式或切入点名字;
      • **argNames: **用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
          /**
           * @After 后置通知 
           */
          @After(value = "logPointcut()")
          public void after() {
              System.out.println("后置通知");
          }
      }
      

    4.4、返回通知

    1. 说明

      返回后通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning 注解来声明

    2. 语法格式

      @AfterReturning(value="",pointcut="",returning="",argNames="")
      
      
    3. 参数说明

      • value:指定切入点表达式或切入点名字;
      • pointcut:指定切入点表达式或命名切入点,如果指定了将覆盖value属性的,pointcut具有高优先级;
      • returning:如果你想获取方法的返回值可以使用该参数,在通知方法中定义参数就可以了
      • argNames:用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
      	/**
      	 * 不获取方法的返回值
           */
          @AfterReturning(value = "logPointcut()")
          public void AfterReturning1() {
              System.out.println("异常通知");
          }
          /**
           * 获取方法的返回值
           * returning的赋值的名字,必须跟通知方法中参数的名字保持一致
           */
          @AfterReturning(value = "logPointcut()", returning = "val")
          public Object afterReturning(Object val) {
              System.out.println("返回后通知");
              return val;
          }
      
      }
      
      

    4.5、异常通知

    1. 说明

      抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing注解来声明

    2. 语法格式

      @AfterThrowing(value="",pointcut="",throwing="",argNames="")
      
      
    3. 参数说明

      • value:指定切入点表达式或命名切入点;
      • pointcut:指定切入点表达式或命名切入点,如果指定了将覆盖value属性的,pointcut具有高优先级;
      • throwing:异常类型;并且在通知方法中定义异常参数;
      • argNames:用来接收AspectJ表达式中的参数,并指定通知方法中的参数;
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      		
          }
          /**
           * @AfterThrowing 异常通知 
           * 	value:指定切入点表达式或命名切入点;
           * 	throwing:异常类型。
           */
          @AfterThrowing("logPointcut()")
          public void afterThrowing() {
              System.out.println("异常通知");
          }
          /**
           * 如果想要限制通知只在某种特定的异常被抛出的时候匹配,同时还想知道异常的一些信息。 
           * 那我们就需要使用throwing属性声明响应
           */
      	@AfterThrowing(value = "logPointcut()", throwing = "exception")
          public void afterThrowing(Exception exception) {
              System.out.println("异常通知");
          }
      }
      
      

    4.6、环绕通知

    1. 说明

      环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)。

      • 使用@Around注解;
      • 环绕通知需要携带ProceedingJoinPoint类型的参数;
      • 且环绕通知必须有返回值,返回值即为有目标方法的返回值。
    2. 语法格式

      @Around(value = "", argNames = "")
      
      
    3. 参数

      • **value *指定切入点表达式或切入点名字;
      • **argNames: **用来接收AspectJ表达式中的参数,并指定通知方法中的参数
    4. 示例代码

      import org.springframework.stereotype.Component;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.ProceedingJoinPoint;
      
      @Aspect
      @Component
      public class LogAspect {
          /**
           * @Pointcut() 切入点表达式
           */
          @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
          public void logPointcut() {
      
          }
          /**
           * @Around 环绕通知
           * 比如 缓存切面,如果缓存中有值,就返回该值,否则调用proceed()方法
           * value:指定切入点表达式或命名切入点;
           * 注意 第一个参数必须是 ProceedingJoinPoint对象 具体这个类的更多详细使用看附录:
           */
          @Around(value = "logPointcut()")
          public Object around(ProceedingJoinPoint pjp) throws Throwable {
              System.out.println("环绕通知1");
              Object obj = pjp.proceed();
              System.out.println("环绕通知2");
              return obj;
          }
      }
      
      

    4.7、通知参数

    1. 说明

      若想要在通知方法获取被通知方法的参数共有两种方式:自动获取、手动指定

      • 自动获取参数:通知类型可以通过参数JoinPoint或者 ProceedingJoinPoint 自动获取被通知方法的参数值并调用该方法
      • 手动指定参数:即在配置切面时,需在切面的通知与切面的切点中明确指定参数。
    2. 手动指定

      • 在@pointcut中切入表达式中使用args声明匹配的参数,注意使用&&连接args

      • 在@pointcut中切入表达式中使用参数argNames用来接收AspectJ表达式中的参数,

        argNames属性是用于指定在表达式中应用的参数名与Advice方法参数是如何对应的

      • 在通知方法中定义参数

    3. 手动获取指定参数

      import org.aspectj.lang.annotation.*;
      import org.springframework.stereotype.Component;
      @Aspect
      @Component
      public class LogAdviceParamsAspect {
      	// 注意参数的个数必须一致,否则匹配不到
          @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name")
          public void testArgs(Object id, Object name) {
              System.out.println(id);
              System.out.println(name);
          }
      }
      
      
    4. 混用使用

      当同时采用自动获取参数与手动指定参数时,自动获取参数必须是第一个参数,即ProceedingJoinPoint 等参数并需是通知方法定义的第一个参数

      import org.aopalliance.intercept.Joinpoint;
      import org.aspectj.lang.annotation.*;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class LogAdviceParamsAspect {
        // args、argNames的参数名与testArgs()方法中参数名 保持一致
          @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name")
          public void testArgs(Object id, Object name) {
              System.out.println(id);
              System.out.println(name);
          }
      	// 也可以不用argNames
          @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)")
          public void testArgs(Object id, Object name) {
              System.out.println(id);
              System.out.println(name);
          }
          
          @Around(value = "execution(* com.wener.example.aop.aspect.*.*(..))&&(args(id,name,..))", argNames = "pjp,id,name")
          public Object testAroundArgs(ProceedingJoinPoint pjp, Object id, Object name) throws Throwable {
              System.out.println("Around之前");
              Object obj = pjp.proceed();
              System.out.println();
              return obj;
          }
      }
      
      

    4.8 、引入

    1. 说明

      有时候有一组共享公共行为类。在OOP中,它们必须扩展相同的基类或者实现相同的接口。此外,Java的单继承机制仅允许一个类最多扩展一个基类。所以,不能同时从多个实现类中继承行为。

      解决方案:引入是AOP中的一种特殊的通知。它允许为一个接口提供实现类,使对象动态的实现接口。就像对象在运行时扩展了实现类。而且,可以用多个实现类将多个接口同时引入对象。这可以实现与多重继承相同的效果。

    2. 在开发中用的不是很多,所以不做过多的分析

    5、声明代理类

    1. 说明

      被代理的对象,跟前面说的一样,代理接口或者类都可以

    2. 示例代码

      public interface AspectDao {
          public void test();
          public void testParams(int id, String name);
          public void testParams(Joinpoint jp, int id, String name);
      }
      
      @Component("aspectDao")
      public class AspectDaoImpl implements AspectDao {
          @Override
          public void test() {
              System.out.println("核心测试方法");
          }
          @Override
          public void testParams(int id, String name) {
              System.out.println("带参数的方法:" + "ID:" + id + "name:" + name);
          }
      }
      
      

    6、测试

    1. 示例代码

      ApplicationContext context = new ClassPathXmlApplicationContext("spring-aspect.xml");
      AspectDao dao = (AspectDao) context.getBean("aspectDao");
      dao.test();
      dao.testParams(1,"hello");
      
      

    7、总结

    1. 使用@Aspect将POJO声明为切面;
    2. 在切面类中使用@Pointcut进行命名切入点声明;
    3. 定义通知方法,使用5中注解声明,其中value用于定义切入点表达式或引用命名切入点;
    4. 配置文件需要使用来开启注解风格的@AspectJ支持;
    5. 将切面类和POJO类注册到Spring容器中

    七、基于xml的AOP编程(掌握)

    1、说明

    如果比较喜欢使用XML格式,Spring2.0也提供了使用新的"aop"命名空间来定义一个切面。 和使用@AspectJ风格完全一样,切入点表达式和通知类型同样得到了支持

    AOP配置元素 用途
    顶层的AOP配置元素,大多数的必须包含在元素内
    定义一个切面
    定义一个切点
    定义AOP通知器
    定义AOP前置通知
    定义AOP环绕通知
    定义AOP返回通知
    定义AOP异常通知
    定义AOP后置通知(不管被通知的方法是否执行成功)
    启用@Aspect注解的切面
    以透明的方式为被通知的对象引入额外的接口

    2、引入aop命名空间标签

    1. 说明

      在beans元素下 引入aop,声明,在配置文件中,我们可以声明多个

      注意:

      • 所有的切面和通知都必须定义在元素内部。
      • 一个可以包含pointcut,advisor和aspect元素 (注意这三个元素必须按照这个顺序进行声明)
    2. 示例代码

    3、声明一个切面

    1. 说明

      切面使用来声明

    2. 示例代码

    4、声明一个切入点

    1. 说明

      一个命名切入点可以在元素中定义,使用声明,这样多个切面和通知就可以共享该切入点,你也可以在切面中定义

    2. 示例代码

    5、声明通知

    5.1、说明

    和@AspectJ风格一样,基于xml的风格也支持5种通知类型并且两者具有同样的语义

    5.2、前置通知

    1. 说明

      前置通知在匹配方法执行前运行。在中使用 元素来声明它

    2. 示例代码

    5.3、后置通知

    1. 说明

      后置通知在匹配的方法完全执行后运行。和前置通知一样,在 里面使用声明,通知方法可以得到返回值。使用returning属性来指定传递返回值的参数名。

    2. 示例代码

    5.4、异常通知

    1. 说明

      异常通知在匹配方法抛出异常退出时执行。在中使用 元素来声明,还可以使用throwing属性来指定传递异常的参数名

    2. 示例代码

      
                          

    你可能感兴趣的:(spring,spring)