一、了解spring
Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中使用 Java 语言所需的一切,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种体系结构。从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8),并且已经为 JDK 9 提供了现成的支持。
Spring 框架分为多个模块。应用程序可以选择所需的模块。核心容器的模块是核心,包括配置模型和依赖项注入机制。除此之外,Spring 框架为不同的应用程序体系结构提供了基础支持,包括消息传递,事务性数据和持久性以及 Web。它还包括基于 Servlet 的 Spring MVC Web 框架,以及并行的 Spring WebFlux 反应式 Web 框架。
二、spring核心(CORE)
2.1IOC容器 依赖注入
2.1.1 Spring IoC 容器和 Bean 简介
org.springframework.beans
和org.springframework.context
软件包是 Spring Framework 的 IoC 容器的基础。 BeanFactory接口提供了一种高级配置机制,能够 Management 任何类型的对象。 ApplicationContext是BeanFactory
的子接口。它增加了:
- 与 Spring 的 AOP 功能轻松集成
- 消息资源处理(用于国际化)
- Event publication
- 特定于应用程序层的上下文,例如用于 Web 应用程序的
WebApplicationContext
。
简而言之,BeanFactory
提供了配置框架和基本功能,而ApplicationContext
添加了更多企业特定的功能。 ApplicationContext
是BeanFactory
的完整超集,在本章中仅使用 Spring 的 IoC 容器描述。
2.1.2容器简介
1.准备spring核心依赖
<!--spring core start-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring core end-->
<!--spring aop start-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spirng aop end-->
<!--spring aspects start-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring aspects end-->
<!--spring instrumentation start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring instrumentation end-->
<!--spring messaging start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring messaging end-->
<!--spring data access start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring data access end-->
<!--spring web start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc-portlet</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring web end -->
<!--spring test start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!--spring test end -->
2.创建maven工程 建立好相应的包、文件夹、xml文件
3.新建一个ApplicationSpring 类
springIOC 容器的设计主要是基于BeanFactory和Application两个接口,其中ApplicationContext是BeanFactory的子接口之一。Application是其高级接口之一,并且对BeanFactory功能做了许多有用的扩展,所以在绝大部分情况下,都会使用Application作为SpringIOC容器。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ApplicationSpring {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
}
}
4.表 1. bean 定义
Property | Explained in… |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
延迟初始化模式 | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
表 2.1. Spring Framework 工件
GroupId | ArtifactId | Description |
---|---|---|
org.springframework | spring-aop | 基于代理的 AOP 支持 |
org.springframework | spring-aspects | 基于 AspectJ 的方面 |
org.springframework | spring-beans | Bean 支持,包括 Groovy |
org.springframework | spring-context | 应用程序上下文运行时,包括调度和远程处理抽象 |
org.springframework | spring-context-support | 用于将通用第三方库集成到 Spring 应用程序上下文中的支持类 |
org.springframework | spring-core | 其他许多 Spring 模块使用的核心 Util |
org.springframework | spring-expression | Spring 表达语言(SpEL) |
org.springframework | spring-instrument | 用于 JVM 引导的工具代理 |
org.springframework | spring-instrument-tomcat | Tomcat 的检测代理 |
org.springframework | spring-jdbc | JDBC 支持包,包括数据源设置和 JDBC 访问支持 |
org.springframework | spring-jms | JMS 支持包,包括用于发送/接收 JMS 消息的帮助程序类 |
org.springframework | spring-messaging | 支持消息传递体系结构和协议 |
org.springframework | spring-orm | 对象/关系 Map,包括 JPA 和 Hibernate 支持 |
org.springframework | spring-oxm | Object/XML Mapping |
org.springframework | spring-test | 支持单元测试和集成测试 Spring 组件 |
org.springframework | spring-tx | 事务基础结构,包括 DAO 支持和 JCA 集成 |
org.springframework | spring-web | 基本的 Web 支持,包括 WebClient 端和基于 Web 的远程处理 |
org.springframework | spring-webmvc | Servlet 堆栈的基于 HTTP 的 Model-View-Controller 和 REST 端点 |
org.springframework | spring-webmvc-portlet | 在 Portlet 环境中使用的 MVC 实现 |
org.springframework | spring-websocket | WebSocket 和 SockJS 基础结构,包括 STOMP 消息传递支持 |
5.新建一个实体类Person
package com.spring.domain;
public class Person {
private String name;
private int age;
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
6.几种不同的实例化方式
用构造函数实例化
<bean id="myPerson" class="com.spring.domain.Person"></bean>
使用静态工厂方法实例化
public static Person getInstance(){
return new Person();
}
<bean id="myPersons" class="com.spring.domain.Person" factory-method="getInstance"></bean>
使用实例工厂方法实例化
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
2.1.3. 依赖注入
依赖注入(DI)是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(即,与它们一起工作的其他对象)。从工厂方法返回。然后,容器在创建 bean 时注入那些依赖项。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来自己控制其依赖关系的实例化或位置的 Bean 本身的逆过程(因此称为 Control Inversion)。
使用 DI 原理,代码更简洁,当为对象提供依赖项时,去耦会更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。结果,您的类变得更易于测试,尤其是当依赖项依赖于接口或抽象 Base Class 时,它们允许在单元测试中使用存根或模拟实现。
DI 存在两个主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖关系注入
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
<beans>
<bean id="thingOne" class="x.y.ThingOne">
<constructor-arg ref="thingTwo"/>
<constructor-arg ref="thingThree"/>
</bean>
<bean id="thingTwo" class="x.y.ThingTwo"/>
<bean id="thingThree" class="x.y.ThingThree"/>
</beans>
构造函数参数类型匹配
<bean id="myPerson" class="com.spring.domain.Person">
<constructor-arg name="name" value="高洋"></constructor-arg>
<constructor-arg name="age" value="11"/>
</bean>
构造函数参数索引
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
构造函数参数类型匹配
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
测试代码验证
import com.spring.domain.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ApplicationSpring {
private static Person person;
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
person = (Person) applicationContext.getBean("myPerson");
//person = applicationContext.getBean("myPerson",Person.class);
System.out.println(person);
}
}
输出结果:
十一月 11, 2020 8:25:56 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4bf558aa: startup date [Wed Nov 11 20:25:56 CST 2020]; root of context hierarchy
十一月 11, 2020 8:25:56 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [ApplicationContext.xml]
Person{name='高洋', age=11}
基于 Setter 的依赖项注入
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
注意:环形注入问题
A类通过构造函数注入需要B类的实例,B类需要A类的实例,因为循环引用会抛出BeanCurrentlyInCreationExceptionS
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。 Spring XML 配置文件的一小部分指定了一些 bean 定义,如下所示:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例显示了相应的ExampleBean
类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
引入数据库连接依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
配置数据源
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/zydemo"/>
<property name="username" value="root"/>
<property name="password" value="pzh200061"/>
</bean>
Collections
<list/>
,<set/>
,<map/>
和<props/>
元素分别设置 Java Collection
类型List
,Set
,Map
和Properties
的属性和参数。以下示例显示了如何使用它们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">[emailprotected]</prop>
<prop key="support">[emailprotected]</prop>
<prop key="development">[emailprotected]</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
懒初始化bean
默认情况下,作为初始化过程的一部分,ApplicationContext
实现会急于创建和配置所有singleton bean。通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。如果不希望使用此行为,则可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在首次请求时而不是在启动时创建一个 bean 实例。
在 XML 中,此行为由<bean/>
元素上的lazy-init
属性控制,如以下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
bean范围
创建 bean 定义时,将创建一个配方来创建该 bean 定义所定义的类的实际实例。 bean 定义是配方的想法很重要,因为它意味着与类一样,您可以从一个配方中创建许多对象实例。
您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean 定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在 Java 类级别上烘烤对象的范围。可以将 Bean 定义为部署在多个范围之一中。 Spring 框架支持六个范围,其中只有在使用网络感知ApplicationContext
时才可用。您也可以创建自定义范围。
下表描述了受支持的范围:
表 3. Bean 作用域
Scope | Description |
---|---|
singleton | (默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。 |
prototype | 将单个 bean 定义的作用域限定为任意数量的对象实例。 |
request | 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext 中有效。 |
session | 将单个 bean 定义的范围限定为 HTTP Session 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
application | 将单个 bean 定义的范围限定为ServletContext 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
websocket | 将单个 bean 定义的范围限定为WebSocket 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
新建数据库Properties文件
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zydemo
jdbc.username=root
jdbc.password=pzh200061
配置context名称空间
<context:property-placeholder location="classpath:datasource.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
2.1.4Spring IOC容器的初始化和依赖注入
1.Bean的初始化和依赖注入在Spring IOC容器中是两大步骤,在初始化之后才会进行依赖注入。
2.2基于注解的容器配置
基于xml的spring配置中包含以下标记来隐式注册他们 目前列出了所有的xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描包-->
<context:component-scan base-package="com.spring"/>
<context:property-placeholder location="classpath:datasource.properties"/>
<context:annotation-config/>
<bean id="myPerson" class="com.spring.domain.Person">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="age" value="11"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
使用@Component装配Bean
package com.spring.domain;
@Component(value = "role")
public class Role {
@Value("1")
private Long id;
@Value("role_name_1")
private String roleName;
@Value("role_note_1")
private String note;
public Role(){
}
public Role(Long id, String roleName, String note) {
this.id = id;
this.roleName = roleName;
this.note = note;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", roleName='" + roleName + '\'' +
", note='" + note + '\'' +
'}';
}
}
注解Component代表Spring IOC会把这个类扫描生成Bean示例,而其中的value属性代表这个类在Spring中的id,相当于XML配置中Bean的id,如果只写写成@Component,Spring IOC容器就默认为类名,但是以首字母小写的形式作为id,为其生成对象,配置到容器中。
使用@Autowried自动装配Bean
@Component("RoleService")
public class RoleServiceImpl implements RoleService {
@Autowired
private Role role;
@Override
public void printRoleInfo(Role role) {
System.out.println(role.getNote());
System.out.println(role.getId());
System.out.println(role.getRoleName());
}
}
测试:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
RoleService roleService = applicationContext.getBean(RoleService.class);
roleService.printRoleInfo();
注解@Qualifier
针对于上面的代码,如果当存在多个Bean对象需要装配时,为了消除歧义,可以使用@Qualifier来消除。
@Autowired
@Qualifier("RoleService")
private RoleService roleService;
2.2AOP
注意!了解AOP之前首先要了解java动态代理和CGLIB动态代理
为了能够更好的理解AOP请看如下代码
public interface RoleService {
public void printRoleInfo(Role role);
}
@Component("RoleService")//标注一个类为Spring容器的Bean
public class RoleServiceImpl implements RoleService {
private Role role;
@Override
public void printRoleInfo(Role role) {
System.out.println(role.getNote());
System.out.println(role.getId());
System.out.println(role.getRoleName());
}
}
public interface Interceptor {
public void before(Object obj);
public void after(Object obj);
public void afterReturning(Object obj);
public void afterThrowing(Object obj);
}
public class RoleInterceptor implements Interceptor {
@Override
public void before(Object obj) {
System.out.println("准备打印角色信息");
}
@Override
public void after(Object obj) {
System.out.println("已经完成角色信息的打印处理");
}
@Override
public void afterReturning(Object obj) {
System.out.println("刚刚完成打印功能,一切正常");
}
@Override
public void afterThrowing(Object obj) {
System.out.println("打印功能执行正常了,查看一下角色对象为空了吗?");
}
}
public class ProxyBeanFactory {
public static <T> T getBean(T obj, Interceptor interceptor){
return (T) ProxyBeanUtil.getBean(obj,interceptor);
}
}
public class ProxyBeanUtil implements InvocationHandler {
//被代理对象 roleService
private Object obj;
//拦截器
private Interceptor interceptor = null;
public static Object getBean(Object obj,Interceptor interceptor){
ProxyBeanUtil _this = new ProxyBeanUtil();
//保存被代理对象
_this.obj = obj;
//保存拦截器
_this.interceptor = interceptor;
//生成代理对象,并绑定代理方法
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),_this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object retObj = null;
//是否产生异常
boolean exceptionFlag = false;
//before方法
interceptor.before(obj);
try {
//反射原有方法
retObj = method.invoke(obj,args);
}catch (Exception e){
exceptionFlag = true;
}finally {
//after方法
interceptor.after(obj);
}
if(exceptionFlag){
//afterThrowing方法
interceptor.afterThrowing(obj);
}else{
//afterReturning方法
interceptor.afterReturning(obj);
}
return retObj;
}
}
测试:
public static void main(String[] args) {
RoleService roleService = new RoleServiceImpl();
Interceptor interceptor = new RoleInterceptor();
RoleService proxy = ProxyBeanFactory.getBean(roleService,interceptor);
Role role = new Role(1L,"role_name","role_note");
//相当于对printRoleInfo方法的一种增强
proxy.printRoleInfo(role);
System.out.println("-------测试afterthrow方法-------");
role = null;
proxy.printRoleInfo(role);
}
输出结果:
准备打印角色信息
role_note
1
role_name
已经完成角色信息的打印处理
刚刚完成打印功能,一切正常
-------测试afterthrow方法-------
准备打印角色信息
已经完成角色信息的打印处理
打印功能执行正常了,查看一下角色对象为空了吗?
从测试代码开始分析:
1.首先实例化了两个对象,分别为roleService、interceptor
2.获取到代理对象 proxy 对getBean方法进行分析:传入的参数是上面的两个对象,然后调用ProxyBeanUtil.getBean方法。
3.因为采用的是JDK动态代理,所以实现了InvocationHandler。
2.2.1AOP的术语
1.切面
切面就是在一个怎么样的环境下工作,可以理解为贯穿整个代码层面,就是一个切面,它能够在被代理对象的方法之前、之后,产生异常或者正常返回后切入你的代码,甚至可以代替原来被代理的方法,在动态代理中可以把它理解成一个拦截器,比如上面的RoleInterceptor就是一个切面类。
2.通知
通知是切面开启后,切面的方法。它根据在代理对象真实方法调用前、后的顺序和逻辑区分,它和约定游戏的例子里的拦截器的方法十分接近。
前置通知(before):在动态代理反射原有对象方法或者执行环绕通知前执行的通知功能。
后置通知(after):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。无论是否抛出异常,它都会被执行。
异常通知(afterThrowing):在动态代理中反射原有对象方法或者执行环绕通知产生异常后执行的通知功能。
环绕通知(aroundThrowing):在动态代理中,它可以取代当前被拦截对象的方法,通过参数或反射调用被拦截对象的方法。上面代码中的printRole方法就是一个切点。
3.引入
引入允许我们在现有的类里添加自定义的类和方法。
4.切点
在动态代理中,被切面拦截的方法就是一个切点,切面将可以将其切点和被拦截的方法按照一定的逻辑织入到约定流程当中。
4.连接点
连接点是一个判定条件,由它可以指定哪些是切点。对于指定的切点,Spring会生成代理对象去使用对应的切面对其拦截,否则就不会拦截它。
5.织入
织入是一个生成带颗粒对象的过程。
2.2.2Spring AOP的实现
这里只讲解基于注解的实现
通过对一下代码进行分析来了解Spring AOP:
2.2.2.1创建切面
通过使用@Aspect注解一个类,那么Spring IOC容器就会认为这是一个切面了。
@Aspect
public class RoleAspect {
@DeclareParents(value = "com.spring.service.impl.RoleServiceImpl+",defaultImpl = RoleVerifierImpl.class)
public RoleVerifier roleVerifier;
@Pointcut("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
public void print(){
}
//@Before("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
@Before("print()")
public void before(){
System.out.println("before");
}
//@After("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
@After("print()")
public void after(){
System.out.println("after");
}
@AfterReturning("print()")
public void afterReturning(){
System.out.println("afterReturning");
}
//@AfterThrowing("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
@AfterThrowing("print()")
public void afterThrowing(){
System.out.println("afterThrowing");
}
// @Around("print()")
// public void around(){
// System.out.println("around before");
// try {
// System.out.println("aa");
// }catch (Exception e){
// e.printStackTrace();
// }
// System.out.println("around after");
// }
}
这里的注解与上面的代码内容一致。
2.2.2.2连接点
execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(…))
针对这个表达式做出分析:
execution:代表执行方法的时候会触发
*:代表任意返回类型的方法
com.spring.service.impl.RoleServiceImpl:代表类的全限定名
printRoleInfo:被拦截方法名称
(…):任意的参数
注意:Spring只能支持上表所列出的AspectJ的指示器。如果使用了非表格中所列举的指示器,那么它将会抛出IllegalArgumentException异常
使用@Pointcut
使用这个注解后就不用再重复写正则表达式。
2.2.2.3测试
@Configuration
public class RoleConfig {
@Bean
public RoleAspect getRoleAspect(){
return new RoleAspect();
}
}
实现要编写一个配置类:@Configration:项目启动时会加载@Configration注解的类
将RoleAspect装配到IOC容器中
//AOP测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
RoleService roleService = applicationContext.getBean(RoleService.class);
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name");
role.setNote("role_note");
roleService.printRoleInfo(role);
System.out.println("##########异常测试#########");
role = null;
roleService.printRoleInfo(role);
2.2.2.4引入
public interface RoleVerifier {
public boolean verify(Role role);
}
public class RoleVerifierImpl implements RoleVerifier {
@Override
public boolean verify(Role role) {
return role!=null;
}
}
在RoleAspect类中添加下面这个属性,并通过@DeclareParents
@DeclareParents(value = "com.spring.service.impl.RoleServiceImpl+",defaultImpl = RoleVerifierImpl.class)
public RoleVerifier roleVerifier;
@DeclareParents分析:
value:com.spring.service.impl.RoleServiceImpl+:
表示 RoleServiceImpl中的方法进行增强
defaultImpl:表示实现增强方法接口的实现类
测试:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
RoleService roleService = applicationContext.getBean(RoleService.class);
RoleVerifier roleVerifier = (RoleVerifier) applicationContext.getBean(RoleService.class);
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name");
role.setNote("role_note");
if(roleVerifier.verify(role)){
roleService.printRoleInfo(role);
}
三、spring和数据库编程
3.1使用spring配置数据库
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="username" value="root"/>
<property name="password" value="pzh200061"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
</bean>
这种配置一般用于测试,因为它不是一个数据库连接池,在更多的时候,我们使用的时第三方的数据库连接。
3.2使用第三方数据库连接池
首先要导入依赖pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.7.0</version>
</dependency>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="pzh200061"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<!-- 最大连接数-->
<property name="maxTotal" value="5"/>
<!-- 最大等待连接中的数量-->
<property name="maxIdle" value="5"/>
<!-- 最大等待毫秒数-->
<property name="maxWaitMillis" value="10000"/>
</bean>
这里配置的时dpcp的数据库连接池。
3.3jdbcTemplate
配置jdbcTemplate
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
测试:
首先在数据库下面新建一个account表
新建实体类
package com.spring.domain;
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
public class JdbcTemplateDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate",JdbcTemplate.class);
int id = 1;
String sql = "select id,name,money from account where id ="+id;
Account account = jdbcTemplate.queryForObject(sql, new RowMapper<Account>() {
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account result = new Account();
result.setId(rs.getInt("id"));
result.setName(rs.getString("name"));
result.setMoney(rs.getDouble("money"));
return result;
}
});
System.out.println(account);
}
}
输出结果:
Account{id=1, name='小明', money=999.0}
分析代码:
这里使用了jdbcTemplate的queryForObject方法、它包含两个参数,一个是SQL,一个是RowMapper接口,这里使用了匿名内部类。同时,还可以通过使用jdk1.8中的lambda表达式。
在mapRow方法中,从ResultSet对象中取出查询到的数据,组装成一个Role对象,而无需再写任何关闭数据库的代码,因为jdbcTemplate内部实现了它们,这便是Spring的模板规则。
3.4jdbcTemplate的增、删、改、差
package com.spring.dao.daoImpl;
import com.spring.domain.Account;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcTemplateDemo {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
/**
* 插入账户
* 插入成功返回1
*
* @return
*/
public int insertAccount() {
String name = "小李";
double money = 10;
String sql = "insert into account(name,money) values(?,?)";
return jdbcTemplate.update(sql, name, money);
}
/**
* 根据id删除账户
* 删除成功返回1
*
* @param id
* @return
*/
public int deleteAccount(int id) {
String sql = "delete from account where id = ?";
return jdbcTemplate.update(sql, id);
}
/**
* 根据姓名查找账户
*
* @param name
* @return
*/
public List<Account> findAccount(String name) {
String sql = "select id,name,money from account where name like concat('%',?,'%')";
Object[] params = {name};
//使用RowMapper接口组织返回
List<Account> accounts = jdbcTemplate.query(sql, params, new RowMapper<Account>() {
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}
});
return accounts;
}
public static void main(String[] args) {
JdbcTemplateDemo jdbcTemplateDemo = new JdbcTemplateDemo();
//System.out.println(jdbcTemplateDemo.insertAccount());
//System.out.println(jdbcTemplateDemo.deleteAccount(8));
List<Account> accounts = jdbcTemplateDemo.findAccount("小");
for (Account data : accounts) {
System.out.println(data);
}
}
}
3.4jdbcTemplate源码分析
@Override
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//从数据源中获取一条连接
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
//释放连接
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
## 3.4jdbcTemplate源码分析
```java
@Override
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//从数据源中获取一条连接
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
//释放连接
DataSourceUtils.releaseConnection(con, getDataSource());
}
}