Java基础之Spring框架

简介

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。

Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。

Spring使用

  1. 创建maven工程,在pom.xml中添加spring依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.7.RELEASE</version>
    </dependency>
  2. 创建User类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package org.example.learn;

    import java.util.Date;

    public class User {
    private String username;
    private int age;
    private String address;
    private Date date;

    public User() {
    }

    public User(String username, int age, String address, Date date) {
    this.username = username;
    this.age = age;
    this.address = address;
    this.date = date;
    }

    public String getUsername() {
    return username;
    }

    public void setUsername(String username) {
    this.username = username;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getAddress() {
    return address;
    }

    public void setAddress(String address) {
    this.address = address;
    }

    public Date getDate() {
    return date;
    }

    @Override
    public String toString() {
    return "User{" +
    "username='" + username + '\'' +
    ", age=" + age +
    ", address='" + address + '\'' +
    ", date=" + date +
    '}';
    }

    public void setDate(Date date) {
    this.date = date;
    }
    }
  3. 创建Spring配置文件applicationContext.xml,在配置文件中加入User bean

    1
    2
    3
    4
    5
    6
    7
    <?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="org.example.learn.User"></bean>
    </beans>
  4. test

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package org.example;

    import org.example.learn.User;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;


    public class App
    {
    public static void main( String[] args )
    {
    // 实例化一个容器
    ClassPathXmlApplicationContext cl = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) cl.getBean("user");
    // User user = cl.getBean("user", User.class);
    System.out.print(user);
    }
    }

基本属性注入

构造注入

平时都是在new的时候或者使用set方法对其进行赋值,在spring框架下,只需要配置xml文件就能进行构造器的注入

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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="org.example.learn.User">
<constructor-arg name="username" value="zhangshan" />
<constructor-arg name="age" value="18" />
<constructor-arg name="address" value="Beijing" />
<constructor-arg name="date" ref="now" />
</bean>

<bean id="now" class="java.util.Date"></bean>
</beans>

注:类中需要提供一个对应参数列表的构造函数

constructor-arg标签属性:

index:指定参数在构造函数参数列表的索引位置

type:指定参数在构造函数中的数据类型

name:指定参数在构造函数中的名称

value:它能赋的值是基本数据类型和 String 类型

ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean

Set注入

配置xml文件

1
2
3
4
5
6
7
8
9
<!-- Set注入 -->
<bean id="user" class="org.example.learn.User">
<property name="username" value="lisi" />
<property name="age" value="18" />
<property name="address" value="Beijing" />
<property name="date" ref="now" />
</bean>

<bean id="now" class="java.util.Date"></bean>

property标签属性:

name:找的是类中 set 方法后面的部分
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
实际开发中,此种方式用的较多。

Java配置

在Spring中,将一个Bean注册到Spring容器中,有三种不同的方式:

  • xml注入(如第3节所述)
  • Java配置(本节所述)
  • 自动化扫描

下面有一个Bean:

1
2
3
4
5
6
7
package org.example.learn;

public class SayHello {
public String sayHello(String name) {
return "hello" + name;
}
}

在Java中,使用一个Java配置类代替之前的XML配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example.learn;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 这个注解表示这不是一个普通的类,这是一个配置类,相当于Spring的配置文件
@Configuration
public class Javaconfig {
// 将这个方法的返回值注入到Spring容器中
@Bean
SayHello sayHello() {
return new SayHello();
}
}

测试:在项目启动时,加载配置类,将Bean注册到Spring容器中

1
2
3
4
5
6
@Test
public void TestJavaConfig() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Javaconfig.class);
SayHello hello = ctx.getBean(SayHello.class);
System.out.print(hello.sayHello("mike"));
}

隐式的bean发现机制和自动装配

例子

创建实体类

Cat类:

1
2
3
4
5
6
7
package org.example.learn;

public class Cat {
public void shout() {
System.out.println("miaomiaomiao");
}
}

Dog类:

1
2
3
4
5
6
7
package org.example.learn;

public class Dog {
public void shout() {
System.out.println("wangwangwang");
}
}

Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package org.example.learn;

