Spring学习记录(一)——IoC、AOP详解

Spring学习记录(一)——IoC、装配、DI

什么是Spring ?

Spring是面向java的开源框架, 有20多个模块组成的。它的核心技术是 IoC ,aop

spring它主要是解决对象,模块之间的耦合关系的。 实现“解耦” 的。

spring也称为一个容器, spring作为容器是管理存储java对象的

IoC

IoC: Inversion of Control 控制反转

控制反转:是一个理论,指导思想。

  • 把java对象的创建,对象关系的管理都交给代码之外的容器实现。
  • 把java对象,创建,属性赋值,依赖管理。这些都交给容器实现。

控制:完成对象的创建,属性赋值,对象之间关系的管理。 从对象的创建,到销毁的这个生命周期。

反转: 把原来由开发人员管理对象,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员实现对象创建,赋值,管理工作。

正转:由开发人员在代码中,使用new 构造方法创建对象,调用对象的方法。管理对象之间的关系。

IoC现有的实现: tomcat就是一个IoC的实现容器, tomcat可以创建servlet, listener,filter,并把这些对象放在tomcat的内部(map)保存。所以tomcat是一个容器,可以在程序代码之外创建servlet对象。tomcat也叫做jsp/servlet容器
spring是一个容器,可以完成对象创建,属性赋值,管理对象之间的关系。

IoC的实现技术

  1. DL:依赖查找,需要在服务器端配置对象的信息,在程序中需要使用特定的类和方法才能获取到服务器中的对象。

  2. DI:依赖注入,在程序中,通过对象的名称就可以获取到对象,至于对象的创建,保存,从容器中查找对象都由容器内部实现

spring框架使用的DI这种技术,实现IoC的功能。 spring底层创建对象,赋值属性使用的是jdk中的反射机制。

一、第一个程序

1.开发步骤

需求:把一个java的对象工作交给spring容器对象, 我们通过名称获取这个对象,
调用这个对象的业务方法。

实现步骤:

  1. 新建maven项目
  2. 加入maven的依赖坐标
    1. spring依赖
    2. junit依赖
  3. 创建一个接口,定义方法
  4. 创建接口的实现类
  5. 创建spring的配置文件
    • 使用<bean>声明对象, 这个对象就交给spring创建和管理的。
    • 由spring容器创建,管理的java对象叫做bean
  6. 创建测试类,测试从spring容器中获取对象

2.pom.xml中添加依赖

<dependencies>
   <!--单元测试-->
   <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.11</version>
     <scope>test</scope>
   </dependency>
   <!--spring依赖-->
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>4.3.16.RELEASE</version>
   </dependency>
 </dependencies>

3.创建一个接口,定义方法

public interface SomeService {
    void doSome();
}

4.创建接口的实现类

public class SomeServiceImpl implements SomeService {

    public SomeServiceImpl() {
        System.out.println("SomeServiceImpl无参数构造方法");
    }

    @Override
    public void doSome() {
        System.out.println("执行了SomeServiceImpl的doSome方法");
    }
}

5.创建spring的配置文件

<bean />:用于定义一个实例对象。一个实例对应一个 bean 元素。

  • id:该属性是 Bean 实例的 唯一标识,程序通过 id 属性访问 Bean, Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。
  • class:指定该 Bean 所属的类, 注意这里只能是类,不能是接口。全限定类名(spring是通过反射创建类的对象)
  • spring创建好对象后,是放入到map中了, id就是map中的key, 值是创建好的对象
<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="mySerivce" class="com.bjpowernode.service.impl.SomeServiceImpl" />
         等同于
         SomeService myService = new SomeServiceImpl();

         spring创建好对象后,是放入到map中了, id就是map中的key, 值是创建好的对象
         相当于map.put("myService",  new SomeServiceImpl())
    -->
    <bean id="myService" class="com.bjpowernode.service.impl.SomeServiceImpl" />
</beans>

6.测试类

public class AppTest 
{
    @Test
    public void testFirst(){
        //1.定义变量保存spring配置文件的位置
        // beans.xml应该是在classpath路径的根下面
        String config="beans.xml";

        //2.创建表示spring的容器对象ApplicationContext
        //ApplicationContext是一个接口,根据spring配置文件位置选择使用这个接口的实现类
        //如果配置文件放在classpath路径中,使用ClassPathXmlAppliationContext实现类
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

        //3.从spring容器中获取对象(也就是从map中获取对象)
        //  使用getBean("id")获取对象,需要强制转为接口类型
        SomeService service = (SomeService) ctx.getBean("myService");

        //4.调用对象的业务方法
        service.doSome();
    }
}

