目录
一,什么是Spring?
百度百科这样形容Spring:Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC
总之:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架。
二,控制反转(IOC)
2.1 控制反转是什么?
- 控制反转是一种思想。
- 控制反转是为了降低程序耦合度,提高扩展度,达到OCP原则,达到DIP原则。
- 控制反转就是把对象的控制权交出去,交给容器(这里是Spring容器)进行管理
2.2 如何实现控制反转?
- DI(Dependency Injection):依赖注入
- 依赖注入通常有两种方法:set注入和构造方法注入
2.3 依赖注入
- 构造方法注入
- 通过构造方法来给属性赋值
- set方法注入
- set方法注入,是基于set方法实现的,底层会通过反射机制调用该属性的set方法给属性进行赋值,这种方法要求该属性必须提供对外的set方法。
下面分别演示两种注入方法:
先创建一个Maven工程:
点击下一步并命名项目:
然后无脑下一步即可。
这时来准备我们的依赖,因为是maven工程,所以这里只给出坐标
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
这里引入spring-context后会关联引入一些其他的jar包,如下图:
然后就可以写代码了。
构造方法注入是spring容器通过构造方法来给属性赋值、所以类必须有构造方法
先写一个实体类:
package com.hkd.Bean;
public class user {
String name;
int age;
public user() {
}
public user(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
写好实体类后就可以 编写Spring配置文件了。
在IDEA中文件的目录结构如下:
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="user" class="com.hkd.Bean.user">
<constructor-arg index="0" value="张三"></constructor-arg>
<constructor-arg index="1" value="20"></constructor-arg>
</bean>
</beans>
运行后可看到结果:
这里constructor-arg标签中的index属性指的是,参数在构造方法中的顺序。
除了index可以指定参数外也可用参数的名称来指定:
如果需要的参数是引用类型则按照下面写法:
2.3.1 set注入简单类型
简单类型包括:
- 基本本数据类型
- 基本数据类型对应的包装类
- String或其他的CharSequence⼦类
- Number⼦类
- Date⼦类
- Enum⼦类
- URI
- URL
- Temporal⼦类
- Locale
- Class
- 另外还包括以上简单值类型对应的数组类型
简单类型注入的代码大致为:
<bean id="" class="">
<property name="" value=""></property>
</bean>
在这里有几个标签:
- bean:具有id属性和class属性,id是spring容器帮忙创建的对象的名称这里的user等同于t通过user user = new user();创建的user对象,class的值表示要创建的对象的类路径,spring容器拿到这个路径后会通过反射机制创建该类的对象。
- property:property单词本身就有属性性质的意思,这里指的也是对象中的属性,其name属性值为对象的属性名,value值就是要给对象中属性赋予的值。
先创建一个实体类,提供set和构造方法
package com.hkd.Bean;
public class user {
String name ;
int age;
public user() {
}
public user(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", 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="user" class="com.hkd.Bean.user">
<property name="name" value="张三"></property>
<property name="age" value="20"></property>
</bean>
</beans>
到这里准备工作完成可以进行set注入的测试了:
写一个测试类如下:
package myTest;
import com.hkd.Bean.user;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DiTest {
@Test
public void TestSetDI(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
user user = ioc.getBean("user", user.class);
System.out.println(user);
}
}
控制台可看到结果为:
简单类型注入成功。
2.3.2 注入Bean
如果在一个对象在另一个对象中作为一个属性,那么在注入的时候就不能用和简单类型一样的方法进行注入了,此时的注入格式如下:
<bean id="" class="">
<property name="" ref=""></property>
</bean>
这里可以看到相比于注入简单类型,注入Bean知识将value=""换为了ref="",此时ref=""中的值应填写为一个Bean的id就是spring容器管理的一个对象的名称。
如在刚才的程序中添加一个Vip类,类中有一个user属性,则可以按照一下方式写:
VIP类:
package com.hkd.Bean;
public class Vip {
int id;
int balance;
user user;
public Vip() {
}
public Vip(int id, int balance) {
this.id = id;
this.balance = balance;
}
public void setUser(com.hkd.Bean.user user) {
this.user = user;
}
public void setId(int id) {
this.id = id;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Vip{" +
"id=" + id +
", balance=" + balance +
", user=" + user +
'}';
}
}
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="user1" class="com.hkd.Bean.user">
<property name="name" value="张三"></property>
<property name="age" value="20"></property>
</bean>
<bean id="vip" class="com.hkd.Bean.Vip">
<property name="id" value="1"></property>
<property name="balance" value="123"></property>
<property name="user" ref="user1"></property>
</bean>
</beans>
在控制台可以看到user1对象被成功注入到了Vip了中。
除了这种方式还有一种注入Bean的方式:
2.3.3 注入数组
当输入的数组类型是简单类型时:
<bean id="user" class="com.hkd.Bean.user">
<property name="bobby">
<array>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</array>
</property>
</bean>
非简单类型时:
<bean id="user" class="com.hkd.Bean.user">
<property name="bobby">
<array>
<ref bean="爱好1"></ref>
<ref bean="爱好2"></ref>
<ref bean="爱好3"></ref>
</array>
</property>
</bean>
2.3.4 注入List集合
<bean id="user" class="com.hkd.Bean.user">
<property name="bobby">
<list>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</list>
</property>
</bean>
注:当List集合中是简单类型时使用value标签反之使用ref标签
2.3.5 注入Set集合
<bean id="user" class="com.hkd.Bean.user">
<property name="bobby">
<set>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</set>
</property>
</bean>
注:当Set集合中是简单类型时使用value标签反之使用ref标签
2.3.6 注入Map集合
<bean id="user" class="com.hkd.Bean.user">
<property name="bobby">
<map>
<entry key="1" value="抽烟"></entry>
<entry key="2" value="喝酒"></entry>
<entry key="3" value="烫头"></entry>
</map>
</property>
</bean>
要点:
- 如果key是非简单类型,则使用key-ref
- 如果value是非简单类型,则使用value-ref
2.3.7 注入Properties
<bean id="dataSource" class="com.hkd.Util.DataSource">
<property name="properties">
<props>
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/spring</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
2.4 Bean的作用域
Spring创建的对象默认是单例的。
如果想让对象是多例的可以在指定scope的属性值为prototype,这样每调用一次getBean方法都会新创建一个对象。
默认情况下scope的值为singleton,即为单例
2.5 IOC的注解式开发
2.5.1声明Bean的注解常见的有四个
- @Component
- @Controller (用于表示层)
- @Service (用于业务层)
- @Repository (用于持久层)
这四个注解中@Controller,@Service,@Repository是@Component的别名, 四个注解用法相同,对程序来说这三个都是一样的,那么为什么还要搞三个别名呢--这是给程序员看的,为了提高高代码的可读性。
这里拿@Component注解举例,其他三个用法相同:
@Component注解写在类上
package com.hkd.Bean;
import org.springframework.stereotype.Component;
@Component(value = "person")
public class preson {
String name;
}
这里的value值就是spring容器在创建对象时的对象名,可以忽略不写,此时对象名为首字母小写的类名
当然仅仅在类中写上注解还不行,spring容器怎么样才能发现,或者说spring容器怎么判断程序员是否写了注解呢?
这时要在spring的核心配置文件中告诉spring容器去哪些包中找带有注解的类,即需要添加包扫描,spring容器会到对应的包中检查是否写了注解,如果写了注解,那就创建对象。
如果是多个包有两种解决⽅案:
- 第⼀种:在配置⽂件中指定多个包,⽤逗号隔开。
- 第⼆种:指定多个包的共同⽗包。
此时需要添加context命名空间
至此对象就可以成功创建了,那有了对象要怎么给属性赋值呢?
2.5.2 spring给出四个注解用来给属性赋值:
- @Value
- @Autowried
- @Qualifier
- @Resource
@Value : 当属性的类型是简单类型时使用@Value进行注入,
@Value可以写在属性上,构造方法里,set方法上,且当@Value写在属性上时可以不提供set方法
@Autowried :当属性的类型是引用类型时使用@Autowried注入(按类型注入)
@Autowried注解可以标注在哪⾥?
- 构造⽅法上
- ⽅法上
- 形参上
- 属性上
- 注解上
该注解有⼀个required属性,默认值是true,表示在注⼊的时候要求被注⼊的Bean必须是 存在的,如果不存在则报错。如果required属性设置为false,表示注⼊的Bean存在或者不存在都没 关系,存在的话就注⼊,不存在的话,也不报错。
因为@Autowried是按类型注入的,所以如果在spring容器中有多个相同类型的对象的话,@Autowried在注入时就会发生混乱,此时程序会报错。
这里的bigBoss和littleBoss是Boss的实现类
当我们进行注入的时候就会报错,发现了两个对象。
有没有办法解决这个问题呢?
肯定是有的,还有一个@Qualifier,这两个注解进行搭配使用就可以根据名称进行注入。
我们将上面代码稍加改动
此时再运行代码就不会出错且在控制台输出:
@Resource:@Resource也可以完成⾮简单类型注⼊。
那它和@Autowired注解有什么区别?
- Resource注解是JDK扩展包中的,也就是说属于JDK的⼀部分。所以该注解是标准注解,更加具 有通⽤性。@Autowired注解是Spring框架⾃⼰的。
- @Resource注解默认根据名称装配byName,未指定name时,使⽤属性名作为name。通过name 找不到的话会⾃动启动通过类型byType装配。 @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解⼀起 ⽤。
- @Resource注解⽤在属性上、setter⽅法上。 @Autowired注解⽤在属性上、setter⽅法上、构造⽅法上、构造⽅法参数上。
- @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引⼊依赖:【如果是JDK8的话不需 要额外引⼊依赖。⾼于JDK11或低于JDK8需要引⼊以下依赖。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2.6 IOC的全注解开发
IOC的全注解开发就是不再写XML配置文件了,而是写一个类来代替XML配置文件
package com.hkd.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.hkd.Bean")
public class SpringConfig {
}
这时候我们不再创建ClassPathXmlApplicationContext对象了
除了创建的对象不一样之外,其余操作与XML写法一致。
三,AOP
3.1:AOP中的名词解释
- 连接点 Joinpoint :在程序的整个执⾏流程中,可以织⼊切⾯的位置。⽅法的执⾏前后,异常抛出之后等位置。
- 切点 Pointcut: 在程序执⾏流程中,真正织⼊切⾯的⽅法。(⼀个切点对应多个连接点)。
- 通知 Advice:通知⼜叫增强,就是具体你要织⼊的代码。 通知包括: 前置通知 后置通知 环绕通知 异常通知 最终通知。
- 切⾯ Aspect: 切点 + 通知就是切⾯。
- 织⼊ Weaving: 把通知应⽤到⽬标对象上的过程。
- 代理对象 Proxy: ⼀个⽬标对象被织⼊通知后产⽣的新对象。
- ⽬标对象 Target: 被织⼊通知的对象。
3.2切入点表达式
什么是切入点表达式:就是表明你想要在哪个方法上织入切面(添加代码,增强功能)
execution([访问控制权限修饰符] 返回值类型 [全限定类名]⽅法名(形式参数列表) [异常])
- 访问控制权限修饰符: 可选项。 没写,就是4个权限都包括。 写public就表示只包括公开的⽅法。
- 返回值类型: 必填项。 * 表示返回值类型任意。
- 全限定类名: 可选项。 两个点“..”代表当前包以及⼦包下的所有类。 省略时表示所有的类。
- ⽅法名: 必填项。 *表示所有⽅法。 set*表示所有的set⽅法。
- 形式参数列表: 必填项 。表示没有参数的⽅法 (..) 参数类型和个数随意的⽅法 (*) 只有⼀个参数的⽅法 (*, String) 第⼀个参数类型随意,第⼆个参数是String的。
- 异常: 可选项。 省略时表示任意异常类型。
3.3 基于AspectJ的AOP注解式开发
先写一个目标类:
package com.hkd.AspectJ.Service;
import org.springframework.stereotype.Service;
@Service
public class orderService {
public void Buy(){
System.out.println("买东西...");
}
}
再写一个切面类:
package com.hkd.AspectJ.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class myAspect {
@Before("execution(* com.hkd.AspectJ.Service.*.*(..))")
public void myBefore(){
System.out.println("前置通知执行了...");
}
}
Spring.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
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:component-scan base-package="com.hkd.AspectJ"></context:component-scan>
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>
这里看到在spring.xml文件中多了这样的一行代码
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
这句的作用是开启aop的自动代理添加完这句代码后所有带有@Aspect注解的都会生成一个代理对象
测试:
3.4 通知类型
通知类型包括:
- 前置通知:@Before ⽬标⽅法执⾏之前的通知
- 后置通知:@AfterReturning ⽬标⽅法执⾏之后的通知
- 环绕通知:@Around ⽬标⽅法之前添加通知,同时⽬标⽅法执⾏之后添加通知。
- 异常通知:@AfterThrowing 发⽣异常之后执⾏的通知
- 最终通知:@After 放在finally语句块中的通知
@Pointut注解:可以写在一个方法上作为一个切点表达式
@Pointcut("execution(* com.hkd.AspectJ.Service.*.*(..))")
public void pointCut(){}
此时pointCut()等同于execution(* com.hkd.AspectJ.Service.*.*(..))
3.4.1 通知的执行顺序
此时我们来看无异常时通知的执行顺序:
package com.hkd.AspectJ.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class myAspect {
@Pointcut("execution(* com.hkd.AspectJ.Service.*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void myBefore(){
System.out.println("前置通知执行了...");
}
@AfterReturning("pointCut()")
public void myAfterReturning(){
System.out.println("后置通知执行了...");
}
@Around("pointCut()")
public void myAround(ProceedingJoinPoint point) throws Throwable{
System.out.println("环绕通知开始...");
point.proceed();
System.out.println("环绕通知结束...");
}
@After("pointCut()")
public void myAfter(){
System.out.println("最终通知执行了...");
}
}
测试结果:
由此可以看出最先执行的是环绕通知,然后是前置通知,在执行目标方法,紧接着后置通知,最终通知。
当有异常时:
我们在切面类中添加异常通知,这里不做演示
在Service类中添加一条异常语句如下图:
此时再进行测试:
从结果中我们可以看到,程序到达错误语句时报错且除了最终通知外后面的通知不再执行, 由此可见最终通知的作用类似finally代码块,这里可以写一些必须执行的代码。
3.4.2 有多个切面时,切面的执行顺序
要搞清楚切面的执行顺序,我们先来看一个注解@Order,@Order注解可以指定一个整形的数字,数字值越小,优先级越高。
我们再编写一个切面类aspect2,aspect和aspect2:分别如下:
除了@Order值不同外,其余都一致。
测试可得到结果:
由此我们可以清楚地看到切面的执行顺序。
3.5全注解式开发AOP
与全注解开发IOC时相同,同样创建一个类作为spring容器的配置文件,但是AOP要多添加一条自动代理的语句,添加了这条语句spring才能自动的帮你创建切面类:
只需要添加一条@EnableAspectJAutoProxy就可以实现自动代理,是不是很简单
这里简单的提一下,@EnableAspectJAutoProxy有一个ProxyTargetClass属性,为布尔类型,当其值为true的时候,表示使用cglib动态代理,为false时使用JDK动态代理,JDk动态代理只能代理接口,cglib动态代理既能代理接口又能代理了类,当值为false时,如果没有接口则还是会代理类,在Spring5中AOP的默认代理方式是JDK动态代理。在XML配置中自动代理的标签<aop:aspectj-autoproxy >也有这样的属性
3.6 AOP的XML开发(了解)
直接编写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: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">
<!--切面类,上文有源码,但是去掉了注解-->
<bean id="myAspect" class="com.hkd.AspectJ.aspect.myAspect"></bean>
<bean id="orderService" class="com.hkd.AspectJ.Service.orderService"></bean>
<!-- AOP配置-->
<aop:config>
<!-- 配置切点-->
<aop:pointcut id="pointCut" expression="execution(* com.hkd.AspectJ.Service.*.*(..))"/>
<!-- 配置切面:切点+通知-->
<!-- 这里ref的值为上面创建的切面类 -->
<aop:aspect ref="myAspect">
<!-- method属性的值为切面类中的方法-->
<aop:before method="myBefore" pointcut-ref="pointCut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
进行测试:
成功得到结果:
那么到这里对IOC的AOP的大致内容便结束了,这只是我个人的一篇学习笔记,如果有错误,还希望大家能够指正,我们一起进步!!