public class Person {
private String username;
private Cat cat;
private Dog dog;

public Person(String username, Cat cat, Dog dog) {
this.username = username;
this.cat = cat;
this.dog = dog;
}

public Person() {

}

@Override
public String toString() {
return "Person{" +
"username='" + username + '\'' +
", cat=" + cat +
", dog=" + dog +
'}';
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Cat getCat() {
return cat;
}

public void setCat(Cat cat) {
this.cat = cat;
}

public Dog getDog() {
return dog;
}

public void setDog(Dog dog) {
this.dog = dog;
}
}

在Spring配置文件上注入Bean

1
2
3
4
5
6
7
8
<bean id="cat" class="org.example.learn.Cat" />
<bean id="dog" class="org.example.learn.Dog" />

<bean id="person" class="org.example.learn.Person">
<property name="username" value="mike" />
<property name="cat" ref="cat" />
<property name="dog" ref="dog" />
</bean>

测试:

1
2
3
4
5
6
7
@Test
public void TestPerson() {
ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) cx.getBean("person");
person.getCat().shout();
person.getDog().shout();
}

ByName自动装配

修改Spring配置

1
2
3
4
5
6
7
8
<bean id="cat" class="org.example.learn.Cat" />
<bean id="dog" class="org.example.learn.Dog" />

<bean id="person" class="org.example.learn.Person" autowire="byName">
<property name="username" value="mike" />
<!-- <property name="cat" ref="cat" /> -->
<!-- <property name="dog" ref="dog" /> -->
</bean>

byName的执行过程:

  • byName依赖于实体类的set方法
  • 查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  • 去spring容器中寻找是否有此字符串名称id的对象。
  • 如果有,就取出注入;如果没有,就报空指针异常(java.lang.NullPointerException)。

ByType自动装配

修改Spring Bean注入配置文件

1
2
3
4
5
6
7
8
<bean class="org.example.learn.Cat" />
<bean class="org.example.learn.Dog" />

<bean id="person" class="org.example.learn.Person" autowire="byType">
<property name="username" value="mike" />
<!-- <property name="cat" ref="cat" /> -->
<!-- <property name="dog" ref="dog" /> -->
</bean>

使用autowire byType首先需要保证:同一类型的对象在spring容器中唯一。如果不唯一,会报不唯一的异常。

1
NoUniqueBeanDefinitionException

byType注意:

  • ByType按照类型自动装配
  • 每个bean的类型必须唯一,不可重复
  • 它不依赖set方法,而是按照对应的类型进行装配

总结:

  • 使用byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
  • 使用byType的时候,需要保证所有bean的Class唯一,并且这个bean需要和自动注入的属性的类型一致!

使用注解实现自动装配

jdk1.5开始支持注解,spring2.5开始全面支持注解

准备:

  1. 导入约束:context约束

  2. 配置注解的支持

    1
    <context:annotation-config/>

Spring 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<context:annotation-config/>

</beans>

@Autowired

在实体类的属性上添加@Autowired注解

1
2
3
4
@Autowired
private Cat cat;
@Autowired
private Dog dog;

修改Spring配置文件

1
2
3
4
5
6
<context:annotation-config/>

<bean id="cat1" class="org.example.learn.Cat" />
<bean id="dog1" class="org.example.learn.Dog" />

<bean id="newperson" class="org.example.learn.NewPerson" />

注:@Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作

  • @Autowired是按类型自动转配的,不支持id匹配。

  • 直接在属性上添加@Autowired注解即可,也可以添加在setter方法上。

  • 使用了@Autowired的属性,不需要setter方法也可以实现注入

  • @Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

    1
    2
    3
    // 如果允许对象为null,设置required = false,默认为true
    @Autowired(required = false)
    private Cat cat;
  • @Nullable,可以用于方法,属性,表示可以为null

    1
    2
    3
    4
    // @Nullable,表示当前属性可以为null
    public People(@Nullable String name) {
    this.name = name;
    }

@Qualifier

修改Bean,一个类有多个对象

1
2
3
4
<bean id="cat1" class="org.example.learn.Cat" />
<bean id="cat2" class="org.example.learn.Cat" />
<bean id="dog1" class="org.example.learn.Dog" />
<bean id="dog2" class="org.example.learn.Dog" />

在实体类中需要加上@Qualifier

1
2
3
4
5
6
@Autowired
@Qualifier("cat1")
private Cat cat;
@Autowired
@Qualifier("dog1")
private Dog dog;

@Resource

修改实体类

1
2
3
4
@Resource(name="cat1")
private Cat cat;
@Resource
private Dog dog;

修改配置文件

1
2
3
4
5
6
<context:annotation-config/>