二、扩展

  1. 把sping配置文件放在磁盘路径中,通过FileSystemXmlApplicationContext()方法读取
String config="D:\\developer\\mybeans.xml";
ApplicationContext ctx = new FileSystemXmlApplicationContext(config);
  1. 把spring的配置文件放在项目的根目录下,只需提供文件名即可
String config="spring.xml";
ApplicationContext ctx = new FileSystemXmlApplicationContext(config);
  1. 获取容器中对象的信息
  • 容器中定义的对象数量
int counts = ctx.getBeanDefinitionCount();
  • 获取容器中每个对象的名称
 String names [] = ctx.getBeanDefinitionNames();

三、装配

Bean 的装配,即 Bean 对象的创建。容器根据代码要求创建 Bean 对象后再传递给代码的过程,称为 Bean 的装配。

1. 默认装配方式

spring默认调用无参数构造方法创建对象

2. 作用域

作用域:表示对象的存在范围和可见性。

  1. singleton 单例,表示叫这个名称的对象只有一个。默认是单例。
    在定义bean对象时,指定其scope属性的值:
<bean id="xx" class="yyy" scope="singleton"/>
  1. prototype 原型,每次使用getBean()都获取一个新的对象。
    使用方式:
<bean id="xx" class="yyy" scope="prototype"/>
  1. request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
  2. session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
    注意:
    1. 对于 scope 的值 request、 session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
    2. 对于 scope 为 singleton 的单例模式, 该 Bean 是在容器被创建时即被装配好了,优点:获取对象的速度快。缺点:占用内存
    3. 对于 scope 为 prototype 的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行装配的,优点:不占内存。缺点:获取对象的速度慢

四、基于XML的DI

基于XML的注入是指:在spring的配置文件中,使用标签和属性,完成java对象属性赋值

bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入(DI)

根据注入方式的不同,常用的有两类:设值注入、构造注入。

1. 设值注入

设值注入, spring调用类的set方法,在set方法中可以给属性赋值。设值注入简单方便,使用最多。 90%以上都是设值注入。(没有set()方法会报错)

1)简单类型(String,基本类型)

语法:使用value属性给对应属性赋值

<bean id="xx" class="yyy">
	<property name="属性名" value="简单类型的属性的值" />
	<property name="属性名" value="简单类型的属性的值" />
	....
</bean>
2)引用类型(非简单类型)
  • 第一种方式,使用ref作为属性(掌握)
<bean id="xx" class="yyy">
	<property name="属性名" ref="bean的id"/>
	...
</bean>
  • 第二种方式,使用ref作为子标签(繁琐)
<bean id="xx" class="yyy">
	<property name="属性名">
		<ref bean="bean的id"/>
	</property>
</bean>

注意:ref属性中的对象,声明位置与当前对象的声明位置无关

2. 构造注入

构造注入,spring调用类的有参数构造方法,创建对象的同时,给属性赋值。
<constructor-arg>表示使用构造注入,一个<constructor-arg>表示构造方法的一个参数
<constructor-arg>的属性有:

  1. name:构造方法的形参名
  2. index:构造方法的参数位置,从0开始
  3. value:简单类型的属性值
  4. ref:引用类型的属性值
  • 例子:
<!--第一种方式,name属性实现构造注入-->
<bean id="myStudent" class="com.bjpowernode.ba03.Student">
    <constructor-arg name="myname" value="李四" />
    <constructor-arg name="myage" value="22" />
    <constructor-arg name="mySchool" ref="mySchool" />
</bean>

<!--第二种方式,使用index属性-->
<bean id="myStudent2" class="com.bjpowernode.ba03.Student">
    <constructor-arg index="0" value="张三" />
    <constructor-arg index="1" value="24" />
    <constructor-arg index="2" ref="mySchool"/>
</bean>

<!--第三种方式,省略index-->
<bean id="myStudent3" class="com.bjpowernode.ba03.Student">
    <constructor-arg  value="周丽" />
    <constructor-arg  value="24" />
    <constructor-arg  ref="mySchool"/>
</bean>

<!--声明School对象-->
<bean id="mySchool" class="com.bjpowernode.ba03.School">
    <property name="name" value="清华大学"/>
    <property name="address" value="北京的海淀区" />
</bean>

一道笔试题:如果构造注入和设值注入同时存在,则构造注入失效

3. 引用类型属性自动注入

