Spring简史
第一阶段:xml配置
在Spring1.x时代,Spring采用xml的方式来配置bean。
第二阶段:注解配置
在Spring2.x时代,随着JDK1.5带来了注解支持,Spring提供了声明Bean的注解,大大减少了配置量。
第三阶段:Java配置
从Spring3.x到现在,Spring提供了Java配置的能力。我们目前刚好处于这个时代,Spring4.x和Spring Boot都推荐使用Java配置。
基于IntelliJ IDEA搭建Spring
(1)新建Maven项目。单击File--New--Project--Maven
(2)输入Maven项目坐标值
(3)选择存储路径
(4)修改pom.xml文件。增加Spring依赖,添加编译插件,将编译级别设置为1.7。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>my</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- Spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 添加编译插件,将编译级别设置为1.7 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Spring框架的四大特征
(1)使用POJO进行轻量级和最小侵入式开发。
(2)提供IoC容器,通过依赖注入和基于接口编程实现松耦合。
(3)通过AOP和默认习惯进行声明式编程。
(4)使用AOP和模板(template)减少模式化代码。
Spring基础知识
Spring IoC容器(Application Context):负责创建Bean。Spring提供使用xml、注解、Java配置、groovy配置来实现Bean的创建和注入。
依赖注入:负责创建对象和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。
注意:控制反转是通过依赖注入实现的。
声明Bean的注解:
l @Component组件,没有明确角色。
l @Service在业务逻辑层(Service层)使用。
l @Controller在展现层使用。
l @Repository在数据访问层(dao层)使用。
注入Bean的注解,一般情况下通用。注意的是,不适用于局部变量。
l @Autowired:Spring提供的注解。
l @Resource:JSR-250提供的注解。
l @Inject:JSR-330提供的注解。
JAVA配置
Java配置是通过@Configuration和@Bean来实现的。
l @Configuration声明当前类是一个配置类,相当于Spring配置的一个xml文件。
l @Bean注解在方法上,声明当前方法返回值为一个Bean。
何时使用Java配置或者注解配置呢?
我们的原则是:全局配置使用Java配置(如数据库相关配置、MVC相关配置),业务Bean的配置使用注解配置(@Component、@Repository、@Service、@Controller)。
示例
示例采用简单的Java配置。
(1)编写功能类Bean:UserService
package my.spring.service;
public class UserService {
public void say(String word) {
System.out.println("hello " + word + " !");
}
}
(2)编写配置类
package my.spring.configuration;
import my.spring.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public UserService userService() {
return new UserService();
}
}
(3)运行
package my.spring;
import my.spring.configuration.Config;
import my.spring.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// 获得Bean
UserService userSrv = context.getBean(UserService.class);
userSrv.say("qinxia");
// 关闭IoC容器
context.close();
}
}
示例讲解与扩充
在配置类Config中:
(1)使用@Configuration注解表明当前类是一个配置类,这意味着这个类中可能有0个或多个Bean注解。
(2)使用@Bean注解声明当前方法的返回值是一个Bean。方法名是可以随便起的,但是为了规范,我们与Bean的类名保持一致,单驼峰标识。
另外:在Spring容器中,只要容器中存在某个Bean,就可以在另外一个Bean的声明方法的参数中注入。(参见示例2)
示例2
(1)编写功能类Bean:UserService和UseUserService
package my.spring.service;
public class UserService {
public String say(String word) {
return "hello " + word + " !";
}
}
package my.spring.service;
public class UseUserService {
UserService userSrv;
public void setUserSrv(UserService userSrv) {
this.userSrv = userSrv;
}
public String say(String word) {
return userSrv.say(word);
}
}
(2)编写配置类
package my.spring.configuration;
import my.spring.service.UseUserService;
import my.spring.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public UseUserService useUserService(UserService userSrv) {
UseUserService useUserService = new UseUserService();
useUserService.setUserSrv(userSrv);
return useUserService;
}
// @Bean
// public UseUserService useUserService() {
// UseUserService useUserService = new UseUserService();
// useUserService.setUserSrv(userService());
// return useUserService;
// }
}
(3)运行
package my.spring;
import my.spring.configuration.Config;
import my.spring.service.UseUserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// 获得Bean
UseUserService useUserService = context.getBean(UseUserService.class);
System.out.println(useUserService.say("qinxia"));
context.close();
}
}
示例3
示例3基于注解的Bean的初始化和依赖注入。
(1)编写功能类Bean:UserService和UseUserService
package my.spring.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String say(String word) {
return "hello " + word + " !";
}
}
package my.spring.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UseUserService {
@Autowired
UserService userService;
public String say(String word) {
return userService.say(word);
}
}
(2)编写配置类
package my.spring.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("my.spring")
public class Config {
}
(3)运行
ackage my.spring;
import my.spring.configuration.Config;
import my.spring.service.UseUserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// 获得Bean
UseUserService useUserSrv = context.getBean(UseUserService.class);
System.out.println(useUserSrv.say("qinxia"));
context.close();
}
}
示例3讲解
@Service注解声明当前类是Spring管理的一个Bean。
@Autowired注解将UserService的实体Bean注入到UseUserService中。
@Configuration注解声明当前类是一个配置类。
@ComponentScan(“包名”)注解自动扫描指定包下面的所有使用@Component、@Repository、@Controller和@Service的类,并注册为Bean。
AOP
AOP:面向切面编程。
好处:
可以动态地添加和删除在切面上的逻辑,而不影响原来的执行代码。
降低耦合度。
JoinPoint:符合条件的每一个被拦截处为连接点,即切点。
PointCut:切点集合。
Spring支持AspectJ的注解式切面编程:
l 使用@Aspect声明一个切面。
l 使用@After、@Before、@Around定义建言(advice),可直接将切点集合作为参数。
拦截方式:
l 基于注解拦截
l 基于方法规则拦截
示例(基于注解的拦截)
(1)修改pom.xml:添加spring aop依赖及AspectJ依赖。
<!-- Spring aop依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<!-- aspectj依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.5</version>
</dependency>
</dependencies>
(2)创建注解:用来声明切点,也就是被拦截处。
package my.spring.annotations;
import java.lang.annotation.*;
@Documented // 文档:声明该注解的有关信息
@Target(ElementType.METHOD) // 指定注解的作用范围:作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 指定注解的生存周期:会在class字节码文件中存在,运行时通过反射可获得
public @interface Cut1 { //①
String name();
}
(3)编写被拦截类
package my.spring.service;
import my.spring.annotations.Cut1;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cut1(name="注解式拦截") //②
public void say1() {
System.out.println("hello world!");
}
}
(4)编写切面
package my.spring.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect //③
public class LogAspect {
@Pointcut("@annotation(my.spring.annotations.Cut1)") //④
public void annotationPointCut1() {};
@Before("annotationPointCut1()") //⑤
public void before() {
System.out.println("===before===");
}
@After("annotationPointCut1()") //⑥
public void after() {
System.out.println("===after===");
}
}
(5)编写配置类
package my.spring.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("my.spring")
@EnableAspectJAutoProxy //⑦开启Spring对AspectJ的支持
public class Config {
}
(6)运行
package my.spring;
import my.spring.configuration.Config;
import my.spring.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// 获得Bean
UserService userService = context.getBean(UserService.class);
userService.say1();
context.close();
}
}
示例讲解
① 创建注解@Cut1,用来声明切点。也就是说只要我们在被拦截处的添加@Cut1注解,就指定了该处为一个切点,执行切面上与之对应的建言。
② 在say()方法上添加@Cut1注解,声明该方法为一个切点。
③ @Aspect声明该类为一个切面。
④ @Pointcut("@annotation(my.spring.annotations.Cut1)")声明了一个切点集合,参数用来指定特定的切点。即@Cut1声明的切点;@annotation用来匹配持有特定注解的方法,参数为自定义的注解类。
⑤ @Before("annotationPointCut1()")声明一个建言,作用于指定的切点集合,并在切点之前执行before()方法。
⑥ @After("annotationPointCut1()")声明一个建言,作用于指定的切点集合,并在切点之后执行after()方法。
⑦ @EnableAspectJAutoProxy开启Spring对AspectJ的支持。
示例2(基于规则方法的拦截)
在示例的基础上做如下修改:
(1)修改被拦截类
package my.spring.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void say1() {
System.out.println("hello world!");
}
}
(2)修改切面
package my.spring.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAspect {
@Before("execution(* my.spring.service..*.*(..))") //①
public void before() {
System.out.println("===before===");
}
@After("execution(* my.spring.service..*.*(..))")
public void after() {
System.out.println("===after===");
}
}
(3)运行:同上,不变。
示例2解析
① @Before("execution(* my.spring.service..*.*(..))")指定了在执行特定方法之后,执行before方法。
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution():表达式主体。
2、第一个*号:表示返回类型,*号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,my.spring.service包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
总结
通过示例和示例2,我们可以看出,不管是基于注解的拦截还是基于方法规则的拦截,对于切面而言,最重要的是明确切点集合,也就是建言的作用范围。
基于注解的拦截:需要在被拦截处使用自定义的注解进行声明,需要在切面中通过@Pointcut(“@annotation(注解类)”)注解作用于方法的形式来明确切点集合,然后方法名作为advise的参数,来确定该建言的作用范围。
基于规则方法的拦截:被拦截处不需要加任何注解,只需要在切面中通过execution(方法集合)作为建言的参数,来确定该建言的作用范围。
其中,注解式拦截能够很好地控制要拦截的粒度和获得更丰富的信息。Spring本身在事务处理(@Transcational)和数据缓存(@Cacheable等)上面都使用此种形式的拦截。
(欢迎指导,共同进步!)