<bean id="cat1" class="org.example.learn.Cat" />
<bean id="cat2" class="org.example.learn.Cat" />
<bean id="dog" class="org.example.learn.Dog" />
<bean id="dog2" class="org.example.learn.Dog" />

注:@Resource不是spring的注解,属于 javax.annotation.Resource ;它也可以实现自动装配

  • @Resource可以指定的name属性,设置name后使用指定的name进行byName方式查找装配;
  • 上面的方法没找到再使用默认的byName方式查找(方法名小写)
  • 通过byName方法没有找到时,再使用byType的方式装配
  • 如果byName和byType方法全部没有找到,则会报错!

AOP

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

方法一

添加依赖

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>

编写业务接口和实现类

1
2
3
4
5
6
7
8
package org.example.learn;

public interface service1 {
void add();
void delete();
void update();
void query();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example.learn;

public class UserService implements service1 {
@Override
public void add() {
System.out.println("添加方法");
}

@Override
public void query() {
System.out.println("查询方法");
}

@Override
public void delete() {
System.out.println("删除方法");
}

@Override
public void update() {
System.out.println("更新方法");
}
}

增强类:前置增强和后置增强

1
2
3
4
5
6
7
8
9
10
11
12
// log.class
package org.example.learn;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class log implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"De"+method.getName()+"方法执行了");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// After.class
package org.example.learn;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class Afterlog implements AfterReturningAdvice {
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了"+method.getName()+"执行结果为"+o);
}
}

在spring配置文件aopContext.xml中注册,实现AOP切入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册bean-->
<bean id="userservice" class="org.example.learn.UserService"/>
<bean id="log" class="org.example.learn.log"/>
<bean id="afterlog" class="org.example.learn.Afterlog"/>

<!-- AOP配置-->
<aop:config>
<!-- 切入点expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* org.example.learn.UserService.*(..))"/>
<!-- 环绕执行;advice-ref执行方法,pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
</aop:config>

</beans>

测试

1
2
3
4
5
6
@Test
public void TestService() {
ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext("aopContext.xml");
service1 service = (service1) cx.getBean("userservice");
service.delete();
}

方法二

业务接口和实现类同上

使用自定义的切入类DiyPointCut.class

1
2
3
4
5
6
7
8
9
10
package org.example.learn;

public class DiyPointCut {
public void before() {
System.out.println("------方法执行前------");
}
public void after() {
System.out.println("------方法执行后------");
}
}

Spring配置文件aopContext2.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册bean-->
<bean id="userservice" class="org.example.learn.UserService"/>

<!-- 方法二:AOP配置-->
<!-- 注册bean -->
<bean id="diy" class="org.example.learn.DiyPointCut" />
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="diyPointCut" expression="execution(* org.example.learn.UserService.*(..))"/>
<aop:before method="before" pointcut-ref="diyPointCut" />
<aop:after method="after" pointcut-ref="diyPointCut" />
</aop:aspect>
</aop:config>
</beans>

测试

1
2
3
4
5
6
@Test
public void TestService2() {
ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext("aopContext2.xml");
service1 service = (service1) cx.getBean("userservice");
service.delete();
}

方法三

业务接口和实现类同上

编写注解实现的增强类AnnotationPointCut.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.example.learn;

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(* org.example.learn.UserService.*(..))")
public void before(){
System.out.println("方法执行前");
}
@After("execution(* org.example.learn.UserService.*(..))")
public void after(){
System.out.println("方法执行后");
}

@Around("execution(* org.example.learn.UserService.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable{
System.out.println("环绕前");
System.out.println("签名:"+pj.getSignature());
Object proceed = pj.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}

在spring配置文件aopContext3.xml中,注册bean,并增加支持注解的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册bean-->
<bean id="userservice" class="org.example.learn.UserService"/>

<!-- 方法三:AOP配置-->
<bean id="annotationPointcut" class="org.example.learn.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
</beans>

通过aop命名空间的注解支持声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面

参考

Spring 中文文档 (springdoc.cn)

Java学习之Spring框架入门篇

Java学习之Spring框架基础篇

Bean的自动装配 - 天下御免 - 博客园 (cnblogs.com)

AOP及实现方式 - aishimin - 博客园 (cnblogs.com)

Spring AOP——Spring 中面向切面编程 - SharpCJ - 博客园 (cnblogs.com)