1. Spring5框架概述
1、Spring是轻量级的开源的JavaEE框架。
2、Spring可以解决企业应用开发的复杂性。
3、Spring有两个核心部分:IOC和AOP
- IOC:控制反转,将创建对象的过程交由Spring进行管理。
- AOP:面向切面,在不修改源代码的基础上进行功能增强。
4、Spring的特点:
- 方便解耦,简化开发:
- 通过Spring提供的IOC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户则不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,而是可以更加专注于应用层的开发。
- 支持AOP:
- 通过Spring提供的AOP功能,能够更方便进行面向切面的编程,许多不容易用传统OOP方式实现的功能,都可以通过AOP轻松应付。
- 方便程序的测试:
- 可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4的支持,可以通过使用@Test注解方便测试Spring程序。
- 方便集成各种优秀的框架:
- Spring不排斥各种优秀的开源框架,相反,Spring还可以降低各种框架的使用难度,Spring提供了对各种优秀框架的直接支持。比如:Struts、Hibernate、Hessian、Quartz等。虽然现在已经不用这些了。😊
- 降低JavaE API的使用难度:
- Spring对很多难用的JavaEE API都提供了一层薄薄的封装,比如对:JDBC、JavaMail、远程调用等。通过Spring的简易封装,使得这些JavaEE API的使用难度大为降低。
Java源码是经典的学习范例:
Spring的源码设计精妙、结构清晰、匠心独运,处处体现着大师对Java设计模式的灵活运用,以及对Java技术的高深造诣。Spring框架的源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用层开发水平,学习和研究Spring源码将会使你收获到意想不到的效果。净扯淡,我能吃透源码,我还干应用层开发?😊
2. Spring5入门案例
2.1 使用IDEA创建一个Maven项目
2.2 引入Spring5相关依赖
注意:会用Maven就没必要再跑到官网下载Spring5框架,有工具不用,你脑残吗?
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--引入spring5的依赖,需要分别引入spring中的jar包,spring包含5个核心部分jar包-->
<!--1.spring-context部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--2.spring-core部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--3.spring-expression部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--4.spring-aop部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--5.spring-beans部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
</dependencies>
2.3 创建配置文件
创建bean.xml配置文件
2.4 创建实体类
创建一个普通Java类
public class User
{
public void show(){
System.out.println("show~");
}
@Override
public String toString()
{
return "I'm"+getClass().getName();
}
}
2.5 配置User对象的创建
在bean.xml配置文件中,创建User对象
<?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">
<!--配置User对象的创建-->
<bean id="user" class="com.lonelysunset.pojo.User"></bean>
</beans>
2.6 使用Junit4测试结果
public class AppTest
{
@Test
public void test1(){
// 1.加载Spring配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
// 2.获取配置文件创建的对象
User user = context.getBean("user", User.class);
// 3.输出对象
System.out.println(user);// I'mcom.lonelysunset.pojo.User
// 4.调用对象中的方法
user.show();// show~
}
}
3. IOC
3.1 什么是IOC?
- IOC:即:控制反转,将对象创建和对象之间的调用过程,交由Spring进行管理。
- 使用IOC的目的是为了解耦,即:降低程序之间的耦合度,简单来说,就是降低A类和B类之间的耦合度。
- 刚刚介绍的入门案例,就是利用了IOC容器技术实现。
3.2 IOC底层原理技术
IOC底层主要使用了:xml解析、工厂模式、反射进行实现
3.3 引出IOC
假设程序中有UserService、UserDao两个类,需求是在UserService类中调用UserDao类中的方法。
实现如下:普通类与类之间的调用方式实现
public class UserDao{
public void show(){
System.out.println(123);
}
}
public class UserService{
public static void main(String[] args){
UserDao userDao = new UserDao();
userDao.show();// 123
}
}
分析:按照以上方式实现,存在高耦合度的问题,当UserDao的类路径发生变化时,所有与UserDao产生关联(耦合)的类,都需要去更改,从而导致高耦合,很明显,这不是程序所追求的极致状态。
对以上的假设进行优化。
实现如下:通过使用工厂模式实现。
public class UserDao{
public void show(){
System.out.println(123);
}
}
public class UserFactor{
public static UserDao getUserDao(){
return new UserDao;
}
}
public class UserService{
public static void main(String[] args){
UserDao userDao = UserFactor.getUserDao();
userDao.show();// 123
}
}
分析:通过使用工厂模式优化后,耦合度有一定的降低,但依然没有达到程序所追求的极致状态。补充说明:任何程序中的类与类之间的关系都不可能做到完全的无耦合,但可以通过程序去实现较低的耦合
对以上的假设再次进行优化。
实现如下:通过使用xml解析、工厂模式、反射实现
1、创建bean.xml文件,用于配置User对象的创建。
<?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">
<!--配置User对象的创建-->
<bean id="dao" class="com.lonelysunset.UserDao"></bean>
</beans>
2、创建工厂方法。
class UserFactor{
public static UserDao getDao(){
String classValue = class属性值;// 1.xml解析 //TODO 这里有一点不理解哈,如何解析xml并获取到指定类对象的全类名路径?听说是使用dom4j技术?等以后有机会了解再说吧。
Class clazz = Class.forName(classValue);// 2.通过反射创建对象
return (UserDao)clazz.newInstance();
}
}
上述代码流程
分析:通过使用xml解析、工厂模式、反射进行优化后,耦合度能够更进一步降低,并且依然达到了Spring底层IOC的低耦合程度。因为IOC底层使用的技术就是xml解析、工厂模式、反射。😊挺搞笑的,什么叫耦合度低?就是别人大佬写的,大佬说的,Spring说使用xml解析、工厂模式、反射就是最低的,那就是最低的。呵呵
3.4 分析IOC接口
- ioc思想基于IOC容器完成,ioc容器底层就是对象工厂。
- Spring提供了IOC容器实现的两种方式:两个接口,
- BeanFactory:IOC容器基本实现,是spring内部使用接口,不提供开发人员使用。加载配置文件时不会创建对象,使用对象时才会创建对象(懒汉式加载对象)。
- ApplicationContext:BeanFatory的子接口,提供更多更强大的功能,一般供开发人员进行使用。加载配置文件时就创建对象(饥汉式加载对象)。
// 当使用BeanFactory加载配置文件时,不会去创建对象,而是当我们在获取对象时,才会去创建对象 BeanFactory context = new ClassPathXmlApplicationContext("bean1.xml");
// 当使用ApplicationContext加载配置文件时,会将所有的配置文件中的对象进行创建 ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
- ApplicationContext接口实现类
- FileSystemXmlApplicationContext(“盘符路径(绝对路径)”)
- ClassPathXmlApplicationContext(“src目录下类路径”)
ctrl+H:打开类的结构
//TODO 实际测试中,这里会报错,不知道啥原因,具体没有往下探究,可能是路径写错了?应该不会啊😓。
ApplicationContext context = new FileSystemXmlApplicationContext("/Users/wufuqiang/IdeaProjects/spring5/src/main/resources/bean1.xml");
// 这种方式,应该是测试的时候使用最多的,瞧瞧多方便啊,直接获取xml配置文件名称就行
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
3.5 分析IOC中的Bean管理
3.5.1 什么是Bean管理?
Bean管理,一般指的是两个操作,即:Spring创建对象和Spring注入属性。
3.5.2 Bean管理操作由两种方式
- 基于xml配置文件方式实现。
- 基于注解方式实现。(推荐方式,也是后期开发企业项目的最佳方式)
3.5.3 IOC操作Bean管理-基于xml方式
3.5.3.1 基于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">
<!--配置User对象的创建-->
<bean id="user" class="com.lonelysunset.pojo.User"></bean>
</beans>
分析<bean id="" class=""></bean>标签:
- 在Spring配置文件中,使用bean标签,并在标签中添加对应的属性,从而实现对象的创建。
- 在bean标签中,有很多属性,以下着重介绍常用的属性。
- id属性:即表示唯一标识。
- class属性:即表示类的全路径,比如com.solitarysunset.pojo.User
- 创建对象的时候,默认是执行无参构造完成对象创建的。
- 如果我们在User中创建有参构造,则系统将不再为User分配无参构造,此时再通过xml创建对象,则会报错。
3.5.3.2 基于xml方式注入属性
DI:即IOC的一种具体实现,称之为依赖注入,主要用于注入属性,其注入属性的前提必须在创建对象的基础上完成。
3.5.3.2.1 第一种注入方式:使用set方法进行注入
1、创建类,定义属性和对应的set方法。
public class Book{
private String name;
private String author;
public void setAuthor(String author)
{
this.author = author;
}
public void setName(String name)
{
this.name = name;
}
}
2、在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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="book" class="com.lonelysunset.pojo.Book">
<!--使用property完成属性注入
name:类里面的属性名称
value:向属性注入值
-->
<property name="author" value="老子"/>
<property name="name" value="道德经"/>
</bean>
</beans>
3.5.3.2.2 第二种注入方式:使用构造器进行注入
创建类,定义属性和有参构造。
public class Cat
{
private String name;
private Integer age;
public Cat(String name, Integer age)
{
this.name = name;
this.age = age;
}
}
在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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.lonelysunset.pojo.Cat">
<constructor-arg name="name" value="小红"/>
<constructor-arg name="age" value="18"/>
</bean>
</beans>
3.5.3.2.3 使用p名称空间注入属性(了解即可)
使用p名称空间注入,可以简化基于xml配置方式。
第一步,添加p名称空间在配置文件中。
<?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">
</beans>
第二步,在bean标签中注入属性。
<?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="book" class="com.lonelysunset.pojo.Book" p:name="金瓶梅" p:author="未知"></bean>
</beans>
3.5.4 IOC操作Bean管理-在xml中注入其它类型属性
设置null值。
设置包含特殊符号的属性值。
<?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="book" class="com.lonelysunset.pojo.Book">
<property name="name">
<!--设置name为null值-->
<null/>
</property>
<property name="author">
<!--属性值中设置包含特殊符号的内容,只需要将内容写在<![CDATA[内容]]>即可-->
<value><![CDATA[~无名氏~]]></value>
</property>
</bean>
</beans>
3.5.4.1 在xml中注入属性-外部bean
假设已有UserDao、UserService两个接口,以及UserDaoImpl、UserServiceImpl实现类,要求在UserServiceImpl中注入UserDao,调用UserServiceImpl中的add方法时,同时能够调用UserDao中的sub方法。
创建UserDao、UserDaoImpl。
public interface UserDao
{
void sub();
}
public class UserDaoImpl implements UserDao
{
@Override
public void sub()
{
System.out.println("sub.......");
}
}
创建UserService、UserServiceImpl,并在UserServiceImpl声明UserDao类型的属性。
public interface UserService
{
void add();
}
public class UserServiceImpl implements UserService
{
private UserDao userDao;
// 这里涉及到了多态,形参类型是UserDao,但是实参应该是UserDao接口的实现类,所以在xml注入的时候,实际注入的应该是UserDaoImpl实现类,而不是把接口注入进去
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
@Override
public void add()
{
System.out.println("add......");
// 这里就是动态绑定咯,编译类型是UserDao,运行类型是UserDaoImpl,所以调用的是实现类中的方法
userDao.sub();
}
}
使用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">
<!--创建UserServiceImpl和UserDaoImpl对象-->
<bean id="userServiceImpl" class="com.lonelysunset.pojo.UserServiceImpl">
<!--
注入userDao对象
name属性:类里面属性名称。
ref属性:创建userDao对象bean标签的id值。
-->
<property name="userDao" ref="userDaoImpl"/>
</bean>
<bean id="userDaoImpl" class="com.lonelysunset.pojo.UserDaoImpl"></bean>
</beans>
测试结果。
@Test
public void test4(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
// 接收类型使用其父类类型接收,还是多态,没什么可说的
UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
userServiceImpl.add();
}
// 结果:
add......
sub.......
3.5.4.2 在xml中注入属性-内部bean
不建议使用,首先呢,不常用,因为这种方式反人类,耦合性太高,了解下即可适用于一对多关系,比如部门和员工之间的关系,一个部门有多个员工,一个员工只能属于一个部门。
创建Dept和Emp实体类。
public class Dept
{
private String dname;
public void setDname(String dname)
{
this.dname = dname;
}
@Override
public String toString()
{
return "Dept{" + "dname='" + dname + '\'' + '}';
}
}
public class Emp
{
private String ename;
private String gender;
// 我觉得这种注入方式就已经很反人类了,所以在xml中使用内部bean,一样反人类。
private Dept dept;
public void setEname(String ename)
{
this.ename = ename;
}
public void setGender(String gender)
{
this.gender = gender;
}
public void setDept(Dept dept)
{
this.dept = dept;
}
@Override
public String toString()
{
return "Emp{" + "ename='" + ename + '\'' + ", gender='" + gender + '\'' + ", dept=" + dept + '}';
}
}
使用xml方式创建实体类对象,并完成对象属性内部bean的注入。
<?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="emp" class="com.lonelysunset.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="Lucy"/>
<property name="gender" value="女"/>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.lonelysunset.bean.Dept">
<property name="dname" value="超神学院"/>
</bean>
</property>
</bean>
</beans>
测试结果。
@Test
public void test5(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
Emp emp = (Emp) context.getBean("emp");
System.out.println(emp);// Emp{ename='Lucy', gender='女', dept=Dept{dname='超神学院'}}
}
3.5.4.3 在xml中注入属性-级联赋值
3.5.4.3.1 第一种写法
采用外部bean的方式进行级联赋值,类就采用刚刚创建的Dept和Emp。
<?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="emp" class="com.lonelysunset.bean.Emp">
<property name="ename" value="jack"/>
<property name="gender" value="男"/>
<property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="com.lonelysunset.bean.Dept">
<property name="dname" value="技术部"/>
</bean>
</beans>
测试结果
@Test
public void test6(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean8.xml");
Emp emp = (Emp) context.getBean("emp");
System.out.println(emp);// Emp{ename='jack', gender='男', dept=Dept{dname='技术部'}}
}
3.5.4.3.2 第二种写法
需要在Emp类中为Dept对象属性生成一个get方法,从而达到覆盖的效果,类依然采用刚刚创建的Dept和Emp。
public class Emp(){
...
public Dept getDept()
{
return dept;
}
...
}
在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 id="emp" class="com.lonelysunset.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="rose"/>
<property name="gender" value="女"/>
<!--级联赋值-->
<property name="dept" ref="dept"/>
<property name="dept.dname" value="安保部"/>
</bean>
<bean id="dept" class="com.lonelysunset.bean.Dept">
<property name="dname" value="技术部"/>
</bean>
</beans>
测试结果。
@Test
public void test7(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean9.xml");
Emp emp = (Emp) context.getBean("emp");
System.out.println(emp);// Emp{ename='rose', gender='女', dept=Dept{dname='安保部'}}
}
3.5.5 IOC操作Bean管理-在xml中注入集合属性
分别注入数组类型、List类型、Map类型、Set类型的属性。
创建类,定义数组、List、Map、Set类型属性,并生成对应的set方法。
// 创建Stu实体类
public class Stu
{
private String[] courses;
private List<String> list;
private Map<String, String> map;
private Set<String> set;
public void setCourses(String[] courses)
{
this.courses = courses;
}
public void setList(List<String> list)
{
this.list = list;
}
public void setMap(Map<String, String> map)
{
this.map = map;
}
public void setSet(Set<String> set)
{
this.set = set;
}
@Override
public String toString()
{
return Arrays.asList(courses)+"-"+list+"-"+map+"-"+set;
}
}
在xml中创建stu对象,并注入属性。
<?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="stu" class="com.lonelysunset.CollectionsType.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>Java课程</value>
<value>PHP课程</value>
</array>
</property>
<!--list集合类型属性注入-->
<property name="list">
<list>
<value>SpringCloud从入门到放弃</value>
<value>SpringSecurity从入门到入土</value>
</list>
</property>
<!--map集合类型属性注入-->
<property name="map">
<map>
<entry key="花果山" value="孙悟空"/>
<entry key="高老庄" value="猪八戒"/>
</map>
</property>
<!--set集合类型属性注入-->
<property name="set">
<set>
<value>如来</value>
<value>杀心观音</value>
</set>
</property>
</bean>
</beans>
测试结果。
@Test
public void test8(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean10.xml");
Stu stu = (Stu) context.getBean("stu");
System.out.println(stu);// [Java课程, PHP课程]-[SpringCloud从入门到放弃, SpringSecurity从入门到入土]-{花果山=孙悟空, 高老庄=猪八戒}-[如来, 杀心观音]
}
3.5.5.1 在集合内部设置对象类型值
创建Student、Course类。
public class Student
{
private List<Course> courseList;
public void setCourseList(List<Course> courseList)
{
this.courseList = courseList;
}
@Override
public String toString()
{
return "Student{" + "courseList=" + courseList + '}';
}
}
public class Course
{
private String name;
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Course{" + "name='" + name + '\'' + '}';
}
}
在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 id="student" class="com.lonelysunset.CollectionsType.Student">
<!--在集合中注入对象类型的值-->
<property name="courseList">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>
<!--创建多个Course对象-->
<bean id="course1" class="com.lonelysunset.CollectionsType.Course">
<property name="name" value="Spring5框架"/>
</bean>
<bean id="course2" class="com.lonelysunset.CollectionsType.Course">
<property name="name" value="MyBatis框架"/>
</bean>
</beans>
测试结果。
@Test
public void test9(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean11.xml");
Student stu = (Student) context.getBean("student");
System.out.println(stu);// Student{courseList=[Course{name='Spring5框架'}, Course{name='MyBatis框架'}]}
}
3.5.5.2 将集合内部的值提取成公共部分使用
创建一个Book类。
public class Book
{
private List<String> books;
public void setBooks(List<String> books)
{
this.books = books;
}
@Override
public String toString()
{
return "Book{" + "books=" + books + '}';
}
}
在xml文件中,创建book对象,并将Book类中的books属性提取成公共部分。
在xml文件中引入util命名空间,和p命名空间差不多,copy就行。
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--1.提取list集合类型,并完成属性注入-->
<util:list id="bookList">
<value>Spring5框架</value>
<value>MyBatis框架</value>
<value>SpringMVC框架</value>
</util:list>
<!--2.完成属性注入-->
<bean id="bookClass" class="com.lonelysunset.CollectionsType.Book">
<property name="books" ref="bookList"/>
</bean>
</beans>
测试结果。
@Test
public void test10(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean12.xml");
com.lonelysunset.CollectionsType.Book bookClass = (com.lonelysunset.CollectionsType.Book) context.getBean("bookClass");
System.out.println(bookClass);// Book{books=[Spring5框架, MyBatis框架, SpringMVC框架]}
}
3.5.6 IOC操作Bean管理-FactoryBean
Spring有两种类型的bean,一种是普通的bean,另一种是工厂bean,即:FactoryBean。
普通bean的特点:在配置文件中定义bean标签类型就是返回类型。
工厂bean的特点:在配置文件中定义bean标签类型可以和返回类型不一样。举例:
首先创建类,让这个类作为工厂bean,实现FactoryBean接口即可。
实现FactoryBean接口里面的方法,在实现的方法中定义返回的bean类型。
在xml中创建MyBean对象。
测试结果。
// 泛型中定义需要返回的类型
public class MyBean implements FactoryBean<Course>
{
@Override
public Course getObject() throws Exception
{
Course course = new Course();
course.setName("金瓶梅");
return course;
}
@Override
public Class<?> getObjectType()
{
return null;
}
@Override
public boolean isSingleton()
{
return false;
}
}
<?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="myBean" class="com.lonelysunset.factorybean.MyBean"></bean>
</beans>
@Test
public void test11()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean13.xml");
Course myBean = (Course) context.getBean("myBean");
System.out.println(myBean);// Course{name='金瓶梅'}
// 为啥我感觉,这个BeanFactory有点SB?😓
}
3.5.7 IOC操作Bean管理-bean作用域
在Spring中,默认情况下,设置创建bean是单实例对象。举例说明:
创建Computer类。
public class Computer
{
private String name;
public void setName(String name)
{
this.name = name;
}
}
创建bean对象。
<?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="computer" class="com.lonelysunset.bean.Computer">
</bean>
</beans>
测试结果。
@Test
public void test12()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean14.xml");
Computer computer1 = (Computer) context.getBean("computer");
Computer computer2 = (Computer) context.getBean("computer");
System.out.println(computer1==computer2);// true,两个是同一个对象,说明两个对象引用指向的是同一块内存地址,同一块堆中的区域。所以说,默认情况下,是单例模式。
}
如何设置多实例?需要在Spring配置文件的bean标签里面使用scope属性,指定范围,并设置scope属性值为prototype,表示多实例对象。举例:
<?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">
<!--
scope属性,表示设置bean为单例或多例
默认值为singleton,表示单实例对象。
prototype表示多实例对象。
-->
<bean id="computer" class="com.lonelysunset.bean.Computer" scope="prototype">
</bean>
</beans>
测试结果。
@Test
public void test13()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean15.xml");
Computer computer1 = (Computer) context.getBean("computer");
Computer computer2 = (Computer) context.getBean("computer");
System.out.println(computer1==computer2);// false,其实也可以将hashCode打出来看一下,肯定是不一样的。
}
3.5.7.1 bean作用域结论
singleton为单实例,prototype为多实例。
设置scope属性值为singleton后,会在加载spring配置文件时创建单实例对象。
设置scope属性值为prototype后,并不会在加载spring配置文件时创建对象,而是在调用getBean方法时创建多个不同的对象。
3.5.8 IOC操作Bean管理-bean生命周期
3.5.8.1 什么是生命周期?
从对象创建到对象销毁的过程,称之为生命周期。
3.5.8.2 bean的生命周期过程?
通过无参构造器创建bean的实例。(无参构造器)
为bean的属性设置值和对其它bean引用。(调用set方法)
调用bean的初始化方法。(需要进行配置初始化方法)
bena可以使用了。(对象获取到了)
当容器关闭的时候,调用bean的销毁方法。(需要进行配置销毁的方法)
演示bean的生命周期
创建Order类。
public class Orders
{
private String name;
// 创建无参构造
public Orders()
{
System.out.println("第一步 执行无参构造器创建bean实例");
}
// 创建set注入方法
public void setName(String name)
{
this.name = name;
System.out.println("第二步 执行set方法设置属性值");
}
// 创建执行初始化的方法
public void initMethod(){
System.out.println("第三步 执行初始化方法");
}
// 创建执行销毁的方法
public void destroyMethod(){
System.out.println("第五步 执行销毁方法");
}
}
在xml中创建bean实例对象,并配置初始化和销毁方法。
<?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="orders" class="com.lonelysunset.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="金刚"/>
</bean>
</beans>
测试结果。
@Test
public void test14()
{
// ApplicationContext 接口中没有close方法
// ApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
// AbstractApplicationContext实现了ApplicationContext接口,内部新增了close方法,并被ClassPathXmlApplicationContext继承,因此ClassPathXmlApplicationContext可以调用close方法。
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
Orders order = (Orders) context.getBean("orders");
System.out.println("第四步 获取创建bean的实例对象");
System.out.println(order);
// 手动让bean实例销毁
context.close();
/**
* 运行结果:
* 第一步 执行无参构造器创建bean实例
* 第二步 执行set方法设置属性值
* 第三步 执行初始化方法
* 第四步 获取创建bean的实例对象
* com.lonelysunset.bean.Orders@646d64ab
* 第五步 执行销毁方法
*/
}
3.5.8.3 关于bean生命周期的细化版-后置处理器
3.5.8.3.1 配置后置处理器,bean的生命周期有7步
通过无参构造器创建bean示例。(无参构造器)
为bean的属性设置值和对其它bean引用。(调用set方法)
将bean实例传递给bean的后置处理器方法,postProcessBeforeInitialization。
调用bean的初始化方法。(需要进行配置初始化方法)
将bean示例传递给bean的后置处理器方法,postProcessAfterInitialization。
bean可以使用了。(对象获取到了)
当容器关闭的时候,调用bean的销毁方法。(需要进行配置销毁的方法)
3.5.8.3.2 演示添加后置处理器后的bean的生命周期
创建类,需要实现BeanPostProcessor接口,创建后置处理器。
public class MyBeanPost implements BeanPostProcessor
{
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
System.out.println("在初始化之后执行的方法");
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
{
System.out.println("在初始化之前执行的方法");
return bean;
}
}
在xml中,配置后置处理器,当配置后置处理器后,spring会自动为每一个bean自动创建初始化前和初始化后执行的方法。
<?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="orders" class="com.lonelysunset.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="金刚"/>
</bean>
<!-- 直接在xml配置文件中注册即可,都不需要与其他bean关联 -->
<bean id="myBeanPost" class="com.lonelysunset.bean.MyBeanPost"></bean>
</beans>
测试结果。
@Test
public void test14()
{
// ApplicationContext 接口中没有close方法
// ApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
// AbstractApplicationContext实现了ApplicationContext接口,内部新增了close方法,并被ClassPathXmlApplicationContext继承,因此ClassPathXmlApplicationContext可以调用close方法。
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean16.xml");
Orders order = (Orders) context.getBean("orders");
System.out.println("第四步 获取创建bean的实例对象");
System.out.println(order);
// 手动让bean实例销毁
context.close();
/**
* 运行结果:
* 第一步 执行无参构造器创建bean实例
* 第二步 执行set方法设置属性值
* 在初始化之前执行的方法
* 第三步 执行初始化方法
* 在初始化之后执行的方法
* 第四步 获取创建bean的实例对象
* com.lonelysunset.bean.Orders@9a7504c
* 第五步 执行销毁方法
*/
}
3.5.9 IOC操作Bean管理-xml自动装配
3.5.9.1 什么是自动装配?
根据指定装配规则,通过配置属性名或属性类型,Spring自动将匹配的属性值进行注入。3.5.9.2 演示自动装配过程
3.5.9.2.1 根据属性名自动注入
创建Dpet、Emp类,并在Emp类中注入Dept对象引用。
public class Dept
{
private String name;
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Dept{" + "name='" + name + '\'' + '}';
}
}
public class Emp
{
private String name;
// 员工类中注入部门类,奇奇怪怪
private Dept dept;
public void setName(String name)
{
this.name = name;
}
public void setDept(Dept dept)
{
this.dept = dept;
}
@Override
public String toString()
{
return "Emp{" + "name='" + name + '\'' + ", dept=" + dept + '}';
}
}
在xml配置文件中创建bean对象。
<?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或内部bean注入对象属性值,现在
通过自动装配去实现,这里介绍根据属性名称注入。
实现自动装配
bean标签中的autowire属性,配置自动装配。
autowire属性常用两个值:
byName根据属性名称注入,要求注入的bean的id值和属性名称一致
byType根据属性类型注入,要求注入的bean的class值和属性类型一致
-->
<bean id="emp" class="com.lonelysunset.autowire.Emp" autowire="byName">
<property name="name" value="小吴"/>
</bean>
<bean id="dept" class="com.lonelysunset.autowire.Dept">
<property name="name" value="技术部"/>
</bean>
</beans>
测试结果。
@Test
public void test15(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean17.xml");
com.lonelysunset.autowire.Emp emp = (com.lonelysunset.autowire.Emp) context.getBean("emp");
System.out.println(emp);// Emp{name='小吴', dept=Dept{name='技术部'}}
}
3.5.9.2.2 根据属性类型自动注入
创建Dpet、Emp类,并在Emp类中注入Dept对象引用。
public class Dept
{
private String name;
public void setName(String name)
{
this.name = name;
}
@Override
public String toString()
{
return "Dept{" + "name='" + name + '\'' + '}';
}
}
public class Emp
{
private String name;
private Dept dept;
public void setName(String name)
{
this.name = name;
}
public void setDept(Dept dept)
{
this.dept = dept;
}
@Override
public String toString()
{
return "Emp{" + "name='" + name + '\'' + ", dept=" + dept + '}';
}
}
在xml配置你文件中创建bean对象。
<?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或内部bean注入对象属性值,现在
通过自动装配去实现,这里介绍根据属性类型注入。
实现自动装配
bean标签中的autowire属性,配置自动装配。
autowire属性常用两个值:
byName根据属性名称注入,要求注入的bean的id值和属性名称一致
byType根据属性类型注入,要求注入的bean的class值和属性类型一致
-->
<bean id="emp" class="com.lonelysunset.autowire.Emp" autowire="byType">
<property name="name" value="小吴"/>
</bean>
<bean id="dept" class="com.lonelysunset.autowire.Dept">
<property name="name" value="技术部"/>
</bean>
</beans>
测试结果。
@Test
public void test16(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean18.xml");
com.lonelysunset.autowire.Emp emp = (com.lonelysunset.autowire.Emp) context.getBean("emp");
System.out.println(emp);// Emp{name='小吴', dept=Dept{name='技术部'}}
}
3.5.10 IOC操作Bean管理-外部属性文件引入
这里了解一下即可。
主要用于公共文件的引入,比如数据库配置文件之类,但是在真实开发场景中用的也不多,毕竟后期都是使用SpringBoot中的yml进行配置。这里就是顺带一说。毕竟Java都是经典白学嘛。😊
3.5.10.1 原始方式配置数据库信息
<?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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/userDb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
3.5.10.2 采用外部属性文件引入的方式配置数据库信息
创建外部属性文件,properties格式文件,写入数据库配置信息。
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root
将外部properties属性文件引入到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">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--其实也没啥,就是动态些-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="username" value="${prop.userName}"/>
<property name="password" value="${prop.password}"/>
</bean>
</beans>
3.5.11 IOC操作Bean管理-基于注解方式
3.5.11.1 什么是注解?
注解是代码的特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值…)
注解主要作用于类、方法和属性上面。
使用注解的目的是为了简化开发,简化xml配置。就目前位为止,以上都是通过xml配置文件方式去创建对象,以及设置对应的属性值。那真实开发场景一个项目中肯定是有很多类的,那这个配置文件得有多少行哦,从而导致后期难以维护。
3.5.11.2 Spring针对Bean管理-创建对象提供了四个注解
@Component
@Service
@Controller
@Repository
以上4个注解的功能都是完全一样的,都是用来创建bean实例的。只不过在开发中为了更有效的区分,通常我们建议在接口层使用哦@Controller,业务处理层使用@Service,数据访问层使用@Repository,而其它不容易区分的Bean实例,则使用@Component标识
3.5.11.3 基于注解方法实现对象的创建
重点,以上全是白学,毕竟开发的真实场景中,需要用注解嘛!😊
3.5.11.3.1 引入依赖
补充:通常,我们如果使用Spring5框架进行开发,应将Spring5所涉及的依赖全部引入,其实吧,后面学到SpringMVC时,直接引入SpringMVC就行了。
<!--4.spring-aop部分-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
3.5.11.3.2 开启组件扫描
补充:开启组件扫描的前提是,需要引入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">
<!--
开启组件扫描:
1.如果需要扫描多个包,则多个包之间使用逗号进行分割即可。
2.另一种方式也可以实现扫描多个包,直接扫描多个包的公共上层目录即可。
-->
<context:component-scan base-package="com.lonelysunset.annotation"></context:component-scan>
</beans>
3.5.11.3.3 创建类,并在类上添加注解
/**
* 注解中的value可以省略不写,默认值就是类的名称,首字母小写
* 如下,如果不写,默认就是userService
*/
//@Service(value = "userService")
@Service
public class UserService
{
public void show()
{
System.out.println("show。。。");
}
}
3.5.11.3.4 测试结果
@Test
public void test18(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean21.xml");
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();// show。。。
}
3.5.11.3.5 关于开启组件扫描的细节补充
<?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:
添加use-default-filters="false"属性,表示不使用默认的filter。
通过自己配置filter,即context:exclude-filter,表示指定扫描哪些内容。
如下配置解释:只扫描com.lonelysunset.annotation包下,所有被@Service的注解标识的类。
-->
<context:component-scan base-package="com.lonelysunset.annotation" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
</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: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">
<!--
默认情况下,只要配置了context:exclude-filter,则表示使用filter过滤器扫描包
细节2:
通过添加配置filter,即context:exclude-filter,表示指定不扫描哪些内容。
如下配置解释:com.lonelysunset.annotation包下,所有被@Service的注解标识的类都不进行扫描。
-->
<context:component-scan base-package="com.lonelysunset.annotation">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
</beans
3.5.11.4 基于注解方式实现属性注入
3.5.11.4.1 @Autowired
@Autowried,根据属性类型自动装配。
演示使用:
开启组件扫描。
<?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">
<context:component-scan base-package="com.lonelysunset.annotation"></context:component-scan>
</beans>
创建UserService、UserDao、UserDaoImpl。并在UserService和UserDaoImpl类上添加注解,用于创建bean对象。然后在UserService类中注入UserDao接口的属性。使用@Autowried通过属性类型进行注入。
@Service
public class UserService
{
// 不需要添加set方法
@Autowired
private UserDao userDao;
public void show()
{
System.out.println("show。。。");
userDao.add();
}
}
public interface UserDao
{
public void add();
}
@Repository
public class UserDaoImpl implements UserDao
{
@Override
public void add()
{
System.out.println("userDao 中的 add...");
}
}
测试结果。
@Test
public void test19(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean24.xml");
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();
/**
* 运行结果:
* show。。。
* userDao 中的 add...
*/
}
3.5.11.14.2 @Qualifier
@Qualifier,根据名称进行注入,使用前提是和@Autowried搭配使用。
解释:已知@Autowried根据属性类型完成属性注入,而@Qualifier根据属性名称完成属性注入,当UserDao有多个实现类时,则Spring使用@Autowried时,将无法区分我们到底需要注入哪一个属性,因此需要使用@Qualifier注解声明区分。
演示使用:
创建类和接口,并完成对象的创建和属性的注入。
public interface UserDao
{
public void add();
}
//@Repository(value = "userDaoImpl1")
@Repository
public class UserDaoImpl implements UserDao
{
@Override
public void add()
{
System.out.println("userDaoImpl1 中的 add...");
}
}
//@Repository(value = "userDaoImpl2")
@Repository
public class UserDaoImpl2 implements UserDao
{
@Override
public void add()
{
System.out.println("userDaoImpl2 中的 add...");
}
}
@Service
public class UserService
{
@Autowired
@Qualifier(value = "userDaoImpl2")
private UserDao userDao;
public void show()
{
System.out.println("show。。。");
userDao.add();
}
}
测试结果。
@Test
public void test19(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean24.xml");
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();
/**
* 运行结果:
* show。。。
* userDaoImpl2 中的 add...
*/
}
3.5.11.14.3 @Resource
@Resource,可以根据属性类型注入,也可以根据属性名称注入。
@Resource // 根据属性类型注入
private UserDao userDao;
@Resource(value="userDao1") // 根据属性名称注入
private UserDao userDao1;
3.5.11.14.4 @Value
@Value,注入普通类型属性。
@Value(value="黄飞鸿")
private String name;
1
2
3.5.12 IOC操作Bean管理-完全注解开发
创建配置类,用于替代xml配置文件
@Configuration // 声明将SpringConfig类作为配置了,替代xml配置文件
//@ComponentScan(basePackages = {"com.lonelysunset"}) // 开启组件扫描
@ComponentScan("com.lonelysunset")
public class SpringConfig
{
}
测试结果。
@Test
public void test20(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
com.lonelysunset.annotation.service.UserService userService = (com.lonelysunset.annotation.service.UserService) context.getBean("userService");
userService.show();
/**
* 运行结果:
* show。。。
* userDaoImpl2 中的 add...
*/
}
4. AOP
4.1 什么是AOP?
AOP:面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能。
举个例子:
我们的程序有一个登录功能,通常在没有鉴权系统的登录模块会直接判断用户名和密码,如果都通过了,则直接放行进入系统,那么后期产品希望给程序加上一个新的功能-鉴权系统,方便程序自动区分哪些用户具有哪些权限,可以做哪些事情。这时我们可以使用AOP来解决,即通过不修改源代码的基础上,实现此功能。
4.2 AOP底层原理
AOP底层使用的是动态代理。
实现动态代理的方式有两种,需要区分是否有接口。
4.2.1 第一种有接口情况,使用JDK动态代理
创建接口实现类代理对象,增强类的方法。
4.2.2 第二种无接口情况,使用CGLIB动态代理
创建子类的代理对象,增强类的方法。(以下没有介绍这种用法)
4.3 AOP-JDK动态代理
使用JDK动态代理,使用Proxy类里面的方法创建代理对象。
模拟用于登录的操作。
创建UserDao接口,声明login方法。
public interface UserDao
{
Boolean login(String username,String password);
}
创建UserDaoImpl实现类,实现userDao接口,重写login方法。
public class UserDaoImpl implements UserDao
{
@Override
public Boolean login(String username, String password)
{
if (!(StringUtils.hasText(username) && StringUtils.hasText(password))){
System.out.println("用户名或密码不能为空");
return false;
}
if (!(username.equals("admin") || username.equals("user"))){
System.out.println("查无此用户");
return false;
}
if (!password.equals("123")){
System.out.println("密码错误");
return false;
}
System.out.println("登录成功");
return true;
}
}
创建JDKProxy类和UserDaoProxy类。
public class JDKProxy
{
public static void main(String[] args)
{
UserDaoImpl userDao = new UserDaoImpl();
Class[] classes = {UserDao.class};
/**
* newProxyInstance方法有三个参数:
* 第一个参数:当前代理类的类加载器
* 第二个参数:需要进行增强方法的类所实现的接口,支持多个接口
* 第三个参数:实现InvocationHandler接口,创建代理对象,在实现该接口中编写增加的业务部分。
*/
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), classes, new UserDaoProxy(userDao));
Boolean user = dao.login("admin", "123");
System.out.println(user);
}
}
class UserDaoProxy implements InvocationHandler
{
// 通过构造器传参,参数类型未知,则直接使用基类Object
// 创建谁的代理对象,就像谁传过来
private Object obj;
public UserDaoProxy(Object obj)
{
this.obj = obj;
}
// 增加的业务逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// 执行原有的登录功能
Boolean res = (Boolean) method.invoke(obj, args);
// 为登录模块增加鉴权功能
if (res)
{
if (args[0].equals("admin"))
{
System.out.println("管理员权限");
}
else if (args[0].equals("user"))
{
System.out.println("普通用户权限");
}
}
return res;
}
}
4.4 AOP-术语
连接点:类里面哪些方法可以被增强,这些方法就称之为连接点。
切入点:实际被真正增强的方法,称之为切入点。
通知(增强):实际增强的逻辑部分称之为通知(增强)。
通知多有种类型:
前置通知。
后置通知。
环绕通知。
异常通知。
最终通知。
切面:切面是一个动作,把通知应用到切入点的过程就叫切面。
4.5 AOP操作-准备工作
4.5.1 Spring框架一般都是基于AspectJ实现AOP操作
AspectJ不是Spring的组成部分,而是一款独立的AOP框架,一般将AspectJ和Spring框架一起使用,进行AOP操作。
4.5.2 基于AspectJ实现AOP操作-两种方式
基于xml配置文件实现。
基于注解方式实现。(推荐,主流方式)
4.5.3 引入AspectJ依赖
<!--AspectJ-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8.M1</version>
</dependency>
4.5.4 切入点表达式
切入点表达式的作用:用于对那个类里面的哪个方法进行增强。
语法结构:execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
- 举例1:对com.atguigu.dao.BookDao类里面的add进行增强。
execution(* com.atguigu.dao.BookDao.add(.. ))
- 举例2:对com.atguigu.dao.BookDao类里面的所有方法进行增强。
execution(* com.atguigu.dao.BookDao.*(..))
- 举例3:对com.atguigu.dao包里面的所有类,类里面的所有方法进行增强。
execution(* com.atguigu.dao.*.*(..))
4.6 AOP操作-基于AspectJ注解方式
创建类,并在类中定义方法。
使用注解创建User类的bean对象。
@Component
public class User
{
public void add(){
System.out.println("add...");
}
}
创建配置类。
开启注解扫描。
开启AspectJ生成代理对象。
@Configuration
@ComponentScan("com.lonelysunset.aopanno")
@EnableAspectJAutoProxy(proxyTargetClass = true) // 开启AspectJ生成代理对象
public class SpringConfig
{
}
创建增强类。
编写增强逻辑。
在增强类中,在通知方法上面添加通知类型注解,使用切入点表达式进行配置。
@Component
@Aspect // 表示生成代理对象
public class UserProxy
{
// @Before注解表示前置通知
@Before(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void before(){
System.out.println("before...");
}
// @AfterReturning注解表示后置通知,表示切点返回结果时执行。如果有异常,则无法返回结果,不能执行。
@AfterReturning(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
// @AfterThrowing注解表示异常通知,表示切入点有异常时,才执行,执行后。
@AfterThrowing(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
// @Around注解表示环绕通知,表示在切入点前后环绕执行
@Around(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
{
System.out.println("环绕之前...");
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
// @After注解表示最终通知,就算切入点有异常,也会执行
@After(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void after(){
System.out.println("after...");
}
}
测试结果。
public class TestAop
{
public static void main(String[] args)
{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
user.add();
/**
* 运行结果:
* 环绕之前...
* before...
* add...
* 环绕之后...
* after...
* afterReturning...
*/
}
}
4.6.1 相同的切入点抽取
参考以上现有代码进行抽取。
@Component
@Aspect // 表示生成代理对象
public class UserProxy
{
// 抽取相同的切入点
@Pointcut(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void pointDemo()
{
}
// @Before注解表示前置通知
@Before(value = "pointDemo()")
public void before()
{
System.out.println("before...");
}
// @AfterReturning注解表示后置通知,表示切点返回结果时执行。如果有异常,则无法返回结果,不能执行。
@AfterReturning(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterReturning()
{
System.out.println("afterReturning...");
}
// @AfterThrowing注解表示异常通知,表示切入点有异常时,才执行,执行后。
@AfterThrowing(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void afterThrowing()
{
System.out.println("afterThrowing...");
}
// @Around注解表示环绕通知,表示在切入点前后环绕执行
@Around(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
{
System.out.println("环绕之前...");
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
// @After注解表示最终通知,就算切入点有异常,也会执行
@After(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void after()
{
System.out.println("after...");
}
}
4.6.2 在多个增强类中,对同一个方法进行增强,并设置增强类的优先级
在增强类上面添加@Order(数字类型值),数字类型值越小,优先级越高。
@Component
@Aspect
@Order(1)
public class PersonProxy
{
@Before(value = "execution(* com.lonelysunset.aopanno.User.add(..))")
public void personProxy(){
System.out.println("优先级最高的增强类...");
}
}
测试结果。
public class TestAop
{
public static void main(String[] args)
{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
user.add();
/**
* 运行结果:
* 优先级最高的增强类...
* 环绕之前...
* before...
* add...
* 环绕之后...
* after...
* afterReturning...
*/
}
}
4.7 AOP操作-基于xml配置文件方式
创建两个类,一个增强类和一个被增强类,然后创建对应的方法。
public class Book
{
public void buy(){
System.out.println("购买一本书...");
}
}
public class BookProxy
{
public void buyBookBefore(){
System.out.println("我得先摸摸口袋有没有钱");
}
}
在Spring配置文件中创建两个类的对象。
在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: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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--使用配置文件创建对象-->
<bean id="book" class="com.lonelysunset.aopxml.Book">
</bean>
<bean id="bookProxy" class="com.lonelysunset.aopxml.BookProxy"/>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.lonelysunset.aopxml.Book.buy(..))"/>
<!--配置切面-->
<!--什么是配置切面?将通知引用在切入点上,就是切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<!--简单理解:通知作用在切入点,这里配置的是前置通知-->
<aop:before method="buyBookBefore" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>
测试结果:
@Test
public void test21(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean25.xml");
com.lonelysunset.aopxml.Book book = context.getBean("book", com.lonelysunset.aopxml.Book.class);
book.buy();
/**
* 运行结果:
* 我得先摸摸口袋有没有钱
* 购买一本书...
*/
}
总结最全:尚硅谷-Spring5框架2020笔记_java_oneOldHen-云原生
原文链接:https://blog.youkuaiyun.com/bigqiangwu/article/details/126747782