从注解和JAVA配置的角度看Spring

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:负责创建BeanSpring提供使用xml、注解、Java配置、groovy配置来实现Bean的创建和注入。

依赖注入:负责创建对象和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。

注意:控制反转是通过依赖注入实现的。


声明Bean的注解:

l @Component组件,没有明确角色。

l @Service在业务逻辑层(Service层)使用。

l @Controller在展现层使用。

l @Repository在数据访问层(dao层)使用。


注入Bean的注解,一般情况下通用。注意的是,不适用于局部变量。

l @AutowiredSpring提供的注解。

l @ResourceJSR-250提供的注解。

l @InjectJSR-330提供的注解。

JAVA配置

Java配置是通过@Configuration@Bean来实现的。

l @Configuration声明当前类是一个配置类,相当于Spring配置的一个xml文件。

l @Bean注解在方法上,声明当前方法返回值为一个Bean

何时使用Java配置或者注解配置呢?

我们的原则是:全局配置使用Java配置(如数据库相关配置、MVC相关配置),业务Bean的配置使用注解配置(@Component@Repository@Service@Controller)。

示例

示例采用简单的Java配置。

 

(1)编写功能类BeanUserService

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)编写功能类BeanUserServiceUseUserService

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)编写功能类BeanUserServiceUseUserService

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的注解式切面编程:

使用@Aspect声明一个切面。

使用@After@Before@Around定义建言(advice),可直接将切点集合作为参数。


拦截方式:

基于注解拦截

基于方法规则拦截

示例(基于注解的拦截)

(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开启SpringAspectJ的支持。

示例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()是最常用的切点函数,其语法如下所示:

整个表达式可以分为五个部分:

1execution():表达式主体。

2、第一个*号:表示返回类型,*号表示所有的类型。

3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,my.spring.service包、子孙包下所有类的方法。

4、第二个*号:表示类名,*号表示所有的类。

5*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

总结

通过示例和示例2,我们可以看出,不管是基于注解的拦截还是基于方法规则的拦截,对于切面而言,最重要的是明确切点集合,也就是建言的作用范围。

 

基于注解的拦截:需要在被拦截处使用自定义的注解进行声明,需要在切面中通过@Pointcut(“@annotation(注解类)”)注解作用于方法的形式来明确切点集合,然后方法名作为advise的参数,来确定该建言的作用范围。

 

基于规则方法的拦截:被拦截处不需要加任何注解,只需要在切面中通过execution(方法集合)作为建言的参数,来确定该建言的作用范围。

 

其中,注解式拦截能够很好地控制要拦截的粒度和获得更丰富的信息。Spring本身在事务处理(@Transcational)和数据缓存(@Cacheable等)上面都使用此种形式的拦截。


(欢迎指导,共同进步!)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值