spring框架根据规则给引用类型赋值。
常用的规则:(通过bean标签中的autowire属性来指定规则,是通过ser()方法注入的)

  • byName:根据名称自动注入
  • byType:根据类型自动注入
1) byName

spring找到引用类型的属性名,把这个名称和spring容器中bean的id比较,找到名称一样的,且数据类型是一样的,这些的bean能够赋值给引用类型

byName的使用方式:

<bean id="xx" class="yyy" autowire="byName">
	简单类型的属性赋值
</bean>

例子:

<!--byName自动注入, spring会把student类中所有符合条件的引用类型全部赋值-->
<bean id="myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
    <property name="name" value="张三" />
    <property name="age" value="24" />
   <!-- <property name="school" ref="mySchool" />-->
</bean>

<!--声明School对象-->
<bean id="school" class="com.bjpowernode.ba04.School">
    <property name="name" value="清华大学"/>
    <property name="address" value="北京的海淀区" />
</bean>
2) byType

java类中引用类型的数据类型和spring容器中bean的class属性值是同源关系的,这样的bean能够赋值给引用类型

同源关系:

  1. java类中引用类型的数据类型和bean的class属性值是一样的。
  2. java类中引用类型的数据类型和bean的class属性值是父子类关系的。
  3. java类中引用类型的数据类型和bean的class属性值接口和实现类关系的。

byType的使用方式:

<bean id="xx" class="yyy" autowire="byType">
   简单类型的属性赋值
</bean>

注意:符合条件的对象,只能有一个,多余一个是报错的。
例子:

<!--byType自动注入 -->
<bean id="myStudent" class="com.bjpowernode.ba05.Student" autowire="byType">
    <property name="name" value="张三" />
    <property name="age" value="24" />
   <!-- <property name="school" ref="mySchool" />-->
</bean>

<!--声明School的子类-->
<bean id="primarySchool" class="com.bjpowernode.ba05.PrimarySchool">
    <property name="name" value="小学"/>
    <property name="address" value="北京小学大兴" />
</bean>

4. 指定多个 Spring 配置文件

使用包含关系的多配置文件。程序中定义一个总的文件,总文件是用来包含其他多个配置文件的, 总文件一般不定义bean子标签。
使用语法:

<import resource="其他配置文件路径" />

关键字classpath:表示类路径

<import resource="classpath:ba06/spring-school.xml" />
<import resource="classpath:ba06/spring-student.xml" />

包含关系的配置文件,可以使用通配符 *,如下的标签包含了上面两个配置文件

<import resource="classpath:ba06/spring*" />

注意:

  1. 总的文件名称total.xml,不能符合通配符的范围,不能叫做spring-total.xml,会报错
  2. 不能在根路径下使用*,例如:<import resource="classpath:spring*"/>,会找不到包含的配置文件

五、基于注解的DI

基于XML的注入是指:在java程序的源代码中,使用注解完成属性赋值。

对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例,单例模式下,如果两者同时使用,且对象名相同,配置文件中的属性值将会覆盖掉注解中的属性值

1. 声明组件扫描器(component-scan)的标签

组件:java对象的意思。使用组件扫描器标签,需要在配置文件中,加入spring-context.xsd的约束文件

base-package:注解在项目中的包名,框架会扫描遍历这个包和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或者给属性赋值。

<context:component-scan base-package="com.bjpowernode.ba01, com.bjpowernode.ba02" />

指定多个包的方式:

  • 多次使用component-scan标签
  • 使用分隔符(;或者,)指定多个包
  • 指定父包

2. 注解修饰类

@Component:创建java类的对象,默认创建是单例对象

  • 属性:value 表示对象的名称,也就是bean的id
  • 位置:在类定义的上面

@Component(value = “myStudent”)作用是创建Student对象,等同于
<bean> id=“myStudent” class=“com.bjpowernode.ba01.Student” />

其它创建java对象的注解:

  1. @Repository:放在Dao层实现类上面,创建dao对象的。dao对象能够访问数据库
  2. @Service:放在Service层的实现类上面,创建service对象,service对象可以有事务功能
  3. @Controller:放在处理器类的上面,创建控制器对象,控制器对象能接受请求,响应处理结果。
    @Component只能创建对象,没有其他的功能, 而上面的@Repository,@Service,@Controller除了有创建对象的功能外,还可以给程序分层。

语法:

  • 通过value指定对象的名称myStudent
@Component(value = "myStudent")
public class MyTest {}
  • 省略value
