java框架之Spring(2)-注解配置IOC&AOP配置

时间:2023-03-09 03:44:47
java框架之Spring(2)-注解配置IOC&AOP配置

注解配置IoC

准备

1、要使用注解方式配置 IoC,除了之前引入的基础 jar 包,还需要引入 spring-aop 支持包,如下:

java框架之Spring(2)-注解配置IOC&AOP配置

2、在 applicationContext.xml 中引入 context 约束:

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置文件约束在 spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html 下可以找到-->
</beans>

简单使用

package com.zze.dao;

public interface UserDao {
    void save();
}

com.zze.dao.UserDao

package com.zze.dao.impl;

import com.zze.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 实例化当前类并将其交给 Spring 管理
 * 相当于在配置文件中配置 <bean id="userDao" class="com.zze.dao.impl.UserDaoImpl"/>
 */
@Component("userDao")
public class UserDaoImpl implements UserDao {
    /*
    给属性注入值
     */
    @Value("张三")
    private String name;

    /*
    如果提供了 set 方法,则可在 set 方法上加上注解
     */
//    @Value("张三")
//    public void setName(String name) {
//        this.name = name;
//    }

    @Override
    public void save() {
        System.out.println("from UserDaoImpl.save()" + name);
    }
}

com.zze.dao.impl.UserDaoImpl : 使用注解

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--使用 IoC 注解开发,配置注解扫描的包(哪些包下使用了 IoC 注解)-->
    <context:component-scan base-package="com.zze.dao"/>
</beans>

applicationContext.xml : 配置注解扫描的包

@Test
public void test1(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    userDao.save();
}

test

常用注解

@Component:实例化注解

修饰一个类,将这个类实例交给 Spring 管理。

这个注解有三个衍生注解(功能相同):

  • @Controller:修饰 web 层的类。
  • @Service:修饰 service 层的类。
  • @Repository:修饰 dao 层的类。

属性注入注解

普通类型的属性注入:

  • @Value:设置普通属性的值。

对象类型的属性注入:

  • @Autowired:设置对象类型属性的值。
    默认情况下是按照类型匹配完成属性注入,如果想按照名称匹配完成属性注入,可以同时再使用一个注解 @Qualifier 来指定要注入对象的名称。
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;

    例:

  • @Resource:根据对象名称匹配完成属性注入。
    @Resource(name = "userDao")
    private UserDao userDao;

    例:

生命周期相关注解

  • @PostConstruct:修饰一个方法,让其在实例化时执行,相当于在 bean 标签中指定 init-method 。
  • @PreDestroy:修饰一个方法,让其在实例销毁之前执行,相当于在 bean 标签中指定 destroy-method。

@Scope:作用范围注解

修饰一个类,指定其作用范围。

value 有如下几个可选值:

singleton :默认值,Spring 会采用单例模式创建这个对象。

prototype :多例的。

request :应用在 web 项目中,Spring 创建这个类对象后,将这个对象存放到 request 范围中。

session :应用在 web 项目中,Spring 创建这个类对象后,将这个对象存放的 session 范围中。

globalsession :应用在 web 项目中,必须在 porlet 环境下使用。

总结

IoC的XML和注解方式开发相比较

XML 方式:结构清晰,维护方便,适用于任意场景。

注解方式:简易开发,当一个类不是自己编写而是由第三方提供时,不可以使用。

XML方式和注解方式整合开发

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--在没有类注解扫描的情况下,使用属性注入注解。-->
    <context:annotation-config/>

    <bean name="userService" class="com.zze.service.impl.UserServiceImpl"/>

    <bean name="userDao" class="com.zze.dao.impl.UserDaoImpl"/>
</beans>

applicationContext.xml

package com.zze.service.impl;

import com.zze.dao.UserDao;
import com.zze.service.UserService;

import javax.annotation.Resource;

public class UserServiceImpl implements UserService {
    @Resource(name = "userDao")
    private UserDao userDao;

    @Override
    public void save() {
        System.out.println("from UserServiceImpl.save()");
        userDao.save();
    }
}

com.zze.service.impl.UserServiceImpl

AOP配置

概述

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

AOP 思想最早是由 AOP 联盟组织提出,Spring 是目前使用这种思想最好的框架。

Spring 本身有自己的 AOP 实现方式,但是非常繁琐。AspectJ 是一个 AOP 框架,Spring 后期引入了 AspectJ 用作自身 AOP 开发。

Spring中AOP实现原理:

Spring 底层是使用动态代理技术实现 AOP。当被代理类实现了接口,此时就会使用 JDK 动态代理方式生成代理对象。当被代理类未实现接口,此时 Spring 就会使用 Cglib 动态代理方式生成代理对象。

