本文已参与 [新人创作礼] 活动 , 一起启动掘金创作之路!
Spring
Spring是主流的 Java Web开发框架 ,该框架是轻量级、性能好、易于测试。
Spring具有控制反转(IoC)和 面向切面(AOP)两大核心。Java Spring 框架通过声明式方式灵活地进 行事务的管理,提高开发效率和质量
Spring优势
- 方便解耦,简化开发
- 方便继承各种优秀框架
- 降低 Java EE API 使用难度
- 方便程序测试
- AOP编程支持
- 声明式事务支持
Spring体系结构
Spring提供了一站式服务,是以模块化形式呈现需要自行选择
- 数据访问/集成
- JDBC模块:提供了JDBC抽象层
- ORM模块:对对象关系映射API
- OXM模块:支持 对象/XML 映射的抽象层实现
- JMS模块:Java消息服务,生产消费信息等
- Transactions事务模块:支持编程和声明式事务管理实现特殊接口类
- Web
- Web模块:Web开发继承的特性
- Servlet模块:Spring 模型-视图-控制器 (MVC) 实现Web应用程序
- Struts模块:支持类内的Spring应用程序
- Portlet模块:提供在Portlet环境中使用MVC实现
- 核心容器
- Beans模块:将对象以Bean进行管理
- Core核心模块:提供框架基本部分, IoC 和 DI 功能 等
- Context上下文模块:是Beans模块基础之上,是访问和配置文件任何对象的媒介
- Expression Language模块:运行时查询和对象图的强大的表达式语音
- 其他
- AOP模块:面向切面的编程实现,允许定义方法拦截器和切入点,按方法功能进行分离,降低耦合性
- Aspects模块:提供与AspectJ的集成,是功能强大的AOP框架
- Instrumentation模块:提供类工具的支持和类加载器的实现,在特定的应用服务器中使用
- Test模块:支持JUnit 或 TestNG 框架测试
Spring IOC
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想
Ioc 在开发中,无需自行实例对象,而是有 Spring Ioc容器 创建。Spring容器会负责控制程序之间的关系,而不是由代码直接控制,因此,控制权由 Spring容器 接管,控制权发生了反转,是Ioc设计思想
Spring 提供了两种 IoC 容器,分别为 BeanFactory 和 ApplicationContext
对象说明和获取
BeanFactory
BeanFactory接口 是一个管理Bean的工厂,根据xml配置文件中的定义Bean进行管理,主要负责初始化各种Bean,并调用他们的生命周期方法
ApplicationContext
ApplicationContext 是 BeanFactory 的子接口,也是 应用上下文。支持了 BeanFactory 的所有功能
ApplicationContext接口有两个常用的 实现类,主要用于加载配置文件的
-
ClassPathXmlApplicationContext 通过 类路径上下文加载
==new ClassPathXmlApplicationContext(<Spring配置文件的名>)==
将普通路径解释为包含包路径的类路径资源名称(例如“mypackage/myresource.txt”)
-
FileSystemXmlApplicationContext 通过 文件系统路径/URL 加载指定配置文件
==new FileSystemXmlApplicationContext(<文件系统路径/URL>);==
**注意:**普通路径将始终被解释为相对于当前 VM 工作目录,即使它们以斜杠开头 使用显式的“file:”前缀来强制使用绝对文件路径
对象获取实例
//指定决定绝对路径的文件
new FileSystemXmlApplicationContext(new FileSystemResource("D:\applicationContext.xml"));
//指定相对根路径的文件
new ClassPathXmlApplicationContext("applicationContext.xml");
Bean标签
Spring Bean标签 可以想象成 我们使用的 实例对象 ,Spring 是通过在配置文件中进行调取出来的实例对象,而往常的操作 是通过 硬编码 new 出来的对象 ,因此 我们需要提前 编写好配置文件
Bean标签,因此我们需要自己手动配置Bean对象,以下有两种Spring配置文件支持的格式
- Properties配置文件 (只能存在键值形式存在
- XML配置文件
XML配置文件
XML 格式配置文件的根元素是 <beans>,该元素包含了多个 <bean> 子元素,每一个 <bean> 子元素定义了一个 Bean,并描述了该 Bean 如何被装配到 Spring 容器中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 使用id属性定义person1,其对应的实现类为com.mengma.person1 (类路径及包路径)-->
<bean id="person1" class="com.mengma.person1 " />
<!--使用name属性定义person2,其对应的实现类为com.mengma.domain.Person2 (类路径及包路径) -->
<bean name="Person2" class="com.mengma.domain.Person2"/>
</beans>
bean标签属性说明
| 属性 | 值 | 说明 |
|---|---|---|
id | String | Bean 的唯一标识符,Spring IoC 容器对 Bean的 配置和管理都通过该属性完成 |
name | String | Bean 的名称,可通过 name属性 为同一个 Bean同时指定多个名称,每个名之间用 逗号/分号 隔开。Spring容器 可通过 name属性 配置和管理容器中的 Bean |
class | String | Bean 的具体实现类 (全限定名 |
scope | String | 定义 的 [Bean作用域](#Bean 作用域) (点击跳转 |
autowire | byName | byType | Bean自动装配,根据定义的 name/type 进行自动装配 |
lazy-init | boolean | 延迟加载 (默认false),在getBean()方法调用时才实例 |
init-method | String | 初始化对象方法 |
destroy-method | String | 销毁对象方法 |
示例
实体类 People.calss
package com.sans;
public class Person {
private String name;
private String msg;
public Person() {}
public Person(String name, String msg) {
this.name = name;
this.msg = msg;
}
// 省略 自动生成的 set/get 方法
public void init() {
System.out.println("Person对象初始化: "+this);
}
public void destroy() {
System.out.println("Person对象销毁: "+this);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}
配置文件 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Bean实例方式
- 构造器实例
- 静态工厂实例
- 实例工厂实例
-->
<!-- 无参构造器 -->
<bean id="person1" class="com.sans.Person"/>
<!-- 有参构造器 -->
<!-- Bean属性 初始化/销毁 -->
<bean id="person2" class="com.sans.Person" init-method="init" destroy-method="destroy">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="Spring挺简单的嘛"/>
</bean>
<!-- 静态工厂实例 -->
<bean id="personFactory1" class="com.sans.MyFactory" factory-method="instanceLisi" />
<!-- 实例工厂实例 -->
<bean id="factory" class="com.sans.MyFactory"/>
<bean id="personFactory2" factory-bean="factory" factory-method="instanceZhangsan"/>
</beans>
测试
@Test
public void beanExampleTest() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person1 = (Person) applicationContext.getBean("person1");
Person person2 = (Person) applicationContext.getBean("person2");
Person factory1 = (Person) applicationContext.getBean("personFactory1");
Person factory2 = (Person) applicationContext.getBean("personFactory2");
System.out.println("person1 = " + person1);
System.out.println("person2 = " + person2);
System.out.println("factory1 = " + factory1);
System.out.println("factory2 = " + factory2);
/* 结果:
Person对象初始化: Person{name='张三', msg='Spring挺简单的嘛'}
Person对象初始化: Person{name='null', msg='null'}
person1 = Person{name='null', msg='null'}
person2 = Person{name='张三', msg='Spring挺简单的嘛'}
factory1 = Person{name='李四', msg='我也这么觉得!'}
factory2 = Person{name='张三', msg='Spring挺简单的嘛'}
*/
}
Bean 作用域
Spring Bean作用域,可方便的管理 Bean应用的时期 以及场景
只需在 <bean>.scope属性 配置范围值即可。scope范围值有以下6种:
| 属性值 | 说明 |
|---|---|
singleton | 单例模式,每次在容器获取都是同一 Bean实例 (默认值) |
prototype | 原型模式,每次在容器获取都是新的 Bean实例 |
request | 每次request请求,容器都会创建一个 Bean实例。该域只在当前 request 内有效 |
session | 每次session会话,不同的会话使用的 Bean实例不同。该域仅在当前 Session 内有效 |
application | Web应用 共享一个 Bean实例。该域在当前 ServletContext 内有效(和单例模式差不多 |
websocket | websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效 |
以上属性应用自行测试
依赖注入 DI
DI—Dependency Injection,即 依赖注入。需要通过 简单的配置,而无需任何代码就可指定目标需要的资源。而注入的方式有:
- setter
- 构造器
- 接口
- 注解
Ioc 和 DI 是同一个概念的不同角度描述。IoC是一种思想;DI是实现它的手段 Spring框架使用依 赖注入实现IoC
属性注入
Bean 属性注入,是将属性数据注入到 Bean 中的过程
实现属性注入方式 :
- 构造函数注入
- setter 注入
bean标签 代表的是一个实例对象,实例对象中 包含多个 属性/子标签 等。以下说明当中的标签以及作用
constructor-arg标签 构造实例。应用的构造器指定的参数进行实例对象。该标签有以下属性
| 属性 | 值 | 说明 |
|---|---|---|
index | String | 传参的序号(从0开始 |
value | String | 指定传递的 常量值 |
name | String | 构造方法对应的 形参名称 |
ref | String | 引用配置 Bean实例 |
type | String | 传参的属性类型 |
**ref:**引用可通过 id/name 属性的值 进行引入
property标签 set注入。该标签是通过 属性的set方法进行填充数据,因此属性一定要有set方法。该标签有以下属性
| 属性 | 值 | 说明 |
|---|---|---|
name | String | 属性名称 |
ref | String | 引用配置 Bean实例 |
value | String | 配置指定 常量值 |
注意:
name属性 在配置的时候,如果失去高亮,那么很有可能实体类没有配置该属性的 set方法 (前提:需要配置Spring上下文
set注入P命名空间,使用前提父标签需要引入以下配置
==xmlns:p="www.springframework.org/schema/p"==
集合属性配置 在配置当中难免会存在一些特殊情况,例如集合属性的配置。以下是 集合标签的说明
| 标签 | 说明 |
|---|---|
<array> | 封装数组(可重复 |
<list> | 封装 List (可重复 |
<set> | 封装 Set(不可重复 |
<map> | 封装 Map (k和v均可任意类型 |
<props> | 封装 Map (k和v都是字符串类型 |
-
<array>封装数组(可重复<ref>.bean:引用对象元素<value>:常量值
-
<list>封装List(可重复<ref>.bean:引用对象元素<value>:常量值
-
<set>封装 Set(不可重复<ref>.bean:引用对象元素<value>:常量值
-
<map>封装 Map (k和v均可任意类型-
<entry>.key:Map键 K -
<entry>.value:Map值 V键和值可以 都可以 引用实例对象。引用方式 属性名后缀添加
-ref
-
-
<props>封装 Map (k和v都是字符串类型<prop>.key:Map键 K<prop>:Map值 V(标签包裹
应用示例
步骤:
- 编写好实体类的属性以及get/set方法
- 在 xml/Properties 配置上编辑 bean实例
- 通过 ==new ClassPathXmlApplicationContext(<配置文件路径>).getBean()==方法获取实例对象
实体类 Person.class
public class Person {
private String name;
private String msg;
// 数组类型
private Person[] array;
// 集合 特殊应用
private List<Person> list;
private Set<String> set;
private Map<String, Object> map;
private Properties properties;
// 构造方法注入
public Person() {
}
public Person(String name, String msg) {
this.name = name;
this.msg = msg;
}
// 省略 自动生成的 get/set 方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", msg='" + msg + '\'' +
'}';
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 无参构造 -->
<bean id="person1" class="com.sans.bean.Person"/>
<!-- 全参构造 -->
<bean id="person2" class="com.sans.bean.Person">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="Spring挺简单的嘛"/>
</bean>
<!-- set注入 -->
<bean id="person3" class="com.sans.bean.Person">
<property name="name" value="李四"/>
<property name="msg" value="我也觉得"/>
</bean>
<!-- P命名空间 set注入-->
<bean id="person4" class="com.sans.bean.Person" p:name="王五" p:msg="还行吧!挺好的"/>
<!-- set方法 集合注入 -->
<bean id="person5" class="com.sans.bean.Person">
<!-- 数组类型 -->
<property name="array">
<array>
<ref bean="person1"/>
<ref bean="person2"/>
<ref bean="person3"/>
<ref bean="person4"/>
</array>
</property>
<!-- List类型 -->
<property name="list">
<list>
<ref bean="person1"/>
<ref bean="person2"/>
<ref bean="person3"/>
<ref bean="person4"/>
</list>
</property>
<!-- Set类型 -->
<property name="set">
<set>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</set>
</property>
<!-- Map类型 -->
<property name="map">
<map>
<entry key="No1" value="张三" />
<entry key="No2" value="李四"/>
<entry key="No3" value="王五"/>
</map>
</property>
<property name="properties">
<props>
<prop key="No1">张三</prop>
<prop key="No2">李四</prop>
<prop key="No3">王五</prop>
<prop key="No4">12</prop>
</props>
</property>
</bean>
</beans>
测试结果
public class Demo {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
/* 通过构造器 实例 */
@Test
public void constructor() {
Person person1 = (Person) applicationContext.getBean("person1");
Person person2 = (Person) applicationContext.getBean("person2");
System.out.println("person1 = " + person1);
System.out.println("person2 = " + person2);
/* 结果 toString方法直接打印
person1 = Person{name='null', msg='null'}
person2 = Person{name='张三', msg='Spring挺简单的嘛'}
*/
}
/* set注入 实例 */
@Test
public void setInjection() {
Person person3 = (Person) applicationContext.getBean("person3");
Person person4 = (Person) applicationContext.getBean("person4");
Person person5 = (Person) applicationContext.getBean("person5");
System.out.println("person3 = " + person3);
System.out.println("person4 = " + person4);
// 由于 person4对象 针对了 数组/集合 类型的属性测试
System.out.println("person5.getArray() = " + Arrays.toString(person5.getArray()));
System.out.println("person5.getList() = ");
person5.getList().forEach(person -> System.out.println("\t"+person));
System.out.println("person5.getSet() = " + person5.getSet());
System.out.println("person5.getMsg() = ");
person5.getMap().forEach((key, value) -> System.out.println("\t"+key+" : "+value));
System.out.println("person5.getProperties() = " + person5.getProperties());
/* 结果 (PS: 由于有一个是无参的引用因此为null
person3 = Person{name='李四', msg='我也觉得'}
person4 = Person{name='王五', msg='还行吧!挺好的'}
person5.getArray() = [Person{name='null', msg='null'}, Person{name='张三', msg='Spring挺简单的嘛'}, Person{name='李四', msg='我也觉得'}, Person{name='王五', msg='还行吧!挺好的'}]
person5.getList() =
Person{name='null', msg='null'}
Person{name='张三', msg='Spring挺简单的嘛'}
Person{name='李四', msg='我也觉得'}
Person{name='王五', msg='还行吧!挺好的'}
person5.getSet() = [张三, 李四, 王五]
person5.getMsg() =
No1 : 张三
No2 : 李四
No3 : 王五
person5.getProperties() = {No2=李四, No1=张三, No4=12, No3=王五}
*/
}
}
自动注入
Spring 的自动注入功能可以让 Spring容器 依据指定规则,为指定的 Bean 从应用的上下文中查找它所依赖的 Bean 并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何 <constructor-arg>.ref / <property>.ref 形式配置Bean
自动注入 主要作用是 简化 Spring 在 XML配置应用,因此在大工程中降低很多工作量
Spring容器 默认不支持自动装配的,需要在配置文件中的 <bean>.autowire属性应用
<bean>.autowire属性说明
| 属性值 | 说明 |
|---|---|
byName | 根据 名称 自动注入 (id/name均可引用 |
byType | 根据 类型 自动注入 |
constructor | 根据 构造器参数 的数据类型,进行 byType模式 的自动装配 |
default | 默认采用上一级元素 <beans>设置的自动装配规则(default-autowire)进行装配 |
no | 默认值,不使用自动注入 |
byName装配
ByName表示 按属性名称自动装配,配置文件中 <bean>.id/name属性值 必须与 类中属性名称 相同
示例
Person实体对象 (省略
public class MyFactory {
private Person person1;
private Person person2;
// 省略 自动生成的 set/get 方法
}
配置文件
<!-- 使用 id/name 属性 标明 -->
<bean id="person1" class="com.sans.bean.Person" p:name="张三" p:msg="Spring挺简单的嘛!"/>
<bean name="person2" class="com.sans.bean.Person" p:name="李四" p:msg="我也觉得"/>
<!-- byName -->
<bean id="Myfactory" class="com.sans.MyFactory" autowire="byName"/>
测试
@Test
public void auotInjection() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyFactory factory = (MyFactory) applicationContext.getBean("Myfactory");
System.out.println(factory.getPerson1());
System.out.println(factory.getPerson2());
/* 结果:
Person{name='张三', msg='Spring挺简单的嘛!'}
Person{name='李四', msg='我也觉得'}
*/
}
byType
byType表示 按属性类型自动装配,配置文件中 被注入类中的属性类型 与 容器内的Bean类型 相同,则自动注入
注意:
- 如果类中有一个以上的相同属性类型,那么该类型全部自动注入
- 在Spring Ioc容器上下文应用中不能存在相同类型的 Bean实例,否则无法引用
示例
Person实体对象 (省略
实体工厂对象 (省略
配置文件
<bean id="person1" class="com.sans.bean.Person" p:name="张三" p:msg="Spring挺简单的嘛!"/>
<!-- 不能存在相同类型
<bean name="person2" class="com.sans.bean.Person" p:name="李四" p:msg="我也觉得"/>-->
<!-- byClass -->
<bean id="Myfactory" class="com.sans.MyFactory" autowire="byType"/>
测试
@Test
public void auotInjection() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MyFactory factory = (MyFactory) applicationContext.getBean("Myfactory");
System.out.println(factory.getPerson1());
System.out.println(factory.getPerson2());
/* 结果:
Person{name='张三', msg='Spring挺简单的嘛!'}
Person{name='张三', msg='Spring挺简单的嘛!'}
*/
}
注解装配Bean
在 Spring 中,尽管使用 XML配置文件可实现 Bean 的装配工作,如果应用中 Bean 的数量较多,会导致 XML配置文件过于臃肿,从而给维护和升级带来一定的困难,因此 Spring 提供了注解应用,需要在原有的运行环境基础上做些变化,由此减少过多的Bean
@Component
在类上添加上 @Component注解 表示该类实例的对象的权限交给 Spring容器 。注解的value属性用于指定bean的 id值 或 name值 ,但一般情况可省略 value 属性!(该注解指定id是类名的首字母小写)
以下注解与 @Component注解 用法和功能 相同,表达的含义不同!
- @Repository dao层实现类的注解(持久层)
- @Service service层实现类的注解(业务层)
- @Controller controller层实现类的注解(控制层)
@Value
该 注解 是为指定属性 注入值。该注解用在 类的属性 或 指定set方法上 。 其注解注入 原理 和 set方法 写入是一样,因此 set方法 也可
package com;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class MyUser {
//方式1
@Value("001")
private int id;
@Value("张三")
private String name;
@Value("23")
private int age;
@Value("洛杉矶")
private String locateion;
···
//方式2
@Value("001")
public void setId(int id) {
this.id = id;
}
@Value("张三")
public void setName(String name) {
this.name = name;
}
@Value("23")
public void setAge(int age) {
this.age = age;
}
@Value("洛杉矶")
public void setLocateion(String locateion) {
this.locateion = locateion;
}
···
}
包扫描
需要在 xml配置文件中配置组件扫描器,用于在指定包中扫描的注解。如果未添加扫描,则对象添加的注解 将无法实例化
xml配置文件 添加扫描
beans标签 配置属性
xmlns:context属性:xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation属性(添加值):http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd··· <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> ··· </beans>context:component-scan标签 指定包扫描 分隔符可以使用逗号(,)分号(;)还可以使用空格,不建议使用空格。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 包扫描 方法1: (包路径 Annotation.dao、Annotation.service、Annotation.controller)--> <context:component-scan base-package="Annotation.dao"/> <context:component-scan base-package="Annotation.service"/> <context:component-scan base-package="Annotation.controller"/> <!-- 包扫描 方法2: (包路径 Annotation.dao、Annotation.service、Annotation.controller)--> <context:component-scan base-package="Annotation.dao;Annotation.service;Annotation.dao"/> </beans>添加对应注解即可
自动注入
自动注入指定对象的前提,需要为该对象添加注解,且 上级包 或 指定类 有扫描到!
@Autowired
用于对 Bean 的 属性变量、属性Set方法 及构造方法进行标注(构造方法应用的前提,参数必须指定的是对应的实例对象),配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型 进行装配
@Autowired 还有一个属性 required,默认值为 true,表示匹配失败后终止程序运行;若值为 false,则匹配失败后 其属性值为 null
@Qualifier
与 @Autowired 注解配合使用,会将默认的按 Bean类型 装配修改为按 Bean实例队形名称 装配,Bean 的实例名称由 @Qualifier 注解的 value参数 指定
@Resource
该注解 在jdk1.6版本以上 支持使用,Bean属性可指定按照 类型(type) 或 名称(name) 进行自动注入,可在 属性变量、属性Set方法 进行使用
Spring AOP
Spring的AOP实现底层就是对上面动态代理的代码进行封装,封装后我们只需要对关注部分进行代码编写,并通过配置的方式完成指定目标的方法增强
Spring 通知 指定目标类方法 的连接点位置,分为以下5种通知类型
通知类型
- 前置通知 在方法执行前的通知,可以应用于权限管理等功能
- 后置通知 在方法执行后的通知,可以应用于关闭流、上传文件、删除临时文件等功能
- 环绕通知 在方法执行 前、后 都通知,可以应用于日志、事务管理等功能
- 异常通知 在方法抛出异常时的通知,可以应用于处理异常记录日志等功能
- 最终通知 方法执行完毕后最后的通知
AOP相关术语:
- Target(目标对象) 要被增强的对象,一般业务逻辑类对象
- Proxy(代理) 一个类被AOP 织入增强后,产生一个结果代理类
- Aspect(切面) 切面点 增强功能,就是一些代码完成某些功能,非业务功能。是切入点和通知的结合
- Joinpoint(连接点) 连接点 指 那些被拦截的点。在Spring中,这些点指定与 核心业务的方法(Spring只支持方法类型的连接点)
- Pointcut(切入点) 切入点 指 声明一个或多个连接点的集合。通过切入点指定一组方法(非final方法)
- Advice(通知/增强) 通知 指 拦截到 连接点 后要做的 通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同
- Weaving(织入) 织入 指 增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
AspectJ 实现AOP
AspectJ 基于 Java语言的AOP框架,它扩展了 Java语言,使得AOP功能使用更便捷
切入点表达式
AspectJ 定义 专门的表达式 指定 切入点
表达式原型:
execution([modifiers-pattern] ret-type-pattern declaring-type-pattern name-pattern(param-pattern) [throws-pattern])
//必要参数:方法返回值 方法声明(参数)
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
参数说明 (中括号表示可选类型)
modifiers-pattern :访问权限类型 ret-type-pattern :返回类型 declaring-type-pattern :包名类名 name-pattern(param-pattern) :方法名(参数的 类型 和 个数) throws-pattern :抛出异常类型
PS:表达式各个部分可以用空格隔开,可用以下符号:
| 符号 | 范围 | 说明 |
|---|---|---|
| * | 所有 | 0 ~ n 个任意字符 |
| .. | 方法参数、包路径 | 指定任意个参数;当前包的子路径 |
| + | 类名、接口 | 当前类路径的子类;当前接口及实现的类 |
切入点表达式实例
execution(* com.service.*.*(..))定义 com.service 包路径里的 任意类、任意方法、方法任意返回类型
execution(* com.service..*.*(..))定义 com.service 包路径里的 任意子包、任意类、任意方法、方法任意返回类型
execution(* com.service.IUserService+.*(..))当路径 com.service.IUserService 的文件类型为以下条件: 若为接口,则为接口中 任意方法及其所有实现类中的任意方法 若为类,则为该类及其子类中的任意方法
AOP声明方式
- 基于
XML声明 - 基于
Annotation声明
XML实现AOP
基于 XML文件配置 切面、切入点 等,Spring 也支持 AOP 的应用,不过应用前提需要 aop命名空间,aop命名空间提供了 <aop:config>标签 主要用于配置AOP应用
实现说明:
-
XML配置AOP 必须在
<beans>头标签,添加以下<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd .... "> -
AOP相关的信息,都在
<aop:config>标签中配置,且他们的子标签必须按照顺序进行配置 **顺序:**pointcut => advisor => aspect
实现步骤:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.sans</groupId>
<artifactId>Spring-AOP</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<!-- CGLIB的动态代理 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
<!--aop切面依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
业务接口 UserService
public interface UserService {
int add(User user);
int del(int id);
int update(User user);
List<User> findAll();
List<User> findById(int id);
}
业务实现类 UserServiceImpl
用于模拟业务需求的场景进行编写的代理应用,本次限于测试学习!
public class UserServiceimpl implements UserService {
@Override
public int add(User user) {
System.out.println("add() 业务方法执行");
return 1;
}
@Override
public int del(int id) {
System.out.println("del() 业务方法执行");
return 1;
}
@Override
public int update(User user) {
System.out.println("update() 业务方法执行");
// 制造异常
int i = 1 / 0;
return 0;
}
@Override
public List<User> findAll() {
System.out.println("findAll() 业务方法执行");
return null;
}
@Override
public List<User> findById(int id) {
System.out.println("findById() 业务方法执行");
return null;
}
}
代理类 MyAspect
public class MyAspect {
// 前置方法
public void before(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
Object target = jp.getTarget();
System.out.println("==>前置方法" +
"\n\t方法名:" + methodName +
"\n\t参数:" + Arrays.toString(args) +
"\n\t目标:" + target
);
}
// 后置方法
public void afterReturning(JoinPoint jp , Object returnValue) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
Object target = jp.getTarget();
System.out.println("==>后置方法" +
"\n\t方法名:" + methodName +
"\n\t参数:" + Arrays.toString(args) +
"\n\t目标:" + target +
"\n\t返回值:" + returnValue
);
}
/* 前后环绕方法
* 参数:强化环绕封装对象
* 返回:业务方法的返回值
* */
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 前置环绕
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
Object target = pjp.getTarget();
System.out.println("==>前置环绕方法" +
"\n\t方法名:" + methodName +
"\n\t参数:" + Arrays.toString(args) +
"\n\t目标:" + target
);
// 调用被 拦截的代理方法
Object returnValue = pjp.proceed();
// 后置环绕
String methodName2 = pjp.getSignature().getName();
Object[] args2 = pjp.getArgs();
Object target2 = pjp.getTarget();
System.out.println("==>后置环绕方法" +
"\n\t方法名:" + methodName2 +
"\n\t参数:" + Arrays.toString(args2) +
"\n\t目标:" + target2+
"\n\t返回值:" + returnValue
);
return returnValue;
}
// 异常增强
public void afterThrowing(JoinPoint jp,RuntimeException e) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
Object target = jp.getTarget();
System.out.println("==>异常增强" +
"\n\t方法名:" + methodName +
"\n\t参数:" + Arrays.toString(args) +
"\n\t目标:" + target
);
System.out.println("异常增强的消息:" + e.getMessage());
}
}
配置文件 aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- 托管 业务Bean对象 -->
<bean id="userService" class="com.sans.service.impl.UserServiceimpl"/>
<!-- 托管 切面对象 -->
<bean id="myAspect" class="com.sans.proxy.MyAspect"/>
<!-- xml代理切入 -->
<aop:config>
<!-- 解释:任意权限 指定com.sans.service包下的所有类中所有方法以及方法中的所有参数-->
<aop:pointcut id="pointcut" expression="execution(* com.sans.service.*.*(..))"/>
<aop:aspect ref="myAspect">
<!-- 前置 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 后置 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="returnValue"/>
<!-- 异常 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e" />
<!-- 环绕 -->
<aop:around method="around" pointcut-ref="pointcut"
</aop:aspect>
</aop:config>
</beans>
@Test
public void xmlTest() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add(new User("张三",23));
// userService.del(1);
// userService.update(new User("张三",23));
// userService.findAll();
// userService.findById(23);
/* 剩下自行测试
==>前置方法
方法名:add
参数:[User{id='null', name='张三', age=23}]
目标:com.sans.service.impl.UserServiceimpl@7905a0b8
==>前置环绕方法
方法名:add
参数:[User{id='null', name='张三', age=23}]
目标:com.sans.service.impl.UserServiceimpl@7905a0b8
add() 业务方法执行
==>后置环绕方法
方法名:add
参数:[User{id='null', name='张三', age=23}]
目标:com.sans.service.impl.UserServiceimpl@7905a0b8
返回值:1
==>后置方法
方法名:add
参数:[User{id='null', name='张三', age=23}]
目标:com.sans.service.impl.UserServiceimpl@7905a0b8
返回值:1
*/
}
Annotation实现AOP(待解决)
对切面类使用指定 注解 定义 切面、切入点 等
注解类型
| 注解名称 | 范围 | 说明 |
|---|---|---|
@Aspect | 类 | 定义 切面 |
@Pointcut | 类 | 定义 切入点表达式 (execution()控制切入点 |
@Before | 方法 | 定义 前置 通知 |
@AfterReturning | 方法 | 定义 后置 通知 |
@Around | 方法 | 定义 环绕 通知 |
@AfterThrowing | 方法 | 定义 异常 通知 |
@After | 方法 | 定义 最终 通知(异常也会通知) |
依赖配置 、 业务类 和 业务接口 与上述一致
切面类
package com.newAOP.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面类
* @author Sans
*/
@Component //创建权限交给 spring容器 进行创建
@Aspect //aspectj 框架注解 标识该类为切面类
public class MyAspect {
/**
* 切点表达式为:
* 方法 任意返回类型
* 指定路径 com.newAOP包
* 指定包下的所有 子包、子类、类、接口、方法、方法参数
*/
@Pointcut("execution(* com.newAOP..*.*(..))")
private void pointCut_all(){ }
/**
* 前置通知
* @param jp
*/
@Before("pointCut_all()")
public void before(JoinPoint jp){
System.out.println("======================BeforeOpen");
System.out.println("前置通知");
System.out.println("拦截信息:");
System.out.println("\t方法名称:"+jp.getSignature().getName());
Object[] args = jp.getArgs();
if (args.length != 0){
System.out.println("\t参数格式:"+args.length);
System.out.println("\t参数列表:");
for (Object arg : args) {
System.out.println("\t\t"+arg);
}
}
System.out.println("======================BeforeEnd");
}
/**
* 后置通知
* @param result
*/
@AfterReturning (value="pointCut_all()",returning = "result")
public void afterReturn(Object result){
System.out.println("======================AfterReturnOpen");
System.out.println("后置通知");
System.out.println("拦截信息:");
System.out.println("\t方法返回值:"+result);
System.out.println("======================AfterReturnEnd");
}
/**
* 环绕通知
* @param pjp
* @return 方法返回类型
* @throws Throwable 当前方法运行抛出的异常
*/
@Around("pointCut_all()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("======================AroundOpen");
System.out.println("环绕通知");
Object obj = pjp.proceed();
System.out.println("方法 返回值为:"+obj);
System.out.println("======================AroundEnd");
return obj;
}
/**
* 异常通知
* @param jp 连接点状态
* @param ex 异常返回
*/
@AfterThrowing(value = "pointCut_all()",throwing = "ex")
public void exception(JoinPoint jp,Throwable ex){
System.out.println("======================ExceptionOpen");
System.out.println("异常通知");
//返回连接点的签名(异常)
System.out.println("异常原因:"+jp.getSignature());
//返回 throwable 的详细消息字符串
System.out.println("异常类型:"+ex.getMessage());
System.out.println("======================ExceptionEnd");
}
/**
* 最终通知(无论出现异常都会执行的通知)
*/
@After("pointCut_all()")
public void myFinally(JoinPoint jp){
System.out.println("======================FinallyOpen");
System.out.println("最终通知");
System.out.println(jp.getSignature().getName()+" 方法结束!");
System.out.println("======================FinallyEnd");
}
}
配置文件 (扫描包,切面类注解应用)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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">
//扫描包。个人配置包路径 为 com.newAOP
<context:component-scan base-package="com.newAOP" />
//允许 Spring容器 权限应用
<aop:aspectj-autoproxy expose-proxy="true"/>
</beans>
测试
@Test
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("MySpring.xml");
IService teamService = (IService) ac.getBean("teamService");
teamService.add(001,"张三");
System.out.println("\n");
teamService.update(6, 5);
}
/*运行结果
======================AroundOpen
环绕通知
======================BeforeOpen
前置通知
拦截信息:
方法名称:add
参数格式:2
参数列表:
1
张三
======================BeforeEnd
MyAspect---add---
======================AfterReturnOpen
后置通知
拦截信息:
方法返回值:null
======================AfterReturnEnd
======================FinallyOpen
最终通知
add 方法结束!
======================FinallyEnd
方法 返回值为:null
======================AroundEnd
======================AroundOpen
环绕通知
======================BeforeOpen
前置通知
拦截信息:
方法名称:update
参数格式:2
参数列表:
6
5
======================BeforeEnd
MyAspect---update---
======================AfterReturnOpen
后置通知
拦截信息:
方法返回值:true
======================AfterReturnEnd
======================FinallyOpen
最终通知
update 方法结束!
======================FinallyEnd
方法 返回值为:true
======================AroundEnd
*/
XML与Annotation 声明区别
xml在外部文件
.xml进行配置;Annotation在类文件中进行配置 无需外部辅助xml效率一般(需要解析);Annotation效率高
xml需要解析工具进行完成;Annotation无需解析,利用Java反射进行完成
xml易于观察对象关系(业务量较多时);Annotation 不易观察
问题
- 代理应用 用于接口的实现过程,如果实现类并未有接口则无法实现
Spring JDBC
Spring 针对数据库开发提供了 JdbcTemplate 类,它封装了 JDBC,支持对数据库的所有操作
JDBC以往的说明:Java学习记录 JDBC篇
jar包:
- spring-jdbc
- spring-tx
- spring-core
应用步骤:
方法
| 返回 | 方法 | 说明 |
|---|---|---|
| int | update(String sql) | 用于执行新增、修改、删除等语句 |
| int | update(String sql,Object... args) | 用于执行新增、修改、删除等语句 args 表示需要传入的参数 |
| void | execute(String sql) | 可以执行任意 SQL,一般用于执行 DDL 语句 action 表示执行完 SQL 语句后,要调用的函数 |
| T | query(String sql, ResultSetExtractor rse) | 用于执行查询语句 以 ResultSetExtractor 作为参数的 query 方法返回值为 Object |
| List | query(String sql, RowMapper rse) | 使用查询结果需要对其进行强制转型 以 RowMapper 作为参数的 query 方法返回值为 List |
| Map<String, Object> | queryForMap(String sql) | SQL查询多个聚合函数结果值,查询出的结果值形式:key-value |
| ... | ... | ... |
JDBC应用
-
导入包
spring-jdbc-x.x.x.jar、spring-tx-x.x.x.jar以下为个人用Maven配置<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.13.RELEASE</version> </dependency> -
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- XML 配置数据源--> <bean id="dateSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <!--驱动加载--> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <!--连接数据库的url . 本次连接 test库--> <!--指定IP地址 、 库(个人应用的 test库)--> <property name="url" value="jdbc:mysql://192.168.74.131/test"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!-- 配置jdbc模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--必须使用数据源--> <property name="dataSource" ref="dateSource"/> </bean> <!-- 配置注入类使用--> <bean id="xxx" class="xxx"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <!-- 例如:--> <!-- <bean id="studentDao" class="com.StudentDao">--> <!-- <property name="jdbcTemplate" ref="jdbcTemplate"/>--> <!-- </bean>--> ... </beans>配置注入类需要自己指定类进行配置
-
创建实体类
Studentpublic class Student { int id ; String name; int age; // 省略多余的 get和set方法 @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } } -
数据库 test库引入库
id name age DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int(11) NULL DEFAULT 16, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; -
指定类注入使用
JdbcTemplate类(应用指定实例 StudentDao;实现一个操作业务即可)public class StudentDao extends JdbcDaoSupport { // 方法封装 public Student turnEncapsulation(ResultSet resultSet) throws SQLException { Student student = new Student(); student.setId(resultSet.getInt("id")); student.setName(resultSet.getString("name")); student.setAge(resultSet.getInt("age")); return student; } /** * 添加数据 * @param student 学生类封装 * @return 更变条数 */ public int insert(Student student) { if (student == null) { return 0; } String sql = "INSERT INTO student(name,age) VALUE(?,?)"; return this.getJdbcTemplate().update(sql,student.getName(),student.getAge()); } /** * 删除数据 * @param id 删除指定id * @return 更变条数 */ public int delete(int id) { String sql = "DELETE FROM student WHERE id = ?"; return this.getJdbcTemplate().update(sql,id); } /** * 更变数据 * @param id 指定学生id * @param student 更变指定 学生类 * @return 更变条数 */ public int update(int id , Student student) { String sql = "UPDATE student set name=?,age=? WHERE id=?"; return this.getJdbcTemplate().update(sql,student.getName(),student.getAge(),id); } /** * 查询所有条数 * @return 学生队列 */ public List<Student> queryAll(){ String sql = "SELECT * FROM student"; return this.getJdbcTemplate().query(sql , new RowMapper<Student>() { @Override public Student mapRow(ResultSet resultSet , int i) throws SQLException { System.out.println("\ti : " + i); return turnEncapsulation(resultSet); } }); } /** * 查询指定学生 * @param id 指定学生id * @return 学生队列 */ public List<Student> queryFindById(int id) { String sql = "SELECT * FROM student WHERE id = ?"; return this.getJdbcTemplate().query(sql,new Object[]{id}, new RowMapper<Student>() { @Override public Student mapRow(ResultSet resultSet , int i) throws SQLException { System.out.println("\ti : " + i); return turnEncapsulation(resultSet); } }); } /** * 查询指定学生名称 * @param name 名称 * @return 学生队列 */ public List<Student> queryFindByName(String name) { String sql = "SELECT * FROM student WHERE name = ?"; //匿名 new RowMapper<Student>() 替换为 lambda return this.getJdbcTemplate().query(sql,new Object[]{name}, (resultSet , i) -> { System.out.println("\ti : " + i); return turnEncapsulation(resultSet); }); } /** * 聚合函数应用 */ /** * 获取学生总数 * @return 学生总数 */ public int tableSize(){ String sql = "SELECT count(id) FROM student"; return this.getJdbcTemplate().queryForObject(sql,Integer.class); } /** * 学生首尾id * @return MAP形式返回id首尾 */ public Map<String , Object> tableMaxMin(){ String sql = "SELECT max(id),min(id) FROM student"; return this.getJdbcTemplate().queryForMap(sql); } } -
public class connectionMySQL { ApplicationContext ac = new ClassPathXmlApplicationContext("springJDBC.xml"); StudentDao dao = (StudentDao) ac.getBean("studentDao"); /** * xml应用 */ @Test public void test01_Insert(){ int insert = dao.insert(new Student("老哥" , 23)); System.out.println("insert : " + insert); } @Test public void test02_Delete() { int delete = dao.delete(19); System.out.println("delete : " + delete); } @Test public void test03_Update() { int update = dao.update(16,new Student("黑马",32)); System.out.println("update : " + update); } @Test public void test04_Query() { List<Student> student = dao.queryFindById(17); System.out.println("student : " + student); } @Test public void test04_Querys() { List<Student> students = dao.queryAll(); // List<Student> students = dao.queryFindByName("李四"); for (Student student : students) { System.out.println(student.toString()); } } @Test public void test05_group(){ int i = dao.tableSize(); System.out.println("i : " + i); System.out.println("=============="); Map<String, Object> stringObjectMap = dao.tableMaxMin(); System.out.println("stringObjectMap : " + stringObjectMap); } }