@Component("myStudent")
public class MyTest {}
  • 不指定对象名称,由框架提供默认名称:类名的首字母小写(student)
@Component
public class Student {}

3. 简单类型属性注入

@Value:简单类型的属性赋值
属性:value:简单类型的属性值(可以省略value)
位置:

  1. 在属性(Field)定义的上面。无需set方法,推荐使用
  2. 在set方法的上面,用的少
@Value("张三")
private String name;

4. 引用类型属性注入

@Autowired

引用类型, 这个注解是spring框架提供的。用来支持自动注入的,可以使用byType, byName. 默认是byType
位置:

  1. 在属性定义的上面,无需set方法,推荐使用
  2. 在set方法的上面
byName自动注入:
  1. @Autowired:引用类型的赋值
  2. @Qualifier(value="bean的id"):从spring容器中获取指定名称的对象。
    这两个标签没有固定顺序
//byName自动注入
@Autowired
@Qualifier("mySchool")
private School school;
@Resource

来自jdk中的注解,给引用类型赋值id。使用的自动注入,支持byName, byType。

  • 若不带name参数,则默认是byName,若byName失败,则使用byType
    位置:

    1. 在属性定义的上面,无需set方法,推荐使用
    2. 在set方法上面
  • 带byName属性:
    给@Resource的name属性赋值,则 name 的值即为按照名称进行匹配的 Bean 的 id,即使用byName的方式自动注入

六、AOP

AOP是基于动态代理的,底层实现就是动态代理。AOP相当于是规范,定义了使用动态代理的步骤和方式。 框架把动态代理的实现封装了,就是aop动态代理:jvm创建了一个类,并创建这个类的对象。 这个类就是动态代理。
动态代理的实现方式有两种:

  1. jdk动态代理:使用java中反射包中的类和方法,创建代理的方式,实现原理是接口,要求类必须实现接口
  2. cglib:第三方工具库,实现动态代理的功能,实现原理是:继承,要求类和方法不能是final的。 子类对父类的方法进行扩展,子类就是代理。

AOP(Aspect Orient Programming),面向切面编程, 它是从动态的角度设计程序的。

  • Aspect:切面,就是指非业务功能, 这些功能需要在程序执行时增加给业务方法的。常见的切面是日志,事务,权限检查等。
  • Orient:面向,对着。
  • Programming:编程

怎么理解面向切面编程?( OOP:面向对象编程 OOP(Object Orient Programming))

  1. 以切面为编程的核心, 设计项目中需要找出可以作为切面功能,把切面作为独立的模块设计,使用。
  2. 考虑切面的执行位置执行时间

1. AOP的术语

(1) 切面(Aspect)

切面泛指交叉业务逻辑。事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

(2) 连接点(JoinPoint)

连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

(3) 切入点(Pointcut)

切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为final修饰的类是不能被修改和增强的。

(4) 目标对象(Target)

目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。

(5) 通知(Advice)

通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。 Advice
也叫增强。 换个角度来说, 通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间

2. AspectJ 对 AOP 的实现(常用)

AOP是一种编程思想,AspectJ和Spring都对其进行了实现,但AspectJ实现方式更为简捷,使用更为方便,而且还支持注解式开发,故在开发时,一般使用 AspectJ 的实现方式。

1. 动态代理的实现方式

在spring.xml配置文件中声明AspectJ中的自动代理生成器(aspectj-autoproxy),创建目标对象的代理。AspectJ是修改目标对象的内存结构,创建代理。实际上目标对象就是代理对象

使用自动代理生成器需要加入spring-aop.xsd约束文件,idea会自动加入

<aop:aspectj-autoproxy proxy-target-class="true"/>

参数:proxy-target-class

  • 为true时使用cglib动态代理,false时使用jdk动态代理。
  • 不指定该参数时,类实现了接口用jdk动态代理,否则用cglib动态代理
2. 切入点表达式

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution ( [ modifiers-pattern ] 访问权限类型
			ret-type-pattern 返回值类型 
			[ declaring-type-pattern ] 全限定性类名
			name-pattern(param-pattern)方法名(参数类型和参数个数)
			[ throws-pattern ] 抛出异常类型 )

切入点表达式要匹配的对象就是目标方法的方法名。所以, execution 表达式中明显就
方法的签名。 注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

符号意义
*0至任意多个字符
..用在方法参数中:表示任意多个参数
用在包名中:表示当前包及其子包路径
+用在类名后:表示当前类及其子类
用在接口后:表示当前接口及其实现类