AOP相关术语

  • 连接点(Joinpoint):

    程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring 仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。

    通俗讲其实就是可以被拦截到的点(方法),增删改查方法都可以被拦截增强,这些方法就可以称为是连接点。

  • 切入点(Pointcut):

    每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。

    通俗讲就是真正被拦截到的点(方法),如果在开发中只对 save 方法进行增强,那么 save 就称为是切入点。

  • 增强、通知(Advice):

    增强是织入到目标类连接点上的一段程序代码,在 Spring 中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。

    通俗讲就是方法层面的增强,如果要使用 checkPermission 方法进行权限校验,那么 checkPermission 方法就称为是增强。

    几种通知类型:
    • 前置通知:在目标方法执行之前进行的操作。
    • 后置通知:在目标方法执行之后进行的操作。
    • 环绕通知:在目标方法执行之前和之后进行的操作。
    • 异常抛出通知:在程序出现异常时进行的操作。
    • 最终通知:无论代码是否有异常,总是会执行。
  • 目标对象(Target):

    增强逻辑的织入目标类。如果没有 AOP,目标业务类需要自己实现所有逻辑,而在 AOP 的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用 AOP 动态织入到特定的连接点上。

    通俗讲就是指被增强的类,如果对 UserDao 类进行增强,那么 UserDao 类就是目标对象。

  • 引介(Introduction):

    引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过 AOP 的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

    通俗讲就是类层面的增强。

  • 织入(Weaving):

    织入是将增强添加对目标类具体连接点上的过程。AOP 像一台织布机,将目标类、增强或引介通过 AOP 这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:

    a、编译期织入,这要求使用特殊的Java编译器。
    b、类装载期织入,这要求使用特殊的类装载器。
    c、动态代理织入,在运行期为目标类添加增强生成子类的方式。
    Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

    通俗讲就是将通知(Advice)应用到目标对象(Target)的过程。

  • 代理(Proxy):

    一个类被 AOP 织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

    通俗讲就是最后返回的代理对象。

  • 切面(Aspect):

    切面由切入点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP 就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

    通俗讲就是切入点与通知的组合。

XML方式

1、导包,除了要引入 6 个基础 jar 包外,还需引入如下 jar 包:

java框架之Spring(2)-注解配置IOC&AOP配置

2、编写切面类:

package com.zze.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 切面类
 */
public class MyAspect {
    public void before() {
        System.out.println("前置通知");
    }

    public void afterReturning(Object result) {
        System.out.println("后置通知" + result);
    }

    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知前");
        joinPoint.proceed();
        System.out.println("环绕通知后");
    }

    public void afterThrowing(Throwable ex) {
        System.out.println("异常抛出通知" + ex);
    }

    public void after(){
        System.out.println("最终通知");
    }
}

com.zze.aspect.MyAspect

3、编写目标类:

package com.zze.dao;

public interface UserDao {
    public void save();

    public Boolean delete();

    public void list();

    public void update();
}

com.zze.dao.UserDao

package com.zze.dao.impl;

import com.zze.dao.UserDao;

public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("保存操作 from com.zze.dao.impl.UserDaoImpl.save()");
    }

    public Boolean delete() {
        System.out.println("删除操作 from com.zze.dao.impl.UserDaoImpl.delete()");
        return true;
    }

    public void list() {
        System.out.println("查询操作 from com.zze.dao.impl.UserDaoImpl.list()");
        int i = 1 / 0;
    }

    public void update() {
        System.out.println("修改操作 from com.zze.dao.impl.UserDaoImpl.update()");
    }
}

com.zze.dao.impl.UserDaoImpl

4、配置 applicationContext.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"
       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 name="userDao" class="com.zze.dao.impl.UserDaoImpl" />
    <!--配置切面类,将切面类交给 Spring 管理-->
    <bean name="myAspect" class="com.zze.aspect.MyAspect"/>

    <!--通过 AOP 配置对目标类代理-->
    <aop:config>
        <!--
        表达式配置使用哪些方法类增强哪些类
        语法:
            [访问修饰符] 方法返回值 包名.类名.方法名(参数)
            * 表示通配符
            参数位置使用 .. 匹配任意参数
        -->
        <aop:pointcut id="pc1" expression="execution(* com.zze.dao.UserDao.save(..))"/>
        <aop:pointcut id="pc2" expression="execution(* com.zze.dao.UserDao.delete(..))"/>
        <aop:pointcut id="pc3" expression="execution(* com.zze.dao.UserDao.update(..))"/>
        <aop:pointcut id="pc4" expression="execution(* com.zze.dao.UserDao.list(..))"/>
        <!--配置切面-->
        <aop:aspect ref="myAspect">
            <!--前置通知-->
            <aop:before pointcut-ref="pc1" method="before"/>
            <!--后置通知-->
            <aop:after-returning pointcut-ref="pc2" method="afterReturning" returning="result"/>
            <!--环绕通知-->
            <aop:around pointcut-ref="pc3" method="around"/>
            <!--异常抛出通知-->
            <aop:after-throwing pointcut-ref="pc4" method="afterThrowing" throwing="ex"/>
            <!--最终通知-->
            <aop:after pointcut-ref="pc4" method="after"/>
        </aop:aspect>
    </aop:config>
