Spring
1、Spring简介
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的开源容器框架
参考文档
中文文档
1.1 框架的主要特征
(1)轻量——完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
(2)控制反转——Spring通过一种称作控制反转(IoC)的技术促进了低耦合。它的底层设计模式采用了工厂模式,所有的 Bean 都需要注册到Bean工厂中,将其初始化和生命周期的监控交由工厂实现管理。
(3)面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发
(4)容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。
1.2 Spring的主要特点
1.方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
1.3 组成
2、Spring之控制反转(IOC)
软件系统中耦合的对象
IOC解耦过程
2.1 百科
(1)控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低耦合度。
(2)最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
(3)通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
2.2 两种方式
(1) 依赖查找:容器提供回调接口和上下文条件给组件。EJB和Apache Avalon 都使用这种方式。
这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回
调方法上(也就是上面所说的 类型1):容器将调用这些回调方法,从而让应用代码获得相关资源。
(2) 依赖注入:组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责
的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通
过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构
造函数参数传入的做法称为构造器注入(Constructor Injection)
2.3 依赖注入(推导)
2.3.1 新建一个User接口
public interface UserMapper {
public void test();
}
2.3.2 新建一个User接口实现类
public class UserMapperImpl implements UserMapper {
@Override
public void test() {
System.out.println("I am superman!");
}
}
2.3.3 新建一个User业务接口
public interface UserService {
public void test();
}
2.3.4新建一个User业务接口实现类
public class UserServiceImpl implements UserService {
//创建一个User接口
UserMapper userMapper;
//通过set方法将User实例注入
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
//也可以通过构造器注入
// public UserServiceImpl(UserMapper userMapper) {
// this.userMapper = userMapper;
// }
@Override
public void test() {
userMapper.test();
}
}
2.3.5 测试
public class MyTest {
public static void main(String[] args) {
//将User实例注入
//构造器注入
//UserService userService = new UserServiceImpl(new UserMapperImpl());
//set方法注入
UserService userService = new UserServiceImpl();
((UserServiceImpl)userService).setUserMapper(new UserMapperImpl02());//接口不能使用实现类中特有的方法
userService.test();
}
}
2.4 IOC创建对象方式
2.4.1 创建一个测试类
public class User {
private String name;
public User(){
System.out.println("无参构造器被调用!");
}
public User(String name){
this.name = name;
}
public void test(){
System.out.println(name);
}
}
2.4.2 编写一个配置文件
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
2.4.3 将对象配置到applicationContext.xml中
- 通过无参构造器创建对象。
//这句话相当于 User user = new User; id的值相当于对象名 class是对象的类型的全限定名(包名+类名)
<bean id="user" class="com.xlj.pojo.User"/>
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//getBean("xxx") 就是获取一个对象。括号内传入对象名也就是bean的id
//这里可以显示设定对象类型 User user = context.getBean("user",User.class);
User user = (User) context.getBean("user");//输出无参构造器被调用
}
- 通过有参构造器创建对象
//通过类型创建
<bean id="u1" class="com.xlj.pojo.User">
<constructor-arg type="java.lang.String" value="王老一"/>
</bean>
//通过参数名创建
<bean id="u2" class="com.xlj.pojo.User">
<constructor-arg name="name" value="王老二"/>
</bean>
//通过参数下标创建
<bean id="u3" class="com.xlj.pojo.User">
<constructor-arg index="0" value="王老三"/>
</bean>
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User u1 = context.getBean("u1", User.class);
User u2 = context.getBean("u2", User.class);
User u3 = context.getBean("u3", User.class);
u1.test();//王老一
u2.test();//王老二
u3.test();//王老三
}
2.5 Spring依赖注入(Dependency Injection,DI)
2.5.1 构造器注入(上面我们已经写过)
2.5.2 Setter 注入
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .
bean | ref | idref | list | set | map | props | value | null
//常量注入
<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
</bean>
<bean id="addr" class="com.kuang.pojo.Address">
<property name="address" value="重庆"/>
</bean>
<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
<!--此处的值是一个引用,用ref-->
<property name="address" ref="addr"/>
</bean>
<bean id="student" class="com.kuang.pojo.Student">
<property name="name" value="小明"/>
<property name="address" ref="addr"/>
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
</bean>
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
<value>爬山</value>
</list>
</property>
<property name="card">
<map>
<entry key="中国邮政" value="456456456465456"/>
<entry key="建设" value="1456682255511"/>
</map>
</property>
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>COC</value>
</set>
</property>
<property name="wife"><null/></property>
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property
2.5.3 命名空间注入
<!--导入约束 : xmlns:p="http://www.springframework.org/schema/p"-->
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
<!--导入约束 : xmlns:c="http://www.springframework.org/schema/c"-->
<!--C(构造: Constructor)命名空间 , 实际是通过构造器注入-->
<bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
2.6 Bean的作用域
3、自动装配Bean
autowire属性
通过bean标签中的autowire属性,可以实现自动装配,底层也是通过set方法。
- byName
byName,查找类(User)中所有的set方法名(setDog),获得除去set后首字母小写的字符串(dog),然后在spring容器中匹配是否有一样id的bean。如果有,则将dog对象取出装配到user中。
<bean id="dog" class="com.xlj.pojo.Dog"/>
<bean id="cat" class="com.xlj.pojo.Cat"/>
<bean id="user" class="com.xlj.pojo.User" autowire="byName">
<property name="name" value="老王"/>
</bean>
- byType
byType,按照类型自动装配。需要保证容器中同一类型唯一。
<bean id="dog" class="com.xlj.pojo.Dog"/>
<bean id="cat" class="com.xlj.pojo.Cat"/>
<bean id="user" class="com.xlj.pojo.User" autowire="byType">
<property name="name" value="老王"/>
</bean>
使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
- 需要导入spring-aop包
- 需要在spring配置文件applicationContext.xml中引入context文件头
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
- 在applicationContext.xml中开启属性注解支持
<context:annotation-config/>
- 在需要装配的字段或者set方法上写一个@Autowired注解
@Autowired是按类型自动装配的,可以不需要set方法。
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
public class User {
private String name;
@Autowired
private Cat cat;
@Autowired
private Dog dog;
}
- 此时applicationContext.xml中只需要注册bean即可
<bean id="dog" class="com.xlj.pojo.Dog"/>
<bean id="cat" class="com.xlj.pojo.Cat"/>
<bean id="user" class="com.xlj.pojo.User" autowire="byType"/>
@Qualifier
@Qualifier注解是和@Autowired注解配合使用,加上@Qualifier注解后,可以让@Autowired注解通过byName的方式自动装配属性。
public class User {
private String name;
@Autowired
@Qualifier("cat1")
private Cat cat;
@Autowired
@Qualifier("dog1")
private Dog dog;
}
@Resource
- @Resource 默认是以变量属性名来匹配bean的id,如果指定了name属性,则是以定义的name属性来匹配。如果都没有匹配到,则通过byType的方式装配(只能有一个同类型的对象,且不能bean的id)
public class User {
private String name;
@Resource
private Cat cat;
@Resource(name = "dog2")
private Dog dog;
}
- @Resource的装配顺序
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
小结 @Autowired 和 @Resource 的区别
- 共同点
二者都可以写在字段和setter方法上。两者如果都写在字段上,那么就可以不需要再写setter方法。
- 不同点
@Autowired
1.是为Spring提供给的注解,需要导入spring-aop包。
2.按照byType的方式装配依赖对象。默认情况下依赖对象不能为null,如果允许为null值,可以设置rqeuired属性为false。
3.如果想通过byName的方式装配对象,可以搭配@Qualifier注解一起使用。
@Resource
1.是J2EE提供的,需要导入javax.annotation.Resouce包
2.默认是按照byName的方式装配依赖对象。
3.@Resource有两个重要的属性:name 和 type;如果设置了name值,则是按照byName的方式自动注入;如果设置了type的值,则是按照byType的方式自动注入;如果都没有设置,将通过反射机制使用byName的方式自动注入。
4、使用注解注册bean
之前我们都是通过bean标签来注册bean,但实际开发中都是通过注解来注册的。
Bean的实现
上面我们已经完成了导入了注解需要的依赖和引入context约束
- 配置扫描包下的注解
applicationContext.xml
<!--组件扫描-->
<context:component-scan base-package="com.xlj.pojo"/>
package com.xlj.pojo;
import org.springframework.stereotype.Component;
//相当于在配置文件中配置<bean id=user(类名首字母小写) class=com.xlj.pojo.User>
//可通过@Component的value属性自定义bean的id
@Component
public class User {
public String str = "222";
}
@Test
public void test04(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//bean的id默认是类名的首字母小写
User user = context.getBean("user", User.class);
System.out.println(user.str);//222
}
属性注入
使用@value注解注入
- 在属性字段上面使用@value,这样可以不需要写setter方法
@Component(value = "User")
public class User {
@Value("该笔")
private String name;
}
- 在setter方法上使用@value
@Component(value = "User")
public class User {
private String name;
@Value("该笔")
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
- 引用类型注入同样是使用@Autowired 和 @Resource注解
衍生注解
- @Component: 定义Bean, 不好归类时使用.
- @Repository: 定义dao层Bean
- @Service: 定义service层Bean
- @Controller: 定义controller层Bean
上面四个注解的作用都是定义Bean
5、动态代理
5.1 动态代理的两种方式
JDK动态代理:
利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLIB动态代理:
利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
区别:
JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
5.2 JDK动态代理
5.2.1 动态代理类
Java动态代理类位于Java.lang.reflect包下
一般主要涉及到以下两个类:接口InvocationHandler 和 类Proxy(该类即为动态代理类)
5.2.2 接口InvocationHandler
InvocationHandler 是代理实例的调用处理程序 实现的接口。
每个代理实例都具有一个关联的调用处理程序。
对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
5.2.3 类Proxy(动态代理类)
Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
创建某一接口 Foo 的代理:
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);
5.3 动态代理实现
5.3.1 创建一个接口
public interface Actor {
public void acting();
}
5.3.2 创建一个Actor实现类
public Class ActorImpl implements Actor {
@Override
public void acting() {
System.out.println("出演大明风华");
}
}
5.3.3 创建一个生成代理类
public Class Agent implements InvocationHandler {
//被代理的接口
private Actor actor;
//可以通过有参构造或者set方法注入被代理的类
public void setActor(Actor actor) {
this.actor = actor;
}
/**
* 在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
* @param proxy 调用该方法的代理实例
* @param method 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
* @param args 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
singAContract();//代理实例执行代理方法
return method.invoke(actor,args);//对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
}
//获取代理类的方法封装
public Object getAgent() {
return Proxy.newProxyInstance
(this.getClass().getClassLoader(),
actor.getClass().getInterfaces(),
this);
}
//代理类特有方法
public void singAContract() {
System.out.println("签约!");
}
}
5.3.4 测试
public void main(String[] args) {
Actor actor = new ActorImpl();
Agent agent = new Agent();
agent.setActor(actor);
Actor actorImpl = (Actor) agent.getAgent();
actorImpl.acting();
}
//输出 : 出演大明风华
6、Spring之面向切面(AOP)
6.1 什么是AOP?
百科
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
- 在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将一个个的对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,记录日志等公用操作处理的过程。
- 切面:简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
- AOP 底层:动态代理。如果是接口采用 JDK 动态代理,如果是类则采用CGLIB 方式实现动态代理。
6.2 AOP相关名词解释
-
Aspect(切面):横切关注点 被模块化 的特殊对象。可以看作是一个类。
-
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
-
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
-
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
-
Target(目标对象):织入 Advice 的目标对象.。
-
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。
6.3 Spring实现AOP
通过Advice实现