举例:

execution(public * *(..))
指定切入点为:任意公共方法。

execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后
面必须跟“*”,表示包、子包下的所有类。

execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
3. 使用注解实现通知

AspectJ 中常用的通知有五种类型:

  1. 前置通知@Before
  2. 后置通知@AfterReturning
  3. 环绕通知@Around
  4. 异常通知@AfterThrowing
  5. 最终通知@After
(1) 切面类@Aspect

@Aspect:来自aspectj框架中的注解, 表示当前类作为切面类身份

  • 切面类:是包含切面功能的类, 是用来给业务方法增加功能的。在切面类中使用通知,通过在切面类中定义方法来实现通知的功能
@Aspect
public class MyAspect {}
(2) 前置通知@Before
  • 属性:value ,切入点表达式,表示切面功能执行的位置(在哪个方法上执行切面)
  • 特点:
    1. 目标方法之前先执行的。
    2. 不会改变目标方法的执行结果
    3. 不会影响目标方法的执行
  • 方法定义的要求
    1. public修饰
    2. 没有返回值(void)
    3. 方法名自定义
    4. 方法可以有参数,但参数不能自定义;也可以没有参数
  • 方法中的参数:
    JoinPoint:
    • JoinPoint:表示切入点的方法,通过JoinPoint能够到方法执行时的信息,例如方法名称,方法的参数等
    • 注意:必须是参数列表的第一个参数
@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
public void myBefore(JoinPoint jp) { 
    //方法就是切面的功能代码
    System.out.println("前置通知:在目标方法之前执行日志功能,输出时间:" + new Date());
    //获取方法的定义
    jp.getSignature();
    //获取方法执行时的参数
    Object args[] = jp.getArgs();
}
(2) 后置通知@AfterReturning
  • 属性:
    • value ,切入点表达式
    • returning 自定义的变量,表示目标方法的返回值。自定义的变量名必须和通知方法中的参数名一样
  • 特点:
    1. 目标方法之后执行的。
    2. 能够获取到目标方法的执行结果,但不会改变目标方法的执行结果
    3. 不会影响目标方法的执行
  • 方法定义的要求与参数: 同@Before
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "result")
public void myAfterReturning(JoinPoint jp,Object result){}
(3) 环绕通知@Around
  • 属性:value ,切入点表达式
  • 特点:
    1. 在目标方法前和后都能增强功能。
    2. 可以控制目标方法是否执行
    3. 可以修改目标方法的执行结果
  • 方法定义的要求
    1. 方法是public
    2. 方法必须有返回值,推荐使用Object,表示目标方法返回值
    3. 方法必须有参数 ProceedingJoinPoint
  • 方法中的参数 ProceedingJoinPoint的方法:
    1. proceed():执行目标的方法
    2. getArgs():获取目标方法的执行时的参数
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    //获取方法执行时参数
    Object args[] = pjp.getArgs();

    //执行目标方法
    res = pjp.proceed(); //doFirst

    //修改目标方法的执行结果,改为其他值
    res = "Hello Aspectj";

    //返回目标方法的执行结果
    return res;
    }
(4) 异常通知@AfterThrowing
  • 属性:
    • value ,切入点表达式
    • throwing 自定义的变量,表示目标方法抛出的异常对象。
  • 特点:
    1. 在目标方法抛出异常时才执行的。可以看做是对目标方法的监控程序。
    2. 它不是异常处理程序,异常还是会抛出的。
  • 方法定义的要求:同@Before
  • 方法中的参数:
    • JoinPoint:同@Before
    • Exception:throwing属性中抛出的异常类,变量名要与throwing属性的变量名相同
  • 作用:
    1. 记录异常信息, 记录到日志文件,或者数据库
    2. 通知开发人员,发送邮件,短信等等。
(5) 最终通知@After
  • 属性:
    • value ,切入点表达式
  • 特点:
    1. 目标方法之后执行的。
    2. 总是会被执行的, 不管目标方法是有异常,都会执行
    3. 一般是做资源回收的。程序最后要执行的代码
  • 方法定义的要求与参数: 同@Before
(6) 定义通用切入点表达式@Pointcut

@Pointcut:定义和管理切入点注解,当切面类中,有多个通知,使用相同的切入点,可以使用@Pointcut对切入点表达式做统一管理

  • 属性:value 切入点表达式
  • 位置:在一个自定义的方法上面, 这个方法不需要代码。这个方法的名称看做是切入点表达式的别名。 其他的通知注解中使用方法的名称就代表了切入点表达式