</beans>

5、测试:

package com.zze.test;

import com.zze.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test{
    @Resource(name = "userDao")
    private UserDao userDao;

    @Test
    public void test() {
        userDao.save();
        userDao.delete();
        userDao.update();
        userDao.list();
        /*
        前置通知
        保存操作 from com.zze.dao.impl.UserDaoImpl.save()
        删除操作 from com.zze.dao.impl.UserDaoImpl.delete()
        后置通知true
        环绕通知前
        修改操作 from com.zze.dao.impl.UserDaoImpl.update()
        环绕通知后
        查询操作 from com.zze.dao.impl.UserDaoImpl.list()
        最终通知
        异常抛出通知java.lang.ArithmeticException: / by zero
         */
    }
}

这里使用了 Spring 整合 JUnit 测试,需要额外导入 spring-test 包。

注解方式

1、导包,和 XML 配置 AOP 时相同,如下:

java框架之Spring(2)-注解配置IOC&AOP配置

2、编写目标类:

package com.zze.dao;

public class UserDao {
    public void save() {
        System.out.println("保存操作 from com.zze.dao.impl.UserDao.save()");
    }

    public Boolean delete() {
        System.out.println("删除操作 from com.zze.dao.impl.UserDao.delete()");
        return true;
    }

    public void list() {
        System.out.println("查询操作 from com.zze.dao.impl.UserDao.list()");
        int i = 1 / 0;
    }

    public void update() {
        System.out.println("修改操作 from com.zze.dao.impl.UserDao.update()");
    }
}

com.zze.dao.UserDao

3、编写切面类并使用注解配置:

package com.zze.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

/**
 * 切面类
 */
@Aspect
public class MyAspect {

    /**
     * 定义一个切入点表达式,在下方可引用
     */
    @Pointcut("execution(* com.zze.dao.UserDao.list(..))")
    public void pc1() {
    }

    /**
     * 前置通知
     */
    @Before(value = "execution(* com.zze.dao.UserDao.save(..))")
    public void before() {
        System.out.println("前置通知");
    }

    /**
     * 后置通知
     */
    @AfterReturning(value = "execution(* com.zze.dao.UserDao.delete(..))", returning = "result")
    public void afterReturning(Object result) {
        System.out.println("后置通知" + result);
    }

    /**
     * 环绕通知
     */
    @Around(value = "execution(* com.zze.dao.UserDao.update(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知前");
        joinPoint.proceed();
        System.out.println("环绕通知后");
    }

    /**
     * 异常抛出通知
     */
    @AfterThrowing(value = "pc1()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("异常抛出通知" + ex);
    }

    /**
     * 最终通知
     */
    @After(value = "pc1()")
    public void after() {
        System.out.println("最终通知");
    }
}

com.zze.aspect.MyAspect

4、配置 applicationContext.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"
       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">
    <!--开启注解 AOP 开发-->
    <aop:aspectj-autoproxy/>
    <!--目标类-->
    <bean name="userDao" class="com.zze.dao.UserDao"/>
    <!--切面类-->
    <bean name="myAspect" class="com.zze.aspect.MyAspect"/>
</beans>

5、测试:

package com.zze.test;

import com.zze.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo2 {
    @Autowired
    private UserDao userDao;

    @Test
    public void test() {
        userDao.save();
        userDao.delete();
        userDao.update();
        userDao.list();
        /*
        前置通知
        保存操作 from com.zze.dao.impl.UserDao.save()
        删除操作 from com.zze.dao.impl.UserDao.delete()
        后置通知true
        环绕通知前
        修改操作 from com.zze.dao.impl.UserDao.update()
        环绕通知后
        查询操作 from com.zze.dao.impl.UserDao.list()
        最终通知
        异常抛出通知java.lang.ArithmeticException: / by zero
         */
    }
}