Spring
Spring就是一个轻量级的控制反转(IOC) 和切面编程(AOP)的框架
IOC的核心是工厂模式,AOP的核心是代理模式
IOC理论推导
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码,如果程序代码量十分大,那么修改一次的成本代价十分昂贵
控制反转
使用一个Set接口实现.已经发生了巨大的变化:
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
之前,程序是主动创建对象,控制权在遍好的程序本身
使用了set注入后,程序不在具有主动性,而是变成了被动的接受对象
这种思想,从本质上解决了问题,程序不用去管理对象的创建了,系统的耦合性大大降低,可以更加专心的在业务上的实现,这是IOC的原型
IOC本质:
控制反转,对象的创建由程序自己控制,控制反转之后将对象的创建交由第三方,获取依赖对象的方式反转了
依赖注入和控制反转(IOC)
一个Java应用程序,对象间往往是相互依赖的。
Spring框架的控制反转(Inversion of Control)就是为了解决这个问题的。
配置第一个Spring
1.导入依赖
<!-- 导入依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<!--spring-webmvc模块(也被称作Web-Servlet模块)包含Spring MVC框架。-->
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
</dependencies>
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">
<!-- 使用Spring来创建对象,在Spring这些都称为Bean -->
<!--
bean等于对象,id为变量名,class为对象路径
property相当于给对象中的属性设一个值
-->
<bean id="hello" class="com.spring.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
3.测试类
public class MyTest {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们要使用,直接去里面取出来就行
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.getStr());
}
}
配置初型IOC Spring
实体类:
public class MySqlDaoImpl implements UserDao {
public void show() {
System.out.println("这里是Mysql");
}
}
public class UserDaoImpl implements UserDao {
public void show() {
System.out.println("这里是User");
}
}
接口:
public interface UserService {
void getUserDao();
}
public interface UserDao {
void show();
}
服务层:
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUserDao() {
userDao.show();
}
}
配置文件:
<?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来创建对象,在Spring这些都称为Bean -->
<!--
bean等于对象,id为变量名,class为对象路径
property相当于给对象中的属性设一个值
-->
<bean id="MySqlImpl" class="com.spring.dao.MySqlDaoImpl"/>
<bean id="UserImpl" class="com.spring.dao.UserDaoImpl"/>
<bean id="service" class="com.spring.service.UserServiceImpl">
<!-- ref引入Spring容器创建好的对象(bean) -->
<property name="userDao" ref="MySqlImpl"/>
</bean>
</beans>
测试类
//获取Spring上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们要使用,直接去里面取出来就行
UserServiceImpl service = (UserServiceImpl) context.getBean("service");
service.getUserDao();
小结:
1.我们将建好的对象在Spring容器中使用bean重新创建
<bean id="MySqlImpl" class="com.spring.dao.MySqlDaoImpl"/>
2.我们在服务层IOC写入
private UserDao userDao;
//这里起到的作用就是将传递过来的对象赋值给userDao
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//类似于控制反转,我们就可以调用传递过来的对象来调用方法
public void getUserDao() {
userDao.show();
}
3.测试类来调配
//获取Spring上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们要使用,直接去里面取出来就行
//参数为配置文件bean标签的id
UserServiceImpl service = (UserServiceImpl) context.getBean("service");
service.getUserDao();
4.这样子我们修改调用的对象只需要在配置文件中更改ref的引入即可
<bean id="service" class="com.spring.service.UserServiceImpl">
<!-- ref引入Spring容器创建好的对象(bean) -->
<property name="userDao" ref="MySqlImpl"/>
</bean>
IOC创建对象的方式
1.使用无参构造创建对象(默认创建方式)
<bean id="user" class="com.spring.pojo.User"/>
2.假设我们要使用有参构造创建对象
2.1,下标赋值:
<bean id="user" class="com.spring.pojo.User">
<!-- index代表构造方法参数下标(从0开始) value为传递的值 -->
<constructor-arg index="0" value="参数值"/>
</bean>
2.2,参数赋值:
<bean id="user" class="com.spring.pojo.User">
<constructor-arg type="java.lang.String" value="类型值"/>
</bean>
2.3,参数名赋值:
<bean id="user_value" class="com.spring.pojo.User">
<constructor-arg name="name" value="参数名"/>
</bean>
总结:
实体类:
public class User {
private String name;
public User() {
this.name = "无参构造";
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
配置文件:
<!-- 构造方法创建对象(默认) -->
<bean id="user" class="com.spring.pojo.User"/>
<!-- 有参构造创建对象(通过下标) -->
<bean id="user_index" class="com.spring.pojo.User">
<constructor-arg index="0" value="构造方法参数值"/>
</bean>
<!-- 类型传值创建对象(引用类型用全类名) -->
<bean id="user_type" class="com.spring.pojo.User">
<constructor-arg type="java.lang.String" value="类型参数值"/>
</bean>
<!-- 参数名创建对象(对应属性名赋值即可) -->
<bean id="user_value" class="com.spring.pojo.User">
<constructor-arg name="name" value="参数名"/>
</bean>
测试类:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User bean = (User) context.getBean("user");
User bean_index = (User) context.getBean("user_index");
User bean_type = (User) context.getBean("user_type");
User bean_value = (User) context.getBean("user_value");
System.out.println("无参构造方法:"+bean.getName());
System.out.println("有参构造方法:"+bean_index.getName());
System.out.println("类型创建对象:"+bean_type.getName());
System.out.println("参数名赋值法:"+bean_value.getName());
运行结果:
无参构造方法:无参构造
有参构造方法:构造方法参数值
类型创建对象:类型参数值
参数名赋值法:参数名
结论:在配置文件加载的时候,容器中管理的对象就已经初始化了
Spring配置
别名:在调用时既可以用别名,也可以用原名
<!-- 别名,左边对应老bean id 右边对应新别名 -->
<alias name="user_index" alias="index"/>
User bean_index = (User) context.getBean("user_index");
User index = (User) context.getBean("index");
System.out.println("别名下标创建:"+index.getName());
System.out.println("有参构造方法:"+bean_index.getName());
运行结果:
别名下标创建:构造方法参数值
有参构造方法:构造方法参数值
bean:
<!-- id:唯一标识,相当于我们创建的对象名 | class为全限定名 :包名+类名 | name:也是别名,而且可以取多个 -->
<bean id="name" class="com.spring.pojo.User" name="n1,n2">
<!-- name属性可以通过","取多个别名用空格同理,使用";"也可以 -->
</bean>
improt:
一般用于团队开发使用,可以将多个配置文件导入合并为一个
applicationContext.xml
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
依赖注入:
构造器注入
<!-- 构造方法创建对象(默认) -->
<bean id="user" class="com.spring.pojo.User"/>
<!-- 有参构造创建对象(通过下标) -->
<bean id="user_index" class="com.spring.pojo.User">
<constructor-arg index="0" value="构造方法参数值"/>
</bean>
<!-- 类型传值创建对象(引用类型用全类名) -->
<bean id="user_type" class="com.spring.pojo.User">
<constructor-arg type="java.lang.String" value="类型参数值"/>
</bean>
<!-- 参数名创建对象(对应属性名赋值即可) -->
<bean id="user_value" class="com.spring.pojo.User">
<constructor-arg name="name" value="参数名"/>
</bean>
Set方式注入 [重点]
依赖注入:Set注入!
依赖:bean对象的创建依赖于容器
注入:bean对象中的所有属性由容器来注入
【环境搭配】
实体类:
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private Properties info;
private String wife;
}
配置文件:
<bean id="address" class="com.spring.pojo.Address">
<property name="address" value="地址"/>
</bean>
<bean id="student" class="com.spring.pojo.Student">
<!-- 1: 普通注入 -->
<property name="name" value="乱杀!"/>
<!-- 2: Bean注入 ref -->
<property name="address" ref="address"/>
<!-- 3: 数组注入 -->
<property name="books">
<array>
<value>数组</value>
<value>对象数组</value>
<value>二维数组</value>
</array>
</property>
<!-- 4: List注入 -->
<property name="hobbys">
<list>
<value>list</value>
</list>
</property>
<!-- 5: Map注入 -->
<property name="card">
<map>
<entry key="键" value="值"/>
</map>
</property>
<!-- 6:Set注入 -->
<property name="games">
<set>
<value>Set</value>
</set>
</property>
<!-- 8:空值注入 -->
<property name="wife">
<null/>
</property>
<!-- 9:Properties注入 -->
<property name="info">
<props>
<prop key="键">值</prop>
</props>
</property>
</bean>
测试类:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println("属性:"+student.getName());
System.out.println("对象:"+student.getAddress().getAddress());
System.out.println("数组:"+student.getBooks()[1]);
System.out.println("list:"+student.getHobbys().toString());
System.out.println("map:"+student.getCard().toString());
System.out.println("set:"+student.getGames().toString());
System.out.println("Properties:"+student.getInfo().toString());
System.out.println("空值:"+student.getWife());
运行结果:
属性:乱杀!
对象:地址
数组:对象数组
list:[list]
map:{键=值}
set:[Set]
Properties:{键=值}
空值:null
拓展方式注入
【P命名空间注入】
P约束引入:
xmlns:p = "http://www.springframework.org/schema/p"
使用方式:
<!-- P命名空间注入,可以直接注入属性值 -->
<bean id="user" class="com.spring.pojo.User" p:name="张三" p:age="18" />
【C命名空间注入】
C约束引入:
xmlns:c = "http://www.springframework.org/schema/c"
<!-- C命名空间注入 构造器注入:construct-args -->
<!-- c:_0代表下标,也可以直接:属性名来注入 -->
<bean id="user_2" class="com.spring.pojo.User" c:_0="" c:_1=""/>
注意点:P和C命名空间不可以直接使用,需要引入xmlns约束
bean作用域
1.单例模式(Spring默认机制)
<bean id="user_2" class="com.spring.pojo.User" c:_0="" c:_1="" scope="singleton"/>
2.原型模式:每次从容器中get的时候,都会产生一个新对象
<bean id="user_2" class="com.spring.pojo.User" c:_0="" c:_1="" scope="prototype"/>
3.其余的 request,session,application,这些个只能在web开发中使用到
Bean的自动装配
自动装配是Spring满足bean依赖的一种方式
Spring会在上下文中自动寻找,并自动给bean装配属性
在Spring中有三种装配的方式:
1.在xml中显示的配置
2.在java中显示的配置
3.隐式的自动装配bean【重要】
自动装配(autowire)
byName
<!-- 自动装配 -->
<bean id="people_self" class="com.spring.pojo.People" autowire="byName">
<property name="name" value="byName自动装配"/>
</bean>
autowire:byName元素会自动在容器上下文中查找和自己对象set方法后面的值对应的beanid
ByType
<!-- 自动装配 -->
<bean id="people_self" class="com.spring.pojo.People" autowire="byType">
<property name="name" value="byType自动装配"/>
</bean>
autowire:byType元素会自动在容器上下文中查找和自己对象属性相同的beanid(弊端:保证这个bean类型全局唯一,优点:可以省略id命名)
小结:
-
byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
-
byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!
使用注解实现自动装配
要使用注解须知:
1.导入约束 context约束
2.配置注解的支持:context:annotation-config/
<?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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解支持 -->
<context:annotation-config/>
</beans>
@Autowired
加在对象属性上即可,也可以在set方式上使用
使用@Autowired我们可以不用编写set方法了,前提是你这个自动装配的属性在IOC(Spring)容器中存在,且符合名字byName
@Autowired:
- 当注入在IOC容器中该类型只有一个时,就通过byType进行装配
- 当注入容器存在同一个类型的对象时,就根据byName进行装配
@Autowired
private Cat cat;
@Autowired
@Qualifier (value = "dog")
private Dog dog;
private String name;
@Qualifier自动装配的环境比较复杂,自动装配无法通过一个注解完成的时候,我们可以使用@Qualifier( value = “xxx”)去配和@Autowired的使用,指定一个唯一的bean对象注入
@Resource
@Resource (name = "cat")
private Cat cat;
@Resource
private Dog dog;
private String name;
小结:
@Resource和@Autowired的区别
- 都是用来自动装配的,都可以放在这段上
- @Autowired 通过byType的方式实现 【常用】
- @Resource 默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!
- 执行顺序不同:@Autowired 通过byType的方式实现。@Resource 默认通过byName的方式实现。
使用注解开发
1.bean
-
1.1:开启注解支持,引入扫描包
-
<!-- 开启注解支持 --> <!-- 创建对象 指定要扫描的包,这个包下的注解会生效 --> <context:component-scan base-package="com.spring.pojo"/>
-
1.2:在扫描包下的类上加上注解:@Component 即可
-
@Component //等价于在容器中注册了一个bean <bean id="user" class="com.spring.pojo.User"> public class User {}
2.属性如何注入
-
@Value("张三") //等价于:<property name="name" value="张三"/> private String name;
3.衍生的注解
@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层
- dao 【@Repository】
- service【@Service】
- controller(Servlet)【@Controller】
- 这四个注解都是一样的,都是代表将某个类注册到Spring,装配Bean
4.自动装配配置
@Autowired
@Autowired
private Cat cat;
@Autowired
@Qualifier (value = "dog")
private Dog dog;
private String name;
@Resource
@Resource (name = "cat")
private Cat cat;
@Resource
private Dog dog;
private String name;
5.作用域
@Scope("prototype") //单例模式:singleton 原型模式:prototype
public class User {}
6.小结
xml与注解
- xml更加万能,适用于各种场合,维护简单方便
- 注解不是自己的类无法使用,维护相对复杂
最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入
- 我们在使用的过程,只需要注意一个问题,必须让注解生效,就需要开启注解的支持
使用Java的方式配置Spring(新特性)
-
我们现在要完全不使用Spring的xml配置了,全权交给Java来做
-
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能
配置类(config 相当于Spring的applicationContext.xml容器):
@Configuration //这个也会被Spring容器托管,注册在容器中,因为他本身就是一个@Component 代表这是一个配置类,就和我们之前看到的Spring的applicationContext.xml一样
@ComponentScan("com.spring.pojo")//扫描pojo实体类包
public class HonMaoConfig {
/**
* 注册一个bean,就相当于我们在Spring容器中写入一个bean标签
* 这个方法的名字,就相当于bean标签的id属性
* 这个方法的返回值,就相当于bean标签的id属性
*/
@Bean
public User getUser(){
return new User();//就是返回要注入到bean的对象
}
}
实体类:
@Component //等价于在容器中注册了一个bean <bean id="user" class="com.spring.pojo.User">
public class User {
@Value("HonMao") //给name属性赋值
private String name;
}
测试类(AnnotationConfigApplicationContext):
public class MyTest {
@Test
public void getConfig(){
/**如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器
通过配置类的class对象加载*/
ApplicationContext context = new AnnotationConfigApplicationContext(HonMaoConfig.class);
User getUser = context.getBean("getUser", User.class);//加载
System.out.println(getUser.getName());
}
}
这种纯Java的配置方式,在SpringBoot随处可见
代理模式
为什么学习静态代理模式?因为这是SpringAOP的底层!【SpringAOP 和 SpringMVC】
代理的分类:
静态代理
静态代理,代理相当于一个枢纽,举例,我们在生活中去看电影,首先我们需要到电影院或者视频APP,而电影院和视频APP就在看电影这个流程中扮演者代理的角色,首先是剧组将电影拍摄完成再发布给电影院和APP,而我们并不是直接通过剧组去看电影,而是通过第三方(代理),电影院(视频APP),虽然不是通过剧组直接看电影,但看电影的目的同样达到了。
而我们如何在代码中实现,例如写一个中介租房:
影片胶卷接口:
//影片胶卷
public interface ShePian {
void shoot();
}
剧组实现胶卷接口:
//剧组
public class JuZu implements ShePian{
public void shoot() {
System.out.println("唐人街电影开始播放!");
}
}
电影院类:
//电影院
public class DianYingYuan {
private JuZu juzu;
//日志文件
public DianYingYuan(JuZu juzu) {
log("唐人街");
this.juzu = juzu;
}
public JuZu getJuzu() {
return juzu;
}
public void log(String msg){//电影院添加的功能
System.out.println("荧幕开始放映:"+msg+"预告片");
}
}
观众:
//观众
public class GuanZho {
public static void main(String[] args) {
DianYingYuan ying = new DianYingYuan(new JuZu());//告诉电影院要看什么电影
ying.getJuzu().shoot();//开始放映
}
}
运行结果:
荧幕开始放映:唐人街预告片
唐人街电影开始播放!
这就是静态代理,观众的目的是为了看电影,而电影的原头在剧组,虽然借助了电影院,但目的并没有发生差异,甚至电影院还有其他功能帮助观众提升影视质量,例如上述的播放预告片
代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公众的业务
- 公共也就交给代理角色!实现了业务分工
- 公共业务发展拓展的时候,方便集中管理
代理的缺点:
- 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低
动态代理★
- 动态代理和静态代理角色一样
- 动态代理的代理是动态类的,不是我们直接写的
- 基于代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理【我们使用的】
- 基于类:cglib
- java字节码实现:javasist
需要了解两个类:Proxy,InvocationHandler:调用处理程序
代理的概念:你只需要将需要处理的数据给到动态代理,代理帮你完成各项工作,最终反馈给你想要的成品,例如一条sql语句,丢入到代理当中,你只需要告诉代理你需要执行的sql操作和表明即可,剩下的执行sql语句通过代理帮你完成
AOP
1.导入依赖包:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
方式一:使用原生Spring API接口
接口:
public interface UserService {
void add();
}
实现接口:
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加用户");
}
}
切面代码:
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue);
}
}
public class Log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//objects :参数
//o :目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
//对象的xxx方法被执行了
System.out.println(o.getClass().getName()+"的"+method.getName());
}
}
配置文件:
<!-- 注册bean -->
<bean id="UserService" class="com.spring.service.UserServiceImpl"/>
<bean id="log" class="com.spring.log.Log"/>
<bean id="afterLog" class="com.spring.log.AfterLog"/>
<!-- 方式一:使用原生Spring API接口 -->
<!-- 配置aop:需要导入aop的约束 -->
<aop:config>
<!-- 切入点: expression:表达式 execution(要执行的位置) -->
<aop:pointcut id="pointcut" expression="execution(* com.spring.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增加 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
代理代码:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理 代理的是接口
UserService userService = (UserService) context.getBean("UserService");
userService.add();
}
}