@After(value = "mypt()")
public void myAfter(){
    System.out.println("执行最终通知,总是会被执行的代码");
}

@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void mypt(){
    //不需要代码
}
4. 使用xml文件实现通知

在spring的配置文件中(applicationContext.xml)进行配置:

<!--声明目标对象-->
<bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>

<!--声明作为切面使用的类-->
<bean id="logAspect" class="com.bjpowernode.handler.LogAspect"/>

<!--配置aop:把注解实现的功能都在这里配置-->
<aop:config>
    <!--声明切入点表达式
        expression:切入点表达式
        id:自定义的表达式名称(可以在其他地方使用这个表达式的id)
    -->
    <aop:pointcut id="somePt" expression="execution(* *..SomeServiceImpl.doSome(..))"/>
    <!--配置切面的功能, 配置@Aspect注解的相同功能
        ref:bean的id,表示这个bean对象作为切面类使用
    -->
    <aop:aspect id="myAspect" ref="logAspect">
        <!--
          aop:before:表示通知类型,相当于 @Before
          method:切面类中的方法名称, 这个方法是增加功能的方法
          pointcut-ref:切入点表达式的id
        -->
        <aop:before method="printLog" pointcut-ref="somePt"/>
    </aop:aspect>
</aop:config>
5. AspectJ的使用注解开发

AspectJ使用简单开发的基本思路:

  1. 新建maven模块,使用quick-start模板
  2. 加入maven依赖
    1. spring依赖
    2. aspectj框架的依赖
    3. junit依赖
<!--单元测试-->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>
<!--aspectj框架的依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>
</dependencies>
  1. 创建业务接口
public interface SomeService {
    void doSome(String name, int age);
}
  1. 创建业务接口的实现类,定义业务方法的功能
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, int age) {
        System.out.println("====执行了业务方法doSome===");
    }
}
  1. 创建普通类,当做切面类角色使用
    1. 在类的上面加入@Aspect注解
    2. 在类中定义方法,方法就是切面要执行的功能,要在方法的上面加入aspectj框架中表示通知的注解,例如:前置通知@Before(value=“确认表达式”)
@Aspect
public class MyAspect {
    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint jp) { 
        //方法就是切面的功能代码
        System.out.println("前置通知:在目标方法之前执行日志功能,输出时间:" + new Date());
    }
}
  1. 创建spring的配置文件
    1. 声明目标类对象
    2. 声明切面类对象
    3. 声明aspectj框架中的自动代理生成器标签。目的是创建代理对象
<?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 id="someService" class="com.bjpowernode.ba08.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.bjpowernode.ba08.MyAspect" />

    <!--声明aspectj中的自动代理生成器(aspectj-autoproxy),创建目标对象的代理
        aspectj是修改目标对象的内存结构,创建代理。实际上目标对象就是代理对象

        使用自动代理生成器需要加入spring-aop.xsd
		参数:
        proxy-target-class:true使用cglib动态代理,false使用jdk动态代理
        不指定该参数时,类实现了接口用jdk动态代理,否则用cglib动态代理
    -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
  1. 测试代理的使用
public class MyTest
{
    @Test
    public void test01(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //获取目标对象,就是代理对象
        SomeService service = (SomeService) ctx.getBean("someService");

        System.out.println("service是代理对象:"+service.getClass().getName());

        //通过代理执行业务方法,实现切面功能的执行
        service.doSome("zhangsan", 20);
    }
}

3. Spring 对 AOP 的实现

由于使用配置文件实现通知比较繁琐,在实际中用的很少,所以这里只介绍一个环绕通知

在spring的配置文件中(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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="serviceTarget" class="com.bjpowernode.service.impl.HelloServiceImpl" />

    <bean id="aroundAspect" class="com.bjpowernode.handler.MyInterceptor" />

    <!--声明spring的代理工程类,创建目标对象的代理对象
        实现的功能就是jdk中的Proxy.newProxyInstance(类加载器,接口,InvocationHandler)方法的功能
    -->
    <bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--目标对象-->
        <property name="target" ref="serviceTarget" />
        <!--接口-->
        <property name="proxyInterfaces" value="com.bjpowernode.service.HelloService"/>
        <!--增强的类-->
        <property name="interceptorNames" value="aroundAspect" />
    </bean>
</beans>

4. AOP的作用

  1. 减少重复代码
  2. 专注业务功能
  3. 实现业务功能和非业务功能的解耦合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值