Spring依赖注入和面向切面

Sep 8, 2016


一、依赖注入

  所谓依赖注入是指容器负责创建对象和维护对象间的依赖关系,而不是通过对象本身负责自己创建和解决自己的依赖。依赖注入的主要目的是解耦,体现了一种“组合”的思想。
  Spring IoC(Application Context)负责创建Bean,并通过容器将功能类Bean注入到你需要的Bean中。Spring提供xml、注解、java配置、groovv配置实现Bean的创建和注入。
  声明Bean的注解:

  • @Component,没有明确的角色。
  • @Service在业务逻辑层(service层)使用。
  • @Repository在数据访问层使用(dao层)使用。
  • @Controller在表现层(MVC-Spring MVC)使用。

  注入Bean的注解,一般情况下通用。

  • @Autowired:Spring提供的注解。
  • @Inject:JSR-330提供的注解。
  • @Resource:JSR-250提供的注解。

  以上三个注入Bean的注解可以用在set()方法上或者属性上。

  • @Configuration声明当前类是一个配置类。
  • @ComponentScan,自动扫描包名下所有使用@Service、@Component、@Repository和@Controller的类,并注册成为Bean。

  下面来看一个具体的例子(代码来自spring boot实战):

@Configuration //使用Configuration声明当前类是一个配置类
@ComponentScan("com.wisely.highlight_spring4.ch1.di") //2
public class DiConfig {

}
@Service //使用service注解声明当前FunctionService类是Spring管理的一个Bean。
public class FunctionService {
	public String sayHello(String word){
		return "Hello " + word +" !";
	}

}
@Service //声明为Bean
public class UseFunctionService {
	@Autowired //将FunctionService实体Bean注入到UserFuncitonService中。
	FunctionService functionService;

	public String SayHello(String word){
		return functionService.sayHello(word);
	}

}

public class Main {
	public static void main(String[] args) {
		//使用AnnotationConfigApplication作为Spring容器,接收一个配置类作为参数。
		 AnnotationConfigApplicationContext context =
	                new AnnotationConfigApplicationContext(DiConfig.class);
		 //通过容器获取Beam
		 UseFunctionService useFunctionService = context.getBean(UseFunctionService.class);

		 System.out.println(useFunctionService.SayHello("world"));

		 context.close();
	}
}

//输出
Hello world !

  注释已经比较清楚啦,利用spring容器获取bean,并实现了自动注入。

二、Java配置

  Java配置是Spring 4.x推荐的配置方式,可以完全替代xml配置,Java配置也是Spring Boot推荐的配置方式。Java配置是通过@Configuration和@Bean实现的。
  一帮全局配置使用Java配置(如数据库相关配置、MVC相关配置),业务Bean配置使用注解配置。

三、面向切面——AOP

  AOP可以让一组类共享相同的行为。Spring支持AspectJ的注解式切面编程。

  1. 使用@Aspect声明式一个切面。
  2. 使用@After、@Before、@Around定义建言(advice),可以直接将拦截规则(切点)作为参数。
  3. 其中@After、@Before、@Around参数的拦截规则为切点(PointCut),为了是切点复用,可使用@PointCut专门定义拦截规则,然后在@After、@Before、@Around的参数中调用。
  4. 其中符合条件的每一个拦截处为连接点(JoinPoint)。

  下面看Spring boot实战中一个具体的例子。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
    String name();
}

  这个类是一个注解类,我们将其作为拦截器规则的注解。

  • @Target标签表明该注解用于什么地方,可能的值在枚举类ElementType中,具体包括:
    • ElementType.CONSTRUCTOR
    • ElementType.FIELD
    • ElementType.LOCAL_VARIABLE
    • ElementType.METHOD
  • @Retention表示在什么级别保存该注解信息。可选的参数值在枚举类型RetentionPolicy中。
    • RetentionPolicy.SOURCE 注解将被编译器丢弃。
    • RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃。
    • RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
  • @Documented将此注解包含在javadoc中,它代表着此注解会被javadoc工具提取成文档。
@Configuration
@ComponentScan("com.wisely.highlight_spring4.ch1.aop")
@EnableAspectJAutoProxy
public class AopConfig {

}
  • @Configuration声明当前类是一个配置类。
  • @ComponentScan(“com.wisely.highlight_spring4.ch1.aop”)用于扫描包下所有的bean,并通过spring容器管理。
  • @EnableAspectJAutoProxy允许面向切面的自动代理,这样spring启动时会自动扫描AOP相关的标注,在创建对象时帮我们去织入过程。
@Service
public class DemoAnnotationService {
    @Action(name="注解式拦截的add操作")
    public void add(){
        System.out.println("DemoAnnotationService");
    }
}

  这个类在add方法前使用了前面定义的@Action注解。

Service
public class DemoMethodService {
    public void add(){
        System.out.println("DemoMethodService");
    }
}

  这个类并没有使用前面定义的注解。

@Aspect //声明为一个切面
@Component //成为bean
public class LogAspect {

    //通过Pointcut注解声明切点
    //切入点表达式,决定哪个函数具体执行。
    @Pointcut("@annotation(com.wisely.highlight_spring4.ch1.aop.Action)")   
    public void annotationPointCut() {} //切入点签名,包含名称和任意参数,这里参数为空。

    @After("annotationPointCut()") //通过after注解声明一个建言,并使用pointcut定义的切点
    public void after(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Action action = method.getAnnotation(Action.class);
        System.out.println("注解式拦截 " + action.name()); //通过反射可以获得注解上的属性,然后做日志相关操作。
    }

    @Before("execution(* com.wisely.highlight_spring4.ch1.aop.DemoMethodService.*(..))") //此建言直接使用拦截规则作为参数。
    public void before(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        System.out.println("方法规则式拦截," + method.getName());
    }
}

  这个类就是我们编写的切面类,具体过程如下:

  • @Aspect先声明为类一个切面,并通过@Component声明为bean。
  • 在类内声明切点,并定义好签名和参数。
  • @After注解声明一个建言,并使用pointcut定义的切点。
    • 建言(advice)就是一个在切点上运行的方法。
  • @Before注解声明一个建言。
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
	public static void main(String[] args) {
		 AnnotationConfigApplicationContext context =
	                new AnnotationConfigApplicationContext(AopConfig.class);

		 DemoAnnotationService demoAnnotationService = context.getBean(DemoAnnotationService.class);

		 DemoMethodService demoMethodService = context.getBean(DemoMethodService.class);

		 demoAnnotationService.add();

		 demoMethodService.add();

		 context.close();
	}
}


//输出
DemoAnnotationService
注解式拦截 注解式拦截的add操作
方法规则式拦截,add
DemoMethodService

  可以看到已经成功织入,切点上执行了建言。