一、Spring简介
Spring的优点
- Spring是一个开源免费的框架、容器
- Spring是一个轻量级的框架,非侵入式的
- 控制反转IOC、面向切面AOP
- 支持事务
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器
二、IOC
2.1 IOC本质
控制反转IOC,是一种设计思想,DI(依赖注入)是实现IOC的一种方式
通俗点来说就是原本你需要自己创建对象(new),现在交给Spring来管理。你只需要告诉Spring需要什么对象,Spring就会帮你创建并注入。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方式是依赖注入(DI)
2.2 IOC创建对象方式
2.2.1 通过无参构造方法来创建
代码示例
1、创建user类
public class User {
private String name;
public User() {
System.out.println("user无参构造方法");
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
2、编辑beans.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="user" class="com.kuang.pojo.User">
<property name="name" value="kuangshen"/>
</bean>
</beans>
3、测试类
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
user.show();
我们会发现在我们调用
show()
方法的时候,User对象已经通过无参构造初始化了。
注意:当我们的方法中没有无参构造方法会报错
2.2.2通过有参构造方法来创建
在user
类中添加有参构造方法
public User(String name) {
this.name=name;
}
有参构造我们有三种方式来编写(在beans.xml中)
-
根据下标
<bean id="user" class="com.kuang.pojo.User"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="kuangshen2"/> </bean>
-
根据参数名字
<bean id="user" class="com.kuang.pojo.User"> <!-- name指参数名 --> <constructor-arg name="name" value="kuangshen2"/> </bean>
-
根据参数类型
<bean id="user" class="com.kuang.pojo.User"> <constructor-arg type="java.lang.String" value="kuangshen2"/> </bean>
三、Spring配置
3.1 别名
通过alias
设置别名,为bean设置别名,可以设置多个别名
<alias name="user" alias="LTY"></alias>
3.2 Bean的配置
id:bean的唯一标识,相当于我们的对象名
class:bean对象所对应的全限定名
name:也是别名,可以同时取多个别名
3.3 import
一般用于团队开发使用,可以将两个或多个不同的配置文件,合并为一个配置文件
<import resource="beans.xml"/>
<import resource="beans1.xml"/>
四、依赖注入(DI)
依赖:指bean对象的创建依赖于容器
注入:指bean对象所依赖的资源,由容器来设置和装配
4.1 构造器注入
我们上面的案例就是构造器注入(构造器注入必须要有有参构造,可以没有无参构造)
4.2 set注入(重点)
要求被注入的属性,必须要有set方法
准备代码
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
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 String wife;
private Properties info;
public void setName(String name) {
this.name = name;
}
public void setAddress(Address address) {
this.address = address;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setWife(String wife) {
this.wife = wife;
}
public void setInfo(Properties info) {
this.info = info;
}
public void show(){
System.out.println("name="+ name
+ ",address="+ address.getAddress()
+ ",books="
);
for (String book:books){
System.out.print("<<"+book+">>\t");
}
System.out.println("\n爱好:"+hobbys);
System.out.println("card:"+card);
System.out.println("games:"+games);
System.out.println("wife:"+wife);
System.out.println("info:"+info);
}
}
-
常量注入
<bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> </bean>
-
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="小明"/> <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>
-
List注入
<property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>爬山</value> </list> </property> 1234567
-
Map注入
<property name="card"> <map> <entry key="中国邮政" value="456456456465456"/> <entry key="建设" value="1456682255511"/> </map> </property> 123456
-
set注入
<property name="games"> <set> <value>LOL</value> <value>BOB</value> <value>COC</value> </set> </property> 1234567
-
Null注入
<property name="wife"><null/></property>
-
Properties注入
<property name="info"> <props> <prop key="学号">20190604</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>
4.3 p命名和c命名
-
P命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p" <!--P(属性: properties)命名空间 , 直接注入属性--> <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
-
c 命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:c="http://www.springframework.org/schema/c" <!--C(构造: Constructor)命名空间 , 使用构造器注入--> <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
4.4 Bean的作用域
那些组成应用程序的主体及由Spring IOC容器所管理的对象,被称之为bean。
bean就是由IOC容器初始化、装配及管理的对象。
singleton(单例模式)
当一个bean的作用域为
singleton
时,那么Spring IOC容器只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则会返回bean的同一实例。
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
测试
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user==user2);//返回true
Prototype(原型模式)
当一个bean的作用域为
Prototype
,表示一个bean定义对应多个对象实例。Prototype
作用域的bean会导致在每次对该bean请求时都会创建一个新的bean实例。
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
Request
当一个bean的作用域为
Request
时,表示在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
五、Bean的自动装配
Spring中bean有三种装配机制,分别是:
- 在xml中显示配置
- 在java中显示配置
- 隐式的bean发现机制和自动装配
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描:spring会自动发现应用上下文中所创建的bean
- 自动装配:soring自动满足bean之间的依赖,也就是我们说的IOC/DI
5.1 byName(按名称自动装配)
修改bean配置,增加一个属性autowire="byName"
<bean id="user" class="com.kuang.pojo.User" autowire="byName">
<property name="str" value="qinjiang"/>
</bean>
- 将查找其类中所有的set方法名,例如setCat,获得将set去点并且首字母小写的字符串,即cat。
- 去spring容器中寻找是否有此字符串名称id的对象
- 如果有,就注入输出;如果没有,就报空指针异常
5.2 byType(按类型自动装配)
使用byType首先要保证:同一类型的对象,在spring容器中唯一;如果不唯一,会报异常。
<bean id="user" class="com.kuang.pojo.User" autowire="byType">
<property name="str" value="qinjiang"/>
</bean>
5.3 使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
-
在spring配置文件中引入
context
文件头xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
-
开启属性注解支持
<context:annotation-config/>
@Autowired
- @Autowired是按类型装配的,不支持id匹配
- 需要导入spring-aop的包
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
@Qualifier
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
@Resourse
- @Resource如果有指定的neme属性,先按该属性进行byName方式查找装配
- 其次再进行默认的byName方式进行装配
- 如果以上都不成功,则按byType进行装配
- 都不成功,报异常
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
@Autowired与@Resource的区别
- @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如果想要使用名称装配可以结合@Qualifier注解进行使用。
- @Resource默认按名称装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。
六、使用注解开发
在spring4之后,想要使用注解形式,必须引入aop的包
在配置文件中,还要引入一个context约束
6.1 Bean的实现
@Component(“xxxx”)
-
配置扫描哪些包下的注解
<!--指定注解扫描包--> <context:component-scan base-package="com.kuang.pojo"/>
-
在指定包下编写类。增加注解
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { public String name = "秦疆"; }
6.2 属性注入
@value
-
可以不用提供set方法,直接在对象名上添加@value(“值”)
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { @Value("秦疆") // 相当于配置文件中 <property name="name" value="秦疆"/> public String name; }
-
如果提供了set方法,在set方法上添加@value(“值”)
@Component("user") public class User { public String name; @Value("秦疆") public void setName(String name) { this.name = name; } }
6.3 衍生注解
@Component三个衍生注解
为了更好的进行分层,spring可以使用其他三个注解,功能一样。
- @Controller:controller层
- @Service:service层
- @Repository:dao层
6.4 作用域
@scope
- singleton:默认的,spring会采用单例模式创建这个对象。关闭工程,所有对象都会销毁
- prototype:多例模式。关闭工厂,所有对象不会销毁。内部的垃圾回收机制会回收。
@Controller("user")
@Scope("prototype")
public class User {
@Value("秦疆")
public String name;
}
6.4 小结
XML与注解比较
- XML可以使用任何场景,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
XML与注解整合开发(推荐):
-
xml管理bean
-
注解完成属性注入
-
使用过程中,可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
七、基于Java类进行配置
@Configuration
-
新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类 public class MyConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public Dog dog(){ return new Dog(); } }
导入其他配置如何做呢?
@Import
-
我们再编写一个配置类
@Configuration //代表这是一个配置类 public class MyConfig2 { }
-
再之前的配置类中我们来选择导入这个配置类
@Configuration @Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签 public class MyConfig { @Bean public Dog dog(){ return new Dog(); } }
八、代理模式
8.1 静态代理
静态代理角色分析
- 抽象角色:一般使用接口或者抽象类来实现
- 真实角色:被代理的角色
- 代理角色:代理真实角色;代理真实角色后,一般会做一些附属的操作
- 客户:使用代理角色来进行一些操作
静态代理模式的核心思想
- 代理与正式角色实现同一接口
- 代理持有真实角色的引用
- 代理增强角色
代码实现
Rent . java 即抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
1234
Host . java 即真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
123456
Proxy . java 即代理角色
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
123456789101112131415161718192021222324
Client . java 即客户
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
分析:真实角色只需关注核心业务,代理角色负责增强功能
静态代理的好处优缺点
优点
- 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
- 公共的业务由代理来完成,实现了业务的分工
- 公共业务发生扩展时变得更加集中和方便.
缺点
- 类多了,多了代理类,工作量变大
8.2 动态代理
动态代理的核心思想是通过反射机制和接口实现,在程序运行期间动态创建代理对象。
动态代理的角色和静态代理的一样
动态代理的代理类时动态生成的 静态代理的代理类是我们提前写好的
动态代理分为两类:一类是基于接口动态代理,一类是基于类的动态代理
- 基于接口的动态代理–JDK动态代理
- 基于类的动态代理–cglib
- 现在用的比较多的是javasist来生成动态代理
JDK动态代理
代理对象会拦截所有接口方法的调用,并将其委托给
InvocationHandler
的invoke
方法动态代理的核心是方法调用的拦截和转发
//万能公式
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
测试
public class Test {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
}
九、AOP
AOP就是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
9.1 AOP在spring中的作用
AOP核心概念
-
横切关注点(Cross-Cutting Concerns):多个模块中重复出现的、与核心业务无关的功能
-
切面(Aspect):封装横切关注点的模块,包含通知(Advice) 和 切点(Pointcut)
-
连接点(Join Point):程序执行过程中可以插入切点的面,例如方法调用、异常抛出等
-
切点(Pointcut):通过表达式匹配一组连接点,确定在哪些地方通知
示例:
execution(* com.example.service.*.*(..))
匹配service
包下所有类的所有方法。 -
通知(Advice):切面在特定连接点执行的动作
9.2 使用Spring实现AOP
重点:使用AOP,需要导入一个依赖包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
9.2.1 通过Spring API实现
首先编写我们的业务接口和实现类
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
12345678910111213141516171819202122
最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.kuang.service.UserServiceImpl"/>
<bean id="log" class="com.kuang.log.Log"/>
<bean id="afterLog" class="com.kuang.log.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
public class MyTest {
@Test
public void test(){
ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
9.2.2 自定义类来实现AOP
第一步 : 写我们自己的一个切入类
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
去spring中配置
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
测试:
public class MyTest {
@Test
public void test(){
ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
9.2.3 使用注解实现
第一步:编写一个注解实现的增强类
package com.kuang.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>