一.概述
1.1简介
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器
官方下载地址: JFrog
GitHub : Spring · GitHub
1.2优点
1.spring是一个开源的轻量级的应用开发框架,其目的是 用于简化企业级应用程序开发,降低侵入性;
2.Spring提供的IOC和AOP功能,可以将组建的耦合度降至最低,即解耦,便于系统日后的维护和升级
3.提供了对持久层、事务的支持
4.提供了MVC框架思想的实现,用于开发企业级应用
5.也可以与第三方框架和技术整合应用,可以自由选择采用哪种技术进行开发
1.3模块
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,组成Spring框架的每个模块(或组件)都可以单独存在,或者与其他一个挥着多个模块联合实现。每个模块的功能如下:
1. Spring Core:核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
2.Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
3.Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
4.Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
5.Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
6.Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
7.Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
二 Spring IOC
2.1 IOC 简介
1.IoC(Inverse Of Control)控制反转:即,把创建对象的权利交给框架,也就是指将对象的创建、对象的初始化、对象的存储、对象的管理交给了Spring容器。IOC本质是一种设计思想。
IoC 是一种通过描述来生成或者获取对象的技术,对于Java初学者 更多时候所熟悉的是使用 new 关键字来创建对象,而在spring中则不是,它是通过描述(XML或注解)来创建对象。
在 Spring 中把每一个需要管理的对象称之为 Spring Bean(简称为Bean),而Spring中管理这些 Bean 的容器,被我们称之为 Spring IoC 容器(简称 IoC容器)
在此之前,我们创建的对象,通常是利用new关键字调用构造器创建一个对象的
但是由于用new关键字创建对象,会提高类与类之间的依赖关系,现在我们将对象的创建交给框架来做。将需要的POJO提前在配置文件(XML或注解)中进行描述,Spring会根据配置文件将其中所描述生成 IoC容器并装配Bean,在需要使用时,再次获取即可
2.2入门案例
在Spring中允许我们通过XML或注解方式装配Bean,下面先来介绍Spring中通过XML方法如何装配Bean。
1.第一步:先创建project项目,然后在创建module
在mudule的pom.xml文件中配置文件,在<dependencies></dependencies>里面配置子标签
配置完之后刷新,刷新后会自动将相关jar包加载到模块中,这个jar 文件包含Spring 框架基本的核心工具类。Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类。
<!-- 添加commons-logging依赖 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- 添加junit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<!-- 添加spring的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
2.第二步:创建实体类(这里用User举例)
package com.sldl.pojo;
public class User {
private String username;
public User() {
}
public User(String username) {
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
'}';
}
}
3.第三步:编写核心配置文件
在src/main/resources目录下 ,创建Spring的核心配置文件, 这里我们命名为spring-ioc.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">
</beans>
4.第四步:配置Bean
在 spring-ioc.xml 文件中添加如下配置:将User类作为Bean装配到Spring IoC容器中
<?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">
<!--在spring的配置文件里,注册User类的bean配置,
此时User类的对象就是Spring容器帮助我们创建对象,默认调用无参构造器-->
<bean id="u1" class="com.sldl.pojo.User"></bean>
</beans>
5.第五步:测试
@Test
public void testIOC(){
//加载spring核心配置文件,返回的就是一个spring的ioc容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-ioc.xml");
//从容器中获取想要的bean对象 : 程序员是通过bean的配置,然后容器在启动解析文件时,他主动构建的对象
//注意:ioc容器创建对象使用的是无参构造器
Object u1 = ac.getBean("u1");
System.out.println(u1);
}
此时的结果的是一个User类的实现对象,但是该对象的属性值都为默认值,但是一般我们在构建对象时,都会给这个对象的属性赋值,这里我们就可以用到依赖注入(DI)给对象属性赋值
6.Spring小结
1)在程序运行后,Spring会到配置文件中读取 Bean 配置信息,根据配置信息生产 Spring IoC容器对象
2)Spring IoC 容器根据读取到的Bean配置信息,到硬盘上加载相应的 Class 字节码文件,根据反射创建 Bean 实例。
3)Spring会将创建的Bean存储到 Spring 容器中,也就是所谓的 Bean Pool(Bean池,底层是一个Map)中
4) 根据 Spring 提供的 getBean 方法获取Bean实例并应用到我们的应用程序中。
2.3依赖注入(DI)
2.3.1.set方法注入
1.常量注入
在spring-ioc.xml文件中的bean子标签的类对应的id中bean标签里面写子标签property
<!-- 注册java类的bean对象,id是唯一标识符,class用来指定类全限定名-->
<bean id="u1" class="com.springioc.pojo.User" scope="prototype">
<property name="name" value = "十个勤天"></property>
</bean>
property标签用于给对象的属性赋值,在该类中需要提供set方法
2.bean注入
先准备数据
创建一个student类和address类
address类:
package com.springioc.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
public class Address {
private String province;
private String city;
public void setProvince(String province) {
this.province = province;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
}
student类:
package com.springioc.pojo;
import java.util.*;
/**
* 研究DI:依赖注入,spring 容器中有很多个bean组件买这些组件之间可能存在着依赖关系
* (bean对象的属性值可能给存在另一个bean对象 Spring 容器是如何将他们管来呢到一起的)
*
* 大白话一点:就是管理bean组件的属性赋值操作
*
* 方式有两种:
* setter方法的注入
* 构造器的注入
*
* 当前案例演示:各种数据类型的属性注入
*/
public class Student {
private int id;
private String name;
private Address addr;
private String[] books;
private List<String> hobbys;
private Set<String> games;
private Map<String,String> cards;
private Properties info;
private String mgr;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setCards(Map<String, String> cards) {
this.cards = cards;
}
public void setInfo(Properties info) {
this.info = info;
}
public void setMgr(String mgr) {
this.mgr = mgr;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id + "\n"+
", name='" + name + '\'' +"\n"+
", addr=" + addr +"\n"+
", books=" + Arrays.toString(books) +"\n"+
", hobbys=" + hobbys +"\n"+
", games=" + games +"\n"+
", cards=" + cards +"\n"+
", info=" + info +"\n"+
", mgr=" + mgr +
'}';
}
}
这两个类准备好之后,我们就可以在spring-ioc.xml中对对象的属性赋值
针对于address类的属性的赋值可以直接使用常量注入,继续在spring-ioc.xml文件中的beans的子标签中写bean标签,如下:
<bean id="adds" class="com.springioc.pojo.Address">
<property name="province" value="云南省"></property>
<property name="city" value="昆明"></property>
</bean>
针对与studnt类的属性的赋值下面我们分别来进行赋值,也是在spring-ioc.xml文件中beans中写子标签bean,student类中的address是一个对象,这个对象有自己的bean标签,如果给这个属性赋值我们需要bean注入。
这时候就不用value,用ref 。ref后面赋值需要写address的bean的id属性的名字(id是唯一的在同一个xml文件中)
<bean id="stu1" class="com.springioc.pojo.Student">
<property name="id" value="1001"></property>
<property name="name" value="王一珩"></property>
<property name="addr" ref="adds"></property>
<!-- 1引用容器的其他bean。使用ref属性,书写id-->
<!-- 数组属性赋值-->
3.数组注入
给student的属性属于数组赋值,继续在<bean id="stu1" class="com.springioc.pojo.Student"></bean>里面写代码,property 的name写在类里面定义的属性名
<property name="books" >
<array>
<value>平凡的世界</value>
<value>爱丽丝历险记</value>
<value>假如给我三天光明</value>
</array>
</property>
4.list注入
给student里是list类型的属性赋值,继续在<bean id="stu1" class="com.springioc.pojo.Student"></bean>里面写代码 ,property 的name写在类里面定义的属性名
<property name="hobbys">
<list>
<value>滑冰</value>
<value>跳舞</value>
<value>游泳</value>
</list>
</property>
5.set注入
给student里是set类型的属性赋值,继续在<bean id="stu1" class="com.springioc.pojo.Student"></bean>里面写代码 ,property 的name写在类里面定义的属性名
<property name="games">
<set>
<value>第五人格</value>
<value>闪耀暖暖</value>
<value>蛋仔派对</value>
</set>
</property>
6.map注入
给student里是Map类型的属性赋值,继续在<bean id="stu1" class="com.springioc.pojo.Student"></bean>里面写代码 ,property 的name写在类里面定义的属性名
<property name="cards">
<map>
<entry key="1111" value="中国银行"></entry>
<entry key="2222" value="工商银行"></entry>
<entry key="3333" value="建设银行"></entry>
</map>
</property>
7.Properties注入
给里是Properties类型的属性赋值,继续在<bean id="stu1" class="com.springioc.pojo.Student"></bean>里面写代码 ,property 的name写在类里面定义的属性名
<property name="info" >
<props>
<prop key="url">jdbc:mysql://localhost:3306</prop>
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
8.null注入
<!-- 属性赋值为null-->
<property name="mgr">
<null></null>
</property>
9.p命名空间
P命名空间 不是新的东西,它是对 IoC 和 DI 的简化。使用 p 命名空间 可以更加方便地完成 bean 的配置以及 bean 之间的依赖注入。
使用p命名空间,要确保 xml 头文件中引入了 p 命名空间。
继续在spring-ioc.xml 文件中的<beans>中添加
xmlns:p="http://www.springframework.org/schema/p"
然后修改bean
<bean id="a1" class="com.shuilidianli.pojo.Address">
<property name="province" value="吉林"/>
<property name="city" value="长春"/>
</bean>
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="e1" class="com.shuilidianli.pojo.Employee" p:id="1002" p:name="小红" p:addr-ref="a1">
<property name="books">
<array>
<value>完美世界</value>
<value>无限恐怖</value>
<value>警世通言</value>
</array>
</property>
//.......
</bean>
2.3.2构造方法注入
就是使用类中的构造函数,为成员变量赋值——即构造方法注入。所以使用构造器注入方法的类必须提供构造器
需要注意的是:为成员变量赋值是通过配置的方式,让spring框架在创建bean对象的同时,为成员变量赋值。而不是我们自己去实现。
1.构造方法的第一种写法
<!-- 构造方法的注入-->
<bean id="t1" class="com.springioc.pojo.Teacher">
<constructor-arg name="addr" ref="adds" ></constructor-arg>
<constructor-arg name="age" value="26" ></constructor-arg>
<constructor-arg name="id" value="1009" ></constructor-arg>
<constructor-arg name="name" value="赵一博" ></constructor-arg>
</bean>
2.构造方法的第二种写法(使用索引index)
<!-- 构造器注入的第二种写法:
使用参数的索引index:指的是参数构造器中参数从左到右的顺序,从0开始-->
<bean id="t2" class="com.springioc.pojo.Teacher" c:id="1010" c:name="赵小童">
<constructor-arg index="3" ref="adds" ></constructor-arg>
<constructor-arg index="2" value="26" ></constructor-arg>
<constructor-arg index="0" value="1009" ></constructor-arg>
<constructor-arg index="1" value="赵一博" ></constructor-arg>
</bean>
3.还可以通过c命名空间注入
在.xml头文件中引入以下代码
在<beans .........>(这个里面添加)
xmlns:c="http://www.springframework.org/schema/c"
然后再配置bean(例如给狗这个类)
<bean id="dog" class="com.springioc.pojo.Dog" c:name="旺财"></bean>
2.4自动装配
-
自动装配是使用spring满足bean依赖的一种方法
-
spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
-
组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
-
自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
2.4.1 byName
数据准备
新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat {
public void call() {
System.out.println("miao~miao~miao");
}
}
public class Dog {
public void call() {
System.out.println("wang~wang~wang");
}
}
在创建一个User类
public class User {
private String name;
private Dog dog;
private Cat cat;
public void setName(String name) {
this.name = name;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", dog=" + dog +
", cat=" + cat +
'}';
}
}
数据准备好之后,再来配置.xml文件
1.autowire byName (按名称自动装配)。由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。
2.bean.xml配置(增加一个属性 autowire=“byName” )
<?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 id="dog" class="com.sldl.pojo.Dog"></bean>
<bean id="cat" class="com.sldl.pojo.Cat"></bean>
<!-- User的属性叫cat和dog ByName自动装备是通过相同的名字进行匹配-->
<bean id="user" class="com.sldl.pojo.User" autowire="byName">
<property name="name" value="小明"/>
<!--<property name="dog" ref="dog"/>
<property name="cat" ref="cat"/>-->
</bean>
</beans>
12.4.2 byType
autowire=”byType” (按类型自动装配)。使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
下面我们将user的bean配置修改一下 : autowire=“byType”
<bean id="dog" class="com.sldl.pojo.Dog"></bean>
<bean id="cat" class="com.sldl.pojo.Cat"></bean>
<bean id="user" class="com.sldl.pojo.User" autowire="byType">
<property name="str" value="小明"/>
</bean>
总结:
-
byName的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性set方法的值一致!
-
byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
2.5使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1.在spring配置文件中引入context文件头
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
2.开启属性注解支持!
<context:annotation-config/>
2.5.1 @Autowired
@Autowired是按类型自动转配的,不支持id匹配。 确认一下,是否引入 spring-aop的包!
1、将User类中的set方法去掉,使用@Autowired注解
public class User {
private String name;
@Autowired
private Dog dog;
@Autowired
private Cat cat;
public void setName(String name) {
this.name = name;
}
/*
public void setDog(Dog dog) {
this.dog = dog;
}
public void setCat(Cat cat) {
this.cat = cat;
}*/
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", dog=" + dog +
", cat=" + cat +
'}';
}
}
2、此时配置文件内容
<context:annotation-config/>
<bean id="dog" class="com.sldl.pojo.Dog"></bean>
<bean id="cat" class="com.sldl.pojo.Cat"></bean>
<bean id="user" class="com.sldl.pojo.User">
<property name="name" value="小明"/>
</bean>
小结:
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
如果允许对象为null,设置required = false,默认为true.
2.5.2 @Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用。
1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="com.sldl.pojo.Dog"></bean>
<bean id="dog2" class="com.sldl.pojo.Dog"></bean>
<bean id="cat1" class="com.sldl.pojo.Cat"></bean>
<bean id="cat2" class="com.sldl.pojo.Cat"></bean>
2、没有加Qualifier测试,直接报错。因为类型不唯一。
3、在属性上添加Qualifier注解
public class User {
private String name;
@Autowired
@Qualifier("dog2")
private Dog dog;
@Autowired
@Qualifier("cat2")
private Cat cat;
//.......
}
2.5.3 @Resource
@Resource如有指定的name属性,先按该属性进行byName方式查找装配;
-
其次再进行默认的byName方式进行装配;
-
如果以上都不成功,则按byType的方式自动装配。
-
都不成功,则报异常。
1.测试
在实体类User中
public class User {
private String name;
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
//.....
}
在xml中
<bean id="dog" class="com.sldl.pojo.Dog"/>
<bean id="cat1" class="com.sldl.pojo.Cat"/>
<bean id="cat2" class="com.sldl.pojo.Cat"/>
<bean id="user" class="com.sldl.pojo.User">
<property name="name" value="小明"/>
<bean>
2.5.4 @Component
1)说明
在spring4之后,想要使用注解形式,必须得要引入aop的包,检查自己是否引入了aop依赖包
在配置文件当中,还得要引入一个context约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
2)Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
步骤1 配置扫描哪些包下的注解
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "小明";
}
步骤2 在指定包下编写类,增加注解
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "小明";
}
步骤3 测试
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
3)属性注入
使用注解注入属性
1、可以不用提供set方法,直接在属性名上添加@value(“值”)
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("小明")
// 相当于配置文件中 <property name="name" value="小明"/>
public String name;
}
2、如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")
public class User {
public String name;
@Value("小明")
public void setName(String name) {
this.name = name;
}
}
4) 衍生注解
这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller <====web层
- @Service <====service层
- @Repository <==== dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
2.5.5 作用域@scope
singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("小明")
public String name;
}
2.6 小结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :推荐最佳实践
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
2.7 基于Java类进行配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
2.7.1 测试1
1、编写一个实体类,Phone
package com.sldl.pojo;
public class Phone {
private String brand;
private String model;
private double price;
public Phone(String brand, String model, double price) {
this.brand = brand;
this.model = model;
this.price = price;
}
@Override
public String toString() {
return "Phone{" +
"brand='" + brand + '\'' +
", model='" + model + '\'' +
", price=" + price +
'}';
}
}
2、新建一个config配置包,编写一个MyConfig配置类
package com.sldl.config;
import com.sldl.pojo.Phone;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件
@Configuration
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Phone phone(){
return new Phone("MI","14Pro",6800);
}
}
3、测试
@Test
public void test2(){
ApplicationContext app =
new AnnotationConfigApplicationContext(MyConfig.class);
Phone phone = app.getBean("phone", Phone.class);
System.out.println(phone);
}
4、成功输出结果!
2.7.2 测试2
如何导入其他配置呢?
1、再弄一个pojo类
package com.sldl.pojo;
public class Computer {
private String brand;
private String model;
private double price;
public Computer(String brand, String model, double price) {
this.brand = brand;
this.model = model;
this.price = price;
}
@Override
public String toString() {
return "Computer{" +
"brand='" + brand + '\'' +
", model='" + model + '\'' +
", price=" + price +
'}';
}
}
2、我们再编写一个配置类!
package com.sldl.config;
import com.sldl.pojo.Computer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件
@Configuration
public class MyConfig2 {
@Bean
public Computer computer(){
return new Computer("thinkPad","X1",10000);
}
}
3、引入新的配置类
在之前的配置类中我们来选择导入这个配置类
package com.sldl.config;
import com.sldl.pojo.Phone;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//该注解的作用是将所在类,注册成一个类似于beans.xml的配置文件
@Configuration
@Import(MyConfig2.class)
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Phone phone(){
return new Phone("MI","14Pro",6800);
}
}
4、测试
@Test
public void test2(){
ApplicationContext app =
new AnnotationConfigApplicationContext(MyConfig.class);
Phone phone = app.getBean("phone", Phone.class);
System.out.println(phone);
Computer com = app.getBean("computer", Computer.class);
System.out.println(com);
}
测试结果:
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
三 SpringAOP
3.1简介
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
3.2 Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能
3.3 使用Spring实现Aop
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
3.3.1 案例1
UserService.java
package com.shuilidianli.service;
public interface UserService {
void add();
void delete();
void update();
void search();
}
UserServiceImpl.java
package com.shuilidianli.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("---增加用户---");
}
@Override
public void delete() {
System.out.println("---删除用户---");
}
@Override
public void update() {
System.out.println("---修改用户---");
}
@Override
public void search() {
System.out.println("---查询用户---");
}
}
编写两个通知类型,一个前置通知,一个后置通知
package com.shuilidianli.aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
/**
*
* @param method 目标对象的方法
* @param objects 被调用的方法的参数
* @param o 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"正在执行····");
}
}
package com.shuilidianli.aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
/**
*
* @param o 方法的返回值
* @param method 目标对象的方法
* @param objects 目标对象的方法的参数
* @param o1 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了"+o1.getClass().getName()+"的"+method.getName()+"方法," +
"返回值:"+o);
}
}
注册bean,实现aop切入 , 注意导入约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册-->
<bean id="userService" class="com.shuilidianli.service.UserServiceImpl"></bean>
<bean id="log" class="com.shuilidianli.aop.Log"></bean>
<bean id="afterLog" class="com.shuilidianli.aop.AfterLog"></bean>
<!--aop配置-->
<aop:config>
<!--切入点 expression: 表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.shuilidianli.service.UserServiceImpl.*(..))"/>
<!--执行环绕:advice-ref执行方法,pointcut-ref:切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
package com.shuilidianli.test;
import com.shuilidianli.service.UserService;
import com.shuilidianli.web.EmpController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest2 {
@Test
public void test1(){
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
UserService us =
(UserService) ctx.getBean("userService");
us.search();
}
}
测试结果
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序员专注领域业务 , 其本质还是动态代理
3.3.2 案例2
自定义类来实现Aop
目标业务类不变依旧是userServiceImpl
1)自己定义一个切入类
package com.shuilidianli.aop;
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
2)注册配置
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.shuilidianli.aop.DiyPointcut"></bean>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPointcut" expression="execution(* com.shuilidianli.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="diyPointcut"/>
<aop:after method="after" pointcut-ref="diyPointcut"/>
</aop:aspect>
</aop:config>
3)测试
package com.shuilidianli.test;
import com.shuilidianli.service.UserService;
import com.shuilidianli.web.EmpController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest2 {
@Test
public void test1(){
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
UserService us =
(UserService) ctx.getBean("userService");
us.search();
}
}
测试结果:
3.3.3 案例3
AnnotationPointcut.java
package com.shuilidianli.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.shuilidianli.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.shuilidianli.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.shuilidianli.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("---环绕前---");
System.out.println("调用的方法:"+joinPoint.getSignature());
//执行目标方法
Object proceed = joinPoint.proceed();
System.out.println("---环绕后---");
System.out.println(proceed);
}
}
开启注解扫描
<context:component-scan base-package="com"/>
<!-- 开启aop组件注解扫描 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
测试: 注意给UserServiceImpl添加注解
package com.shuilidianli.test;
import com.shuilidianli.service.UserService;
import com.shuilidianli.web.EmpController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest2 {
@Test
public void test1(){
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
UserService us =
(UserService) ctx.getBean("userServiceImpl");
us.search();
}
}
测试结果:
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
3.3.4 案例4
1)EmpController.java
package com.sldl.controller;
import org.springframework.stereotype.Controller;
@Controller //添加Bean注解
public class EmpController {
public void findAll(){
System.out.println("---正在查询所有员工信息---");
}
public void addEmp(){
System.out.println("---正在添加一个员工信息---");
String str = null;
System.out.println(str.length());
}
}
2)Operation.java
package com.sldl.log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component // 添加Bean注解
@Aspect // 添加Aop注解
public class OperationLog {
//后置通知注解
@After("within(com.sldl.controller..*)")
public void log(){
System.out.println("记录日志");
}
//环绕通知注解
@Around("within(com.sldl.controller..*)")
public Object log1(ProceedingJoinPoint p) throws Throwable{
//获取目标组件的名字
String className = p.getTarget().getClass().getName();
//获取目标组件里执行的方法名
String methodName = p.getSignature().getName();
System.out.println("------------");
//执行目标组件
Object obj = p.proceed();
System.out.println(
"xxx正在执行"+className
+"里的"+methodName+"方法");
return obj;
}
//异常抛出通知
@AfterThrowing(pointcut="within(com.sldl.controller..*)",throwing="e")
public void log2(Exception e){
System.out.println(e.toString());
StackTraceElement[] eles =
e.getStackTrace();
System.out.println(eles[0]);
System.out.println(eles[1]);
}
}
3)beans.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"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解扫描功能-->
<context:component-scan base-package="com.sldl"/>
<!--开启AOP注解扫描-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
4)AOPTest
package com.sldl.test;
import com.sldl.controller.EmpController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
@Test
public void test1(){
ApplicationContext ctx =
new ClassPathXmlApplicationContext("beans.xml");
EmpController ec =
ctx.getBean("empController", EmpController.class);
//ec.findAll();
ec.addEmp();
}
}
测试结果: