如果有好几千个jsp, 这些jsp互相调用(通过GET/POST), 到了最后调用关系无人能搞懂。(随后演变出了Model2)
JavaBean作为Model层,定义bean来表示数据和封装业务逻辑
定义数据Bean来表示需要显示给用户的结果
定义业务Bean来封装业务逻辑,也就是DAO
使用Servlet处理用户请求
JSP作为View层,负责生成交互后返回的界面
商务软件的核心部分是它的业务逻辑
EJB是sun的JavaEE服务器端组件模型,设计目标与核心应用是部署分布式应用程序。
简单来说就是把已经编写好的程序(即:类)打包放在服务器上执行。
凭借java跨平台的优势,用EJB技术部署的分布式系统可以不限于特定的平台。
EJB (Enterprise JavaBean)是J2EE(javaEE)的一部分,定义了一个用于开发基于组件的企业多重应用程序的标准。
其特点包括网络服务支持和核心开发工具(SDK)。
Java 数据库连接, 没有数据库的支持怎么能叫企业级应用
Java命名和目录接口, 通过一个名称就可以定位到一个数据源, 连jdbc连接都不用了
远程方法调用(Remote Method Invoke ), 让一个机器上的java 对象可以调用另外一个机器上的java对象
注释:要实现远程调用必须序列化
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
Java Transaction 事务管理, 支持分布式事务, 能在访问、更新多个数据库的时候,仍然保证事务, 还是分布式。
Java Message Service 各个软件模块收发消息的消息中间件,在各个软件模块之间收发消息
收发邮件也是必不可少的啊。
一个业务分拆多个子业务,部署在不同的服务器上
强调 机器间的协作,其重点是任务可拆分, 如 某个任务需要一个机器运行10个小时, 将该该任务用10台机器的分布式跑,可能2个小时就跑完了 (主要是解决计算机内存,io,瓶颈)
同一个业务,部署在多个服务器上
例如:某个任务需要一个机器运行10个小时,那任务放到 处理该任务的集群上 还是需要10个小时。 假如有10个这样的任务, 放到同一个集群上, 仍然需要10个小时
Spring开源项目开启
基于一个易懂的,轻量级的框架开发,已经是后EJB时代的趋势( Expert One-On-One J2EE Development Without EJB )Spring框架是澳大利亚计算机专家Rod Johnson(罗德·约翰逊)于2002年发布,诞生以来发布了很多版本,目前最新版是5.X
Spring是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。
版本 | 日期 |
---|---|
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 |
Spring 是轻量的,基本的版本大约2MB。
Spring 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
Spring 包含并管理应用中对象的生命周期和配置。
Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
Spring可以将简单的组件配置、组合成为复杂的应用。
在Spring中,应用对象被声明式地组合,典型的是在一个XML文件里。
Spring也提供了很多基础功能(事务管理、持久化框架集成等),将应用逻辑的开发留给开发者。
简化开发,降低Java API的使⽤难度(针对数据层做了各种封装)。
Spring框架包含的功能大约由20个模块组成。这些模块按组可分为核心容器、数据访问/集成,Web,AOP(面向切面编程)、设备、消息和测试
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 的支持 |
spring-core
这个jar文件包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类
spring-beans 模块
这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI支持,引入spring-core.jar及spring- beans.jar文件就可以了
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)等。
spring-expression 模块
为在运行时查询和操作对象图提供了强大的表达式语言。它是JSP2.1规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。
数据访问与集成层包含 JDBC、ORM、OXM、JMS和事务模块。
spring-jdbc 模块
提供了 JDBC抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。
spring-tx 模块
支持编程式事务和声明式事务,可用于实现了特定接口的类和所有的 POJO 对象。编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细。
spring-orm 模块
提供了对流行的对象关系映射 API的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring 的其它功能整合,比如前面提及的事务管理。
spring-oxm
模块提供了对 OXM 实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。
spring-jms
模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了 spring-messaging 模块
提供了面向切面编程(AOP)的实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦。使用源码级的元数据。
提供了对 AspectJ 的集成
模块提供了对检测类的支持和用于特定的应用服务器的类加载器的实现。
模块包含了用于 Tomcat 的Spring 检测代理。
spring-messaging 模块
从 Spring 4 开始集成,从一些 Spring 集成项目的关键抽象中提取出来的,这些项目包括 Message、MessageChannel、MessageHandler 和其它服务于消息处理的项目。这个模块也包含一系列的注解用于映射消息到方法
Web 层包括 spring-web、spring-webmvc、spring-websocket、spring-webmvc-portlet 等模块。
提供面向 web 的基本功能和面向 web 的应用上下文,比如 multipart 文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分
为 web 应用提供了模型视图控制(MVC)和 REST Web 服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成
(即Web-Portlet模块)提供了用于 Portlet 环境的 MVC 实现,并反映了 pring-webmvc 模块的功能
通过 JUnit 和 TestNG 组件支持单元测试和集成测试。它提供了一致性地加载和缓存 Spring 上下文,也提供了用于单独测试代码的模拟对象(mock object)
下载地址:http://repo.spring.io/release/org/springframework/spring
maven仓库下载
参照idea+maven创建工程
说明
修改跟目录下的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>
/**
* @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;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-necoLWWy-1655729408699)(https://zhangwei-imgs.oss-cn-beijing.aliyuncs.com/superbed/2019/08/11/5d502cad451253d178413abb.png)]
<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>
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);
}
}
在采用面对对象方法设计的软件系统中,底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。即软件系统中对象之间的耦合,对象A和对象B之间有关联,对象B又和对象C有依赖关系,这样对象和对象之间有着复杂的依赖关系,所以才有了Ioc这个思想。
1.ioc :Inversion of Control,翻译即控制反转,是一种设计思想
1.(谁被反转?):Martin Fowler(马丁·福勒)得出了答案:“获得依赖对象的过程被反转了”。即控制被反转之后,获得依赖对象的过程由自身管理对象变为由IoC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection,DI)”。他的这个答案,实际上给出了实现IoC的方法:注入。
2.依赖注入:就是Ioc容器在运行期间,动态的将某种依赖关系注入到对象中
3.所以,DI和Ioc是从不同角度描述,应用通过引入Ioc容器,利用依赖关系注入的方式,实现对象之间的解耦。
说明
对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系
四种情况
如果出现了上述四种情况之一,两个类很有可能就是“依赖”关系。 依赖关系(Dependency):是类与类之间的连接,依赖总是单向的。依赖关系代表一个类依赖于另一个类的定义
DI(依赖注入) 和 Ioc(控制反转)是同一个概念:
例子:
当某个java实例需要另一个java实例协助时在传统的程序设计过程中,通常由调用者来穿件被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此成为控制反转;创建被调用者实例的工作通常由Spring容器来完成,然后注入调用者,因此也成为依赖注入。
Spring支持三种方式配置Bean,Spring1.0仅支持基于XML的配置,Spring2.0新增基于注解配置的支持,Spring3.0新增基于Java类配置的支持,Spring4.0则新增给予Groovy动态语言配置的支持。
在实际开发中前面三种方式都会用到,后面一种相对用的比较少,因为需要学习Groovy语言
基于注解自动注入(优先使用)
**优点:**在class文件中,降低维护成本。不需要第三方解析工具,利用java反射机制。编辑期就可以检验正确性,提高开发效率。
**缺点:**如果需要对注解进行修改,那么要重新编译整个工程。Bean之间的关系不好把控。
Java 接口和类中配置实现配置(Spring Boot的推荐方式)
同隐式的bean扫描,基于注解
XML配置(第三方框架,无法修改源代码的情况下)
**优点:**简单易懂,配置文件修改不需要重新编译,扩展起来相对容易,比较典型的应用场景就是第三方的库中,一般都采用此种配置方式
**缺点:**配置文件过多的时候难以管理
使用 XML 装配 Bean 需要定义对应的 XML,这里需要引入对应的 XML 模式(XSD)文件,这些文件会定义配置 Spring Bean 的一些元素,当我们在 IDEA 中创建 XML 文件时,会有友好的提示
<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>
说明
栗子
// java 代码
public class User {
private String name;
private String password;
}
<bean id="user,userId" class="com.wener.example.bean.User"/>
说明
一个类的全限定名(类的完整路径)
栗子
<bean class="com.wener.example.bean.User"/>
说明
默认false,懒加载,让Spring容器在Bean首次用到时创建Bean实例,而不是在应用一启动就创建
栗子
<bean class="com.wener.example.bean.User" lazy-init="true"/>
scope 用来配置 spring 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环境 |
当一个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
设置为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容器负责管理Bean的生命周期
Bean在创建时,需要执行一些资源(数据库、套接字、文件)申请等初始化工作,可以在Bean的初始化回调方法中处理,此方法由Spring容器调用。
同样Bean在销毁时,需要执行一些资源(数据库、套接字、文件)申请等销毁工作,可以在Bean的销毁回调方法中处理,此方法由Spring容器调用。
说明
初始化方法,此方法将在BeanFactory创建JavaBean实例之后,在向应用层返回引用之前执行。一般用于一些资源的初始化工作。
示例代码
public class User {
public void init() {
System.out.println("初始化");
}
}
<bean class="com.werner.di.User"
name="user"
init-method="init"/>
说明:
销毁方法,此方法将在BeanFactory销毁的时候执行,一般用于资源释放。
示例代码
public class User {
public void destroy() {
System.out.println("销毁...");
}
}
<bean class="com.wener.example.bean.User"
id="user"
init-method="init" destroy-method="destroy"/>
XML配置文件中,在bean的定义中可配置该bean的依赖项,通常使用的配置方式有2种
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());
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。
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;
}
}
通过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与构造函数参数相同类型,则一个致命的错误会产生。 |
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>
注解本身没有功能的,就和xml一样。注解和xml都是一种元数据,元数据即解释数据的数据,这就是所谓配置。
Spring3的基于注解实现Bean依赖注入支持如下三种注解:
默认情况下,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>
Spring容器扫描指定包路径下的所有类,每当找到1个@Component
注解,就会注册Bean,同时设置Bean ID。
**注: **默认情况下Bean ID就是类名,但首字母小写。如果类名以连续几个大写字母开头,首字母不小写。(即HELLOService -> HELLOService)
常用Spring的声明的注解,
注解 | 作用域 | 说明 |
---|---|---|
@Component | 注解在类上,可以作用在任何层次。 | 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。是一个泛化的概念,仅仅表示一个组件 (Bean) ,将一个实体类,放入Spring中。 |
@ Service | 注解在类上 | 用于标注业务层组件 |
@ Controller | 注解在类上 | 用于标注控制层组件 |
@ Repository | 注解在类上 | 用于标注数据访问组件,即DAO组件。 |
表示 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 {
}
当一个组件代表业务层时,可以使用@Service进行注解,bean 的ID 默认为类名称开头字母小写
@Controller('accountController')
public class AccountController {
}
//或者,
@Controller
public class AccountController {
}
通常用于注解Service类,也就是服务层
@Service
public class AccountServiceImpl implements AccountService {
}
当一个组件代表数据访问层(DAO)的时候,使用@Repository进行注解 ,bean 的ID默认为类名称开头字母小写
@Repository
public class UserDaoImpl implements UserDao {
...
}
//或者
@Repository("userDao")
public class UserDaoImpl implements UserDao {
...
}
被注解的java类当做Bean实例,Bean实例的名称默认是Bean类的首字母小写,其他部分不变。
Controller 、@Repository、@Controller、 @Service可以自定义Bean名称,理论上只要符合java的命名规范的名字都可以,但是必须是唯一的,
尽量使用对应组件注解的类替换@Component注解,在spring未来的版本中,@Controller,@Service,@Repository会携带更多语义。并且便于开发和维护!
指定了某些类可作为Spring Bean类使用后,在Spring配置文件加入如下配置
<context:component-scan base-package="自动扫描指定包及其子包下的所有Bean类"/>
自动装配是指Spring 在装配 Bean 的时候,根据指定的自动装配规则,将某个 Bean 所需要引用类型的 Bean 注入进来。可以在类的成员变量上,构造方法,setter方法使用,常用的主要有以下三种
这个注解相当于我们之前在xml文件中配置的autowire=“constructor/byName/byType”,只不过我们这里使用@Autowired方式注解方式,且默认是通过类型判断,意思就是不使用byName,和construtor。通过@Autowired注解,spring会自动去容器中查找对应的类型,注入到该属性中,且bean类中,使用@Autowired注解其属性,我们可以不用提供getter,setter方法
默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如果我们想使用按照名称装配,可以结合@Qualifier注解一起使用
说明
将Autowired注解声明在属性上面,
示例代码
@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());
}
优点
缺点
说明
将Autowired注解声明在构造方法上面,在Spring4.x版本中推荐的注入方式
示例代码
@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;
}
优点
缺点
备注(官方说明)
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。此外。此外,构造器注入的组件总是以完全初始化的状态返回给客户机(调用)代码。
说明
将Autowired注解声明在方法上面,Spring3.x的时候,官方推荐使用的注入
示例代码
@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;
}
优点
缺点
当Spring容器中存在多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。
该注解可以使用字段、方法、参数、注解上
DataSource
public interface DataSource {
void connection();
}
MysqlDataSource
@Component("mysql")
public class MysqlDataSource implements DataSource {
@Override
public void connection() {
System.out.println("mysql database connecting");
}
}
OracleDataSource
@Component("oracle")
public class OracleDataSource implements DataSource {
@Override
public void connection() {
System.out.println("oracle database connecting");
}
}
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;
}
}
说明
对@Qualifier的扩展来提供细粒度选择候选者;具体使用方式就是自定义一个注解并使用@Qualifier注解其即可使用
示例代码
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;
}
其它方式自行参考其它资料
属性注入
@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
构造方法注入
@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
区别
如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException从而提醒你避免循环依赖,如果是属性注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。
@Autowired的三种用法其实没有所谓的孰优孰劣,存在即是合理。个人建议,对于依赖关系无需变化的注入,可以采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入,不过个人比较喜欢用属性多点
@Autowired
按 byType 自动注入,而@Resource
默认按 byName 自动注入罢了。将@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());
}
在方法上使用@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
@Inject是JSR-330的一部分。在Spring3中开始支持JSR-330的注解
@Inject支持构造函数、方法和字段注解,也可能使用于静态属性。与@Autowired不同的是强制要求示例必须存在
注意: 需要导入第三方的jar包
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
说明
在属性上声明,注意属性不能是final的
示例代码
@Component
public class User {
@Inject
private Address address;
}
说明
在构造方法上声明,构造函数可以是无参或多个参数的构造函数,@Inject每个类中最多注解一个构造函数。
示例代码
@Component
public class User {
private Address address;
@Inject
public User(Address address) {
this.address = address;
}
}
说明
在方法上声明,注意不能是抽象方法,可以有0个或多个参数。
示例代码
@Component
public class User {
private Address address;
public Address getAddress() {
return address;
}
@Inject
public void setAddress(Address address) {
this.address = address;
}
说明
@Inject默认按类型匹配,如果你想按着Bean的名字来使用,可以使用@Name属性使用,一般用来类上面声明Bean的名字@Component的作用,如何在注入的时候在属性上声明相当于@Qualifier
示例代码
// 在类上声明
@Named("address1")
public class Address {
}
// 配合@Inject一起使用
@Component
public class User {
@Inject
@Named("address")
private Address address1;
}
条件
注解类型 | 所在包 | 版本支持 | 作用域 |
---|---|---|---|
@AutoWired | Spring自带的方式 | Spring 2.5+ | 可以用在构造器、方法、属性、参数、注解上面 |
@Resource | JSR-250标准,JDK6以上自带, | Spring版本要求2.5以上 | 可以用在方法、属性、类上 |
@Inject | JSR-303标准, | Spring版本3以上。需要导入外部依赖 | 可以用在方法、属性、构造器上 |
使用上
1、@Autowired、@Inject用法基本一样,不同的是@Autowired有一个required属性
2、@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的
3、@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Name一起使用
4、@Autowired可以允许对象为空,而@Resource与@Inject不允许对象为空
六、另外两种注解
当引入的对象存在多个实例配合使用与@Autowired一起使用的。@Qualifier可以被用在单个构造器或者方法的参数上。当上下文有几个相同类型的bean, 使用@Autowired则无法区分要绑定的bean,此时可以使用@Qualifier来指定名称。
就是当Spring容器扫描到某个接口的多个 bean 时,如果某个bean上加了@Primary 注解 ,则这个bean会被优先选用
前面介绍了Bean的XML配置方法,从Spring 3.0开始,可以使用java代码配置Bean,替代XML配置。Java配置与注解配置不同,Java配置是把Java代码文件当作配置文件,注解配置是在实际Java类中使用注解设置依赖关系。Java配置也会用到一些注解,主要有:@Configuration
、@ComponentScan
和@Bean
。
Spring Boot中彻底抛弃了xml配置 后期推荐使用此种方式
@Configuration
注解标注的类是配置类,用于配置Bean之间依赖关系。
@Import
注解允许从另一个配置Java/XML文件加载bean定义。
栗子
// 表明这是个Bean的Java配置类
@Configuration
public class DruidConfig {
}
要定义一个Bean,可以通过:
@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方法注入依赖项。
栗子
@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);
}
}
前面介绍了Bean的XML配置方法,从Spring 3.0开始,可以使用java代码配置Bean,替代XML配置。Java配置与注解配置不同,Java配置是把Java代码文件当作配置文件,注解配置是在实际Java类中使用注解设置依赖关系。Java配置也会用到一些注解,主要有:@Configuration
、@ComponentScan
和@Bean
。
Spring Boot中彻底抛弃了xml配置 后期推荐使用此种方式
@Configuration
注解标注的类是配置类,用于配置Bean之间依赖关系。
@Import
注解允许从另一个配置Java/XML文件加载bean定义。
栗子
// 表明这是个Bean的Java配置类
@Configuration
public class DruidConfig {
}
要定义一个Bean,可以通过:
@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方法注入依赖项。
栗子
@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);
}
}
前面介绍了Bean的XML配置方法,从Spring 3.0开始,可以使用java代码配置Bean,替代XML配置。Java配置与注解配置不同,Java配置是把Java代码文件当作配置文件,注解配置是在实际Java类中使用注解设置依赖关系。Java配置也会用到一些注解,主要有:@Configuration
、@ComponentScan
和@Bean
。
Spring Boot中彻底抛弃了xml配置 后期推荐使用此种方式
@Configuration
注解标注的类是配置类,用于配置Bean之间依赖关系。
@Import
注解允许从另一个配置Java/XML文件加载bean定义。
栗子
// 表明这是个Bean的Java配置类
@Configuration
public class DruidConfig {
}
要定义一个Bean,可以通过:
@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方法注入依赖项。
栗子
@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);
}
}
App app = context.getBean("app", App.class);
软件开发一直在寻求一种高效、护展、维护的方式。
面向对象的特点是封装继承、多态。而封装的核心就是将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类的复用性增加。但是新的问题又来了,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程,我们把**切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。**有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
参考资料
面向切面编程(也叫面向方面):Aspect Oriented Programming(AOP)。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP是OOP的补充和完善
需求
比如我们要写一个UserService,在调用保存用户和删除用户的时候需要去打开数据库和开启事务
示例代码
说明
通过上面的代码我们发现打开数据,开启事务,提交事务,关闭数据库是重复的代码,有重复的代码我们怎么办?
通过类封装成方法提取重复的代码啊!(抽取成类的方式我们称之为:纵向抽取)
说明
这个时候我们新建一个DBManager类 定义四个方法
示例代码
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();
}
}
说明
通过上面的案例解决了代码重复性的问题,但同时也会带来另外两个问题:
但是,这种做法相比最原始的代码写法,已经有了一些的改进,那么有没有一种方案能解决耦合度,侵入性强的问题,
答案就是代理模式
代理模式是一种非常好理解的一种设计模式,举几个简单的代理的例子:
让代练帮我们升级,我们可能就想下副本,让黄牛帮我们买票,我们就的目的就是想回家,明星让经纪人接拍电影,明星只是需要去拍电影就行了。无论是游戏代练,黄牛,还是经纪人其它他们其实都是在帮我们在做事(做一些我们不想做,或者做不来的事情),但并不能把所有的事情都帮我们做了,比如黄牛帮我们买点票了,回家你还的自己回吧,
静态
由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态
在程序运行时运用反射机制动态创建而成。
代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但必须,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法
静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了
步骤
1:将业务抽象为接口
2:代理对象和被代理对象都实现该接口
3:代理对象持有被代理对象的引用,在执行具体核心业务时交由被代理对象完成。
业务代码
/**
* 简单业务层接口
*/
public interface UserService{
public void saveUser();
}
/**
* 业务层实现类,实现save方法
*/
public class UserServiceImpl implements UserService{
@Override
public void saveUser() {
System.out.println("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();
}
}
测试代码
/**
* 测试类
*/
public class TestProxy {
public static void main(String[] args) {
UserService userService =new UserServiceProxy(new UserServiceImpl());
userService.saveUser();
}
}
由于每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类
所以我们就会想能不能通过一个代理类完成全部的代理功能?,那么我们就需要用动态代理
**JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象 以及方法名 ,动态地创建了一个代理类并执行,然后通过该代理类对象进行方法调用。**我们需要做的,只需指定代理类的预处理、调用后操作即可
目前Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。 其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
说明
这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象
核心方法
返回值 | 方法 | 说明 |
---|---|---|
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) | 用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 |
说明
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问
语法
// 该方法负责集中处理动态代理类上的所有方法调用。,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
Object invoke(Object proxy, Method method, Object[] args)
参数说明
proxy
第一个参数既是代理类实例
method
被调用的方法对象
args
调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
说明
动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
示例代码
// 第一步 定义代理类,实现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;
}
}
说明
jdk动态代理只能代理接口,所以第一步先声明接口,然后在定义个实现类
示例代码
//代理接口类
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;
}
}
测试代码
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();
}
}
可以看到,我们可以通过DynamicProxy代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作
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"));
开发步骤
- 创建目标类:定义接口和和接口实现类(jdk动态代理)或者定义类(cglib)
- 定义通知
- 配置Spring IOC容器
添加引用spring-aspects包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.1.4.RELEASEversion>
dependency>
定义接口
public interface PojoDao {
public void test();
}
定义接口实现类
public class PojoDaoImpl implements PojoDao {
@Override
public void test() {
System.out.println("核心测试方法");
}
}
定义切面类(横切处理类)
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;
}
}
在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>
举例说明
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>
Spring 使用了和AspectJ 一样的注解并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)(编译器与织入器暂时不要管)
说明
为了在Spring中使用@AspectJ切面,你首先必须启用Spring对@AspectJ切面配置的支持,并确保开启自动代理。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行
新建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>
说明
在代码中定义一个类任意在类上使用@Aspect注解
示例代码
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class LogAspect {
}
说明
切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:
@Pointcut
注解来表示(作为切入点签名的方法必须返回void
类型)语法格式
@Pointcut(value="", argNames = "")
参数说明
value
指定切入点表达式
argNames
指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数
示例代码
@Aspect
public class LogAspect {
// 也可以在通知上定义,当需要复用切入点的时候
@Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
// 返回值 必须是void类型
public void log() {
}
}
备注
切入点的定义是非必要的,也可以直接在通知上使用切入点表达式
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式,通知的类型就是我们前面提到过的类型
说明
在关注点执行前运行的方法,切面里使用 @Before
注解声明前置通知
语法格式
@Before(value = "", argNames = "")
参数说明
示例代码
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("前置通知");
}
}
说明
不论一个方法是如何结束的,最终通知都会运行。使用@After
注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。相当于异常处理里finally的代码
语法格式
@After(value = "", argNames = "")
参数
示例代码
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("后置通知");
}
}
说明
返回后通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning
注解来声明
语法格式
@AfterReturning(value="",pointcut="",returning="",argNames="")
参数说明
示例代码
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;
}
}
说明
抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing
注解来声明
语法格式
@AfterThrowing(value="",pointcut="",throwing="",argNames="")
参数说明
示例代码
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("异常通知");
}
}
说明
环绕通知在一个方法执行之前和之后执行。`
@Around
注解;语法格式
@Around(value = "", argNames = "")
参数
示例代码
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;
}
}
说明
若想要在通知方法获取被通知方法的参数共有两种方式:自动获取、手动指定
手动指定
在@pointcut中切入表达式中使用args声明匹配的参数,注意使用&&连接args
在@pointcut中切入表达式中使用参数argNames用来接收AspectJ表达式中的参数,
argNames属性是用于指定在表达式中应用的参数名与Advice方法参数是如何对应的
在通知方法中定义参数
手动获取指定参数
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);
}
}
混用使用
当同时采用自动获取参数与手动指定参数时,自动获取参数必须是第一个参数,即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;
}
}
说明
有时候有一组共享公共行为类。在OOP中,它们必须扩展相同的基类或者实现相同的接口。此外,Java的单继承机制仅允许一个类最多扩展一个基类。所以,不能同时从多个实现类中继承行为。
解决方案:引入是AOP中的一种特殊的通知。它允许为一个接口提供实现类,使对象动态的实现接口。就像对象在运行时扩展了实现类。而且,可以用多个实现类将多个接口同时引入对象。这可以实现与多重继承相同的效果。
在开发中用的不是很多,所以不做过多的分析
说明
被代理的对象,跟前面说的一样,代理接口或者类都可以
示例代码
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);
}
}
示例代码
ApplicationContext context = new ClassPathXmlApplicationContext("spring-aspect.xml");
AspectDao dao = (AspectDao) context.getBean("aspectDao");
dao.test();
dao.testParams(1,"hello");
来开启注解风格的@AspectJ支持;执行流程
AOP定义了一个切面(Aspect),一个切面包含了切入点,通知,引入,这个切面上定义了许多的切入点(Pointcut),一旦访问过程中有对象的方法跟切入点匹配那么就会被AOP拦截。此时该对象就是目标对象(Target Object)而匹配的方法就是连接点(Join Point)。紧接着AOP会用过JDK动态代理或者CGLIB生成一个目标对象的代理对象(AOP proxy),这个过程就是织入(Weaving)。这个时候我们就可以按照我们的需求对连接点进行一些拦截处理。可以看到,我们可以引入(Introduction)一个新的接口,让代理对象来实现这个接口来,以实现额外的方法和字段。也可以在连接点上进行通知(Advice),通知的类型包括了前置通知,返回后通知,抛出异常后通知,后置通知,环绕通知。最后也是最骚的是整个过程不会改变代码原有的逻辑
Spring 使用了和AspectJ 一样的注解并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)(编译器与织入器暂时不要管)
说明
为了在Spring中使用@AspectJ切面,你首先必须启用Spring对@AspectJ切面配置的支持,并确保开启自动代理。自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确保通知在需要时执行
新建spring-aspect.xml配置文件
说明
在代码中定义一个类任意在类上使用@Aspect注解
示例代码
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class LogAspect {
}
说明
切入点决定了连接点关注的内容,使得我们可以控制通知什么时候执行。Spring AOP只支持Spring bean的方法执行连接点。所以你可以把切入点看做是Spring bean上方法执行的匹配。一个切入点声明有两个部分:
@Pointcut
注解来表示(作为切入点签名的方法必须返回void
类型)语法格式
@Pointcut(value="", argNames = "")
参数说明
value
指定切入点表达式
argNames
指定命名切入点方法参数列表参数名字,可以有多个用“,”分隔,这些参数将传递给通知方法同名的参数
示例代码
@Aspect
public class LogAspect {
// 也可以在通知上定义,当需要复用切入点的时候
@Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))")
// 返回值 必须是void类型
public void log() {
}
}
备注
切入点的定义是非必要的,也可以直接在通知上使用切入点表达式
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者前后运行。 切入点表达式可能是指向已命名的切入点的简单引用或者是一个已经声明过的切入点表达式,通知的类型就是我们前面提到过的类型
说明
在关注点执行前运行的方法,切面里使用 @Before
注解声明前置通知
语法格式
@Before(value = "", argNames = "")
参数说明
示例代码
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("前置通知");
}
}
说明
不论一个方法是如何结束的,最终通知都会运行。使用@After
注解来声明。最终通知必须准备处理正常返回和异常返回两种情况。通常用它来释放资源。相当于异常处理里finally的代码
语法格式
@After(value = "", argNames = "")
参数
示例代码
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("后置通知");
}
}
说明
返回后通知通常在一个匹配的方法返回的时候执行。使用 @AfterReturning
注解来声明
语法格式
@AfterReturning(value="",pointcut="",returning="",argNames="")
参数说明
示例代码
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;
}
}
说明
抛出异常通知在一个方法抛出异常后执行。使用@AfterThrowing
注解来声明
语法格式
@AfterThrowing(value="",pointcut="",throwing="",argNames="")
参数说明
示例代码
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("异常通知");
}
}
说明
环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)。
@Around
注解;语法格式
@Around(value = "", argNames = "")
参数
示例代码
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;
}
}
说明
若想要在通知方法获取被通知方法的参数共有两种方式:自动获取、手动指定
手动指定
在@pointcut中切入表达式中使用args声明匹配的参数,注意使用&&连接args
在@pointcut中切入表达式中使用参数argNames用来接收AspectJ表达式中的参数,
argNames属性是用于指定在表达式中应用的参数名与Advice方法参数是如何对应的
在通知方法中定义参数
手动获取指定参数
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);
}
}
混用使用
当同时采用自动获取参数与手动指定参数时,自动获取参数必须是第一个参数,即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;
}
}
说明
有时候有一组共享公共行为类。在OOP中,它们必须扩展相同的基类或者实现相同的接口。此外,Java的单继承机制仅允许一个类最多扩展一个基类。所以,不能同时从多个实现类中继承行为。
解决方案:引入是AOP中的一种特殊的通知。它允许为一个接口提供实现类,使对象动态的实现接口。就像对象在运行时扩展了实现类。而且,可以用多个实现类将多个接口同时引入对象。这可以实现与多重继承相同的效果。
在开发中用的不是很多,所以不做过多的分析
说明
被代理的对象,跟前面说的一样,代理接口或者类都可以
示例代码
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);
}
}
示例代码
ApplicationContext context = new ClassPathXmlApplicationContext("spring-aspect.xml");
AspectDao dao = (AspectDao) context.getBean("aspectDao");
dao.test();
dao.testParams(1,"hello");
来开启注解风格的@AspectJ支持;
如果比较喜欢使用XML格式,Spring2.0也提供了使用新的"aop"命名空间来定义一个切面。 和使用@AspectJ风格完全一样,切入点表达式和通知类型同样得到了支持
AOP配置元素 | 用途 |
---|---|
|
顶层的AOP配置元素,大多数的
|
|
定义一个切面 |
|
定义一个切点 |
|
定义AOP通知器 |
|
定义AOP前置通知 |
|
定义AOP环绕通知 |
|
定义AOP返回通知 |
|
定义AOP异常通知 |
|
定义AOP后置通知(不管被通知的方法是否执行成功) |
|
启用@Aspect注解的切面 |
|
以透明的方式为被通知的对象引入额外的接口 |
说明
在beans元素下 引入aop,声明
注意:
元素内部。
可以包含pointcut,advisor和aspect元素 (注意这三个元素必须按照这个顺序进行声明)
示例代码
说明
切面使用
示例代码
说明
一个命名切入点可以在
示例代码
和@AspectJ风格一样,基于xml的风格也支持5种通知类型并且两者具有同样的语义
说明
前置通知在匹配方法执行前运行。在
示例代码
说明
后置通知在匹配的方法完全执行后运行。和前置通知一样,在
示例代码
说明
异常通知在匹配方法抛出异常退出时执行。在
示例代码