spring的核心模块
spring-core | 依赖注入IOC与DI的最基本实现 |
---|---|
spring-beans | Bean工厂与bean的装配 |
spring-context | spring的context上下文即IoC容器 |
spring-expression | spring表达式语言 |
spring中的IOC
IOC是 Inverse of Control 的简写,意思是控制反转。是降低对象之间的耦合关系的设计思想,即IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序通过对对象的统一管理与分配,降低对象之间的耦合。
Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制,也就是说在传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
下面我们通过一个maven小demo,使用代码来感受一下控制反转;我们在写web项目时,一般都是业务逻辑sevice层调用数据dao层,实现对数据的处理访问,接下来我们来简单的写写数据层和业务层。
//用户接口
public interface UserDao {
int add();
}
//用户接口的实现类
public class UserDaoImpl implements UserDao {
public int add() {
System.out.println("UserDaoImpl被调用");
return 0;
}
}
//业务逻辑层接口
public interface UserService {
int add();
}
//业务逻辑几口实现类
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public int add() {
userDao.add();
System.out.println("UserServiceImpl被调用");
return 0;
}
}
//测试类
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.add();
}
}
运行结果:先是UserDaoImpl这个实现类被调用,然后是UserServiceImpl这个实现类被调用。
以上就是我们传统开发的大致流程,这里我们有个需求就是在业务逻辑层的实现类中,假如说我们数据访问层的实现类需求有变动,实现了一个新接口UserDaoImpl2,那么我们是在代码中(业务逻辑层的实现类中)直接改动代码,修改新实现出来的对象吗?答案是否定的,由于我们在类内部主动修改对象,它是难于测试的,因为测试的一方是改不了的,测试一方可能得到的是一个字节码文件,不准许直接改源代码。所以我们就引入spring,它把对象的创建提取出来,对象的创建和修改交给IOC容器统一的进行管理分配,程序中所有的对象都通过这个容器进行管理。所以我们变更需求而修改实现类的话,直接通过配置文件修改即可(修改了配置文件就相当于修改了IOC容器),源码不能直接修改,但是配置文件可以随意修改。
使用Ioc优化代码:
- 在pom.xml文件中添加所需要的jar依赖
<!-- Spring的核心工具包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--在基础IOC功能上提供扩展服务,还提供许多企业级服务的支持,有邮件服务、
任务调度、远程访问、缓存以及多种视图层框架的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring context的扩展支持,用于MVC方面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring表达式语言 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
- 创建配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- 在配置文件中创建对象
//bean表示对象 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="huang.dao.impl.UserDaoImpl">
</bean>
<!--创建对象名称为userService,它是根据huang.service.impl.UserServiceImpl这个类进行创建出来的-->
<bean id="userService" class="huang.service.impl.UserServiceImpl">
<!--这个对象有个属性,名称是userDao,类型是huang.dao.impl.UserDaoImpl2-->
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
注意这一步骤就是在使用IOC容器创建对象,对对象统一管理;在这个配置文件中对程序中所有有调用关系的对象进行管理与分配。今后在框架的学习中最好是通过这种方式创建对象,而不是直接的new。
对象已经创建出来了,如何给程序中的对象进行赋值?
若对象是某个类的属性,那么程序就通过set方式得到对象;
若对象不是某个类的属性,就通过加载配置文件的方式得到对象。
修改UserServiceImpl中的代码,通过容器得到UserDao对象
public class UserServiceImpl implements UserService {
//这里的userDao对象就是通过容器得到的
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public int add() {
userDao.add();
System.out.println("UserServiceImpl被调用");
return 0;
}
}
- 在配置文件中创建对象
public class Main {
public static void main(String[] args) {
//加载配置文件
ApplicationContext app = new ClassPathXmlApplicationContext("application.xml");
//通过配置文件得到对象
UserService userService = (UserService) app.getBean("userService");
userService.add();
}
}
至此,程序中所有对象都是由IOC容器统一进行创建和管理的,现在我们要变更需求,换实现类只需要改动配置文件即可,如下,
<bean id="userDao" class="huang.dao.impl.UserDaoImpl2">
</bean>
<!--创建对象名称为userService,它是根据huang.service.impl.UserServiceImpl这个类进行创建出来的-->
<bean id="userService" class="huang.service.impl.UserServiceImpl">
<!--这个对象有个属性,名称是userDao,类型是huang.dao.impl.UserDaoImpl2-->
<property name="userDao" ref="userDao"></property>
</bean>
程序的运行结果:
IOC的主要的目的就是统一创建分配对象,达到对象间的解耦。
bean标签的属性介绍
属性 | 说明 |
---|---|
class | 指定bean对象对应类的全路径 |
name | name是bean对象对应对象的一个标识;id和name的作用都是差不多的,都是用于给bean对象起名字,但是不同的是name准许重复,而id唯一标识。 |
scope | 执行bean对象创建模式和生命周期,scope="singleton"和scope=“prototype” |
id | id是bean对象的唯一标识,不能添加特别字符 |
lazy-init | 是否延时加载 默认值:false。true 延迟加载对象,当对象被调用的时候才会加载,测试的时候,通过getbean()方法获得对象。lazy-init=“false” 默认值,不延迟,无论对象是否被使用,都会立即创建对象,测试时只需要加载配置文件即可。注意:测试的时候只留下id,class属性 |
init-method | 容器创建对象的初始化方法,只需要加载配置文件即可对象初始化方法 |
destory-method | 销毁对象的方法 |
DI依赖注入
前面我们讲过IOC控制反转,它就是将对象统统的交给spring容器进行管理,管理完成之后就要把对象赋值给调用者,怎么赋值?这就引入了DI,其实赋值的操作就称之为DI,而依赖注入的方式有以下两种:
- 1.set注入值
- 基本属性类型值注入
- property name=“name” value=“jeck”
- 引用属性类型值注入
- property name=“car” ref=“car”
- 基本属性类型值注入
可以参考前面小demo中的set方法注入赋值。
- 2.构造注入
- 通过构造方法创建对象,在创建对象的时候就通过构造函数注入赋值
public Person(String name , Car car){
this.name = name;
this.car = car;
System.out.println("Person的有参构造方法:"+name+car);
}
<bean name="person" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="rose"/>
<constructor-arg name="car" ref="car"/>
</bean>
- 通过spel spring表达式注入赋值
+ property name=“car” ref=“car”
<bean name="car" class="com.xzk.spring.bean.Car" >
<property name="name" value="mime" />
<property name="color" value="白色"/>
</bean>
<!--利用spel引入car的属性 -->
<bean name="person1" class="com.xzk.spring.bean.Person" p:car-ref="car">
//意思就是person1的属性name 的值是从car对象中得到的信息
<property name="name" value="#{car.name}"/>
<property name="age" value="#{person.age}"/>
</bean>
- 通过p命名空间注入值赋值
- 使用p:属性名 完成注入,走set方法
- 基本类型值: p:属性名=“值”
- 引用类型值: P:属性名-ref=“bean名称”
- 实现步骤:配置文件中 添加命名空间p
xmlns:p="http://www.springframework.org/schema/p" //实例 <bean id="u6" class="com.entity.Users" p:age="30" p:name="李四" p:student-ref="stu1"></bean>
- 使用p:属性名 完成注入,走set方法
复杂的数据类型注入
首先创建一个类,添加复杂类型的属性,设置getter和setter方法,因为我们是通过set的方式进行注入的。
package spring01;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* @author HSD
* @create 2020-11-20 16:50
* 复杂类型的注入
*/
public class Teacher {
private Object[] objects;
private Map map;
private List list;
private Set set;
private Properties properties;
public Object[] getObjects() {
return objects;
}
public void setObjects(Object[] objects) {
this.objects = objects;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public Set getSet() {
return set;
}
public void setSet(Set set) {
this.set = set;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
在配置文件中创建对象,为对象的属性进行赋值也就是注入值。
<?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="user123" class="spring01.User" p:name="hhh" p:age="22"></bean>
<bean class="spring01.Teacher" id="teacher">
<!--注入赋值无非就是set注入和构造注入两种,set就是通过set方法,而构造是通过构造函数-->
//注入数组,底层其实是list
<property name="objects">
<list>
<value>123</value>
<value>王五</value>
<ref bean="user123"></ref>
</list>
</property>
//注入list集合
<property name="list">
<list>
<value>123123</value>
<value>王五122</value>
<ref bean="user123"></ref>
</list>
</property>
//注入set集合
<property name="set">
<set>
<value>456456</value>
<value>李六</value>
<ref bean="user123"></ref>
</set>
</property>
//注入map集合
<property name="map">
<map>
<entry key="校花" value="谢大脚"></entry>
<entry key="校草" value="刘能"></entry>
</map>
</property>
//注入properties
<property name="properties">
<props>
<prop key="name">麻子</prop>
<prop key="age">33</prop>
</props>
</property>
</bean>
</beans>
package spring01;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.*;
/**
* @author HSD
* @create 2020-11-20 16:49
*/
public class 复杂类型的注入 {
public static void main(String[] args) {
//读取配置文件中的信息
ApplicationContext app = new ClassPathXmlApplicationContext("application2.xml");
Teacher t = (Teacher) app.getBean("teacher");
//通过对象的get方法获取值,因为对象中的属性值我们已经在配置文件中注入过了,最后循环遍历输出
Object[] objects = t.getObjects();
for (Object o : objects) {
System.out.println(o);
}
System.out.println("---------------");
List list = t.getList();
for (Object o : list) {
if (o instanceof User) {
System.out.println(((User) o).getName() + ((User) o).getAge());
break;
}
System.out.println(o);
}
System.out.println("---------------");
Set set = t.getSet();
for (Object o : set) {
if (o instanceof User) {
System.out.println(((User) o).getName() + ((User) o).getAge());
break;
}
System.out.println(o);
}
System.out.println("---------------");
Map map = t.getMap();
Set set1 = map.keySet();
Iterator iterator = set1.iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
System.out.println(map.get(key));
}
System.out.println("---------------");
Properties properties = t.getProperties();
System.out.println(properties.getProperty("name"));
System.out.println(properties.getProperty("age"));
}
}
运行结果:
现在,我们所学习的spring注入DI和控制反转IOC都是通过操作配置文件的方式来实现的。也就是说我们在配置文件中创建和统一管理对象,并且用set注入和构造注入的方式给程序中对象赋值。接下来我们就来学习使用注解的方式来实现IOC。
注解实现IOC的实现步骤
- 1.配置文件中添加约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
</beans>
- 2.配置注解扫描:指定扫描包下所有类中的注解,扫描包时,会扫描包所有的子孙包
<!--扫描包的设置,扫描有相应注解的包-->
<context:component-scan base-package="annotate.huang"></context:component-scan>
- 3.添加注解
- 添加在类名上,这些注解使用来创建对象的(IOC)
- @Component(“对象名”)
@Service(“person”) // service层
@Controller(“person”) // controller层
@Repository(“person”) // dao层
@Scope(scopeName=“singleton”) //单例对象
@Scope(scopeName=“prototype”) //多例对象
- @Component(“对象名”)
- 添加在属性上,这些注解是用来注入依赖的(DI)
- @Value(“属性值”)
- @Autowired //如果一个接口类型,同时有两个实现类,则报错,此时可以借助@Qualifier(“bean name”) AutoWire默认使用的是配置文件中AutoWire的ByType类型,根据类型注入依赖,若是依赖的类型有多个的话,那么ByType就会报错,@Qualifier(“bean name”) 是AutoWire的一个补充,此注解的类型是ByName,根据名称找依赖关系,注入依赖赋值的。
- 添加在类名上,这些注解使用来创建对象的(IOC)
我们还是使用上面讲的demo为例:
public interface UserDao {
int add();
}
//这次我们没有在配置文件中创建对象,而是通过注解的方式创建对象
@Repository("uDao")
public class UserDaoImpl implements UserDao {
public int add() {
System.out.println("UserDaoImpl被调用");
return 0;
}
}
public interface UserService {
int add();
}
//通过Service注解就创建了对象
@Service("userService")
public class UserServiceImpl implements UserService {
//创建对象之后,不要忘记注入依赖
@Autowired //AutoWire默认使用的是配置文件中AutoWire的ByType类型,根据类型注入依赖
@Qualifier("uDao2") //@Qualifier("bean name") 是AutoWire的一个补充,此注解的类型是ByName,根据名称找依赖关系,注入依赖赋值的。
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public int add() {
userDao.add();
System.out.println("UserServiceImpl被调用");
return 0;
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("AnnotateApplication.xml");
UserService userService = (UserService) app.getBean("userService");
userService.add();
}
}
Aop介绍
AOP(Aspect Oriented Programming)即面向切面编程。即在不改变原程序的基础上为代码段增加新的功能。应用在权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
JDK 的动态代理:针对实现了接口的类产生代理。实现InvocationHandler接口,以下是JDK 的动态代理的代码实现:
/**
* @author HSD
* @create 2020-11-21 10:24
*/
public interface UserDao {
void Test();
void Test2();
}
/**
* @author HSD
* @create 2020-11-21 10:23
*/
public class UserDaoImpl implements UserDao {
public void Test() {
System.out.println("UserDaoImpl");
}
public void Test2() {
System.out.println("UserDaoImplTest2");
}
}
public class JdkProxy implements InvocationHandler {
private UserDao userDao;
//创建有参构造,这样的话就可以在创建代理类对象的时候就可以传入一个真实userDao对象
public JdkProxy(UserDao userDao) {
this.userDao = userDao;
}
//代理方法,定义需要代理的类要做的事情
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志开始");
//中间就是调取真实的方法,肯定要有一个真实的对象
//如何执行这个真实的方法,invoke有点像反射方法
//method就是要执行的方法 对象是userDao对象 方法中的参数是args 这就是要代理的方法执行
//可以理解为通过反射的方法是,调取真实的方法(真实的方法进行调取)
Object invoke = method.invoke(userDao, args); //return 返回的值
System.out.println("日志结束");
return invoke;
}
}
public class AopTest {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
userDao.Test();
//由代理对象来调取方法,创建代理实现类
JdkProxy jdkProxy = new JdkProxy(userDao);
//创建代理对象 :(要被代理的类)类加载器,接口的类型,代理的实现类
UserDao o = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(), jdkProxy);
o.Test2();
}
}
测试结果:
CGlib 的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象,MethodInterceptor接口。
public class Student {
public void say() {
System.out.println("我是学生");
}
public void say2() {
System.out.println("我是高年级学生");
}
}
public class CglibProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始日志:cglib");
//目标方法的调用(真实的方法的调用)
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("结束日志:cglib");
return o1;
}
}
public class CglibProxyTest {
public static void main(String[] args) {
//创建真实对象
Student student = new Student();
//创建代理对象
Enhancer enhancer = new Enhancer();
//指定超类的信息(真实类的信息)
enhancer.setSuperclass(student.getClass());
enhancer.setCallback(new CglibProxy());
Student o = (Student) enhancer.create();
o.say();
o.say2();
}
}