0%

IOC与AOP的使用小结

一、bean的几种创建方式(IOC)

1. 使用构造函数创建bean

1.1 使用默认构造函数创建bean

<bean id=.. class=.. />, 则默认调用无参构造函数来创建bean,并装入容器中. 此时如果类中没有默认构造函数,那么对象将无法被创建

1.2 使用有参构造函数创建bean

1
2
3
4
<bean id=".." class="..">
<constructor-arg name="变量名" value="变量值"></constructor-arg>
<constructor-arg name="变量名" ref="其他bean的id"></constructor-arg>
</bean>

2. 使用工厂方法创建bean

2.1 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

例如: 我想通过类GameFactory中的getGame()方法来创建一个Game对象

那么在配置文件中:

1
2
3
4
<!--先配置GameFactory类的信息-->
<bean id="GF" class="cn.xjtu.czf.factory.GameFactory"></bean>
<!--然后配置Game类的信息, 但创建对象的来源不再是Game类的全限定名, 而是创建Game对象的类以及对应方法-->
<bean id="MyGame" factory-bean="GF" factory-method="getGame"></bean>

然后就可以通过”MyGame”这个关键字来获取Game的对象了。

2.2 使用静态工厂方法里的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

依然用StaticGameFactory的例子来说明,只不过getGame()方法变为静态方法,那么就应该像如下这样弄.

1
<bean id="MyGame" factory-bean="cn.xjtu.czf.factory.StaticGameFactory" factory-method="getGame"></bean>

3. 使用setter方法来创建bean

注意,集合类型只能通过setter来进行初始化内容(比如想创建一个存入元素0的ArrayList,那么只能用这个方法,具体规则用到再查吧.. 这里不记了)..

4. 使用注解来创建bean

1. 标记了@Controller, @Service, @Repository@Component的类, 用于创建bean

  • @Controller: 表现层
  • @Service: 业务层
  • @Repository:持久层
    1
    2
    3
    4
    5
    /* 等价于 <bean id="aaa" class="包.AAA" /> */
    @Component("aaa")
    public class AAA{
    ...
    }

2. 使用配置类@Configuration+@Bean来创建

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@ComponentScan("czf")
public class Configurer {
@Bean(name="accountService")
public IAccountService getAccountService( Account account ){
return new IAccountServiceImpl(account); // 形参的account可以是容器里的id
}
@Bean(name="account")
public Account getAccount(){
return new Account();
}
}
  • @Configuration

    • 作用:指定当前类是一个配置类
    • 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。例如 AnnotationConfigApplicationContext(配置类名.class)
  • @Bean

    • 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
    • 属性:
      • name:用于指定bean的id。当不写时,默认值是当前方法的名称
    • 细节:
      • 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象(bean的id就是形参的名字)。查找的方式和Autowired注解的作用是一样的
    • 其中的initMethoddestroyMethod可以分别指派初始化和销毁方法.(但在多实例的时候,销毁方法不由spring管理,由GC管理..)
  • 配置类的导入:

    • 平级结构:
      使用AnnotationConfigApplicationContext(配置类1.class,配置类2.class,配置类3.class,...)
    • 树型结构:
      使用AnnotationConfigApplicationContext(根配置类.class),然后对于根配置类 使用:
      • @ContextConfiguration(classes = {配置子类.class}) 或者 @ContextConfiguration(locations = {"classpath:包名/aaa.xml", "classpath*:bbb.xml"})
      • @Import({配置子类1.class, 配置子类2.class,...})
  • 在配置类中使用properties文件来初始化配置类中的成员变量(成员变量可以作为初始化bean的变量,从而降耦)

    • @PropertySource:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Configuration
      @PropertySource(value = "Config.properties")
      public class AccountConfig {
      @Value("${age}")
      private Integer age = null;
      @Value("${money}")
      private Float money = null;
      @Value("${name}")
      private String name = null;
      @Bean("account")
      public Account getAccount(){
      return new Account(name, age, money);
      }
      }
      其中,Config.properties的内容为:
      1
      2
      3
      name=czf
      age=22
      money=1
  • 注入数据的注解

    • @Autowired : spring会自动到容器中去寻找当前@Autowired标注的类型,根据类型来初始化。 但是要注意的是,这里多态也会起作用,如果有多个类实现了同一个接口,而当前变量或对象类型正好是接口类型,那么就会找到多个匹配成功的对象; 然后就会根据变量名去匹配,如果依然匹配不到的话,就会报错; 但我们一般不会把变量名写的和类名一样,因此解决办法就是使用@Qualifier
    • @Qualifier 在给类的成员变量注入的时候,是不可以单独使用的,必须和@Autowired在一起使用; 但是在给方法参数注入的时候,是可以单独使用的; 使用方法就是:@Qualifier(value="具体某个类的别名"), 用输入的类名来填充当前需要填充的变量;
    • @Resource 相当于上面两个的合体, 可以直接单独使用,直接通过 类的别名 来确定给当前变量填充的类型,但要注意,这里接收类的别名的参数是name不再是value@Resource(name="某个类的别名")
    • 基本类型和String类型的注入(@Value(value="数据的值"))
      • 可以使用Spring中的el表达式;
      • SlEL的写法:${表达式} (配合@PropertySource使用)
    • 集合类型的注入,只能通过xml来做。
  • Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

    • @Resource:
      • 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;没有能支持@Primary功能没有支持@Autowired(reqiured=false);
    • @Inject:
      • 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
    • @Autowired:Spring定义的; @Resource、@Inject都是java规范

5. 使用Conditional注解

Conditional:按照一定的条件进行判断,满足条件则给容器注册bean

1
2
3
4
5
6
7
8
9
10

...
// cmp是一个继承了Condition接口并实现了match方法的类
// 对于这个match方法,如果返回了true,那么就注入,如果是false就不注入
// 以此来对注入加上一个或多个条件限制
@Conditional({cmp.class})
public class person01{
..
}
...

6. 通过BeanFactory来注册Bean

  • 使用Spring提供的 FactoryBean(工厂Bean);
    1. 默认获取到的是工厂bean调用getObject创建的对象
    2. 要获取工厂Bean本身,我们需要给id前面加一个&,&colorFactoryBean

下面看个示例, 使用实现了FactoryBean接口的ColorFactoryBean类来注册Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {

//返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
// TODO Auto-generated method stub
// 返回的对象会自动添加到容器中
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}

@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Color.class;
}

//是单例?
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
}
}

在配置类中:

1
2
3
4
5
6
7
8
9
10
11
@...
@Configuration
public class Config{
// 尽管这里注册的Bean是个工厂,但!实际上注册的对象是“工厂+工厂里的getBean”
// 1. 默认获取到的是工厂bean调用getObject创建的对象
// 2. 要获取工厂Bean本身,我们需要给id前面加一个&,&colorFactoryBean
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
}

7. 想在自定义Bean中获取Spring框架底层的Bean(例如 ApplicationContext)

  • 自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx);
  • 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware;
  • 把Spring底层一些组件注入到自定义的Bean中;
  • xxxAware:功能使用xxxProcessor(继承了);
    • ApplicationContextAware==》ApplicationContextAwareProcessor;

三、Bean的初始化和销毁方法

1. 使用Bean(initMethod=”..”, destoryMethod=”..”)

2. 让Bean实现InitializingBean接口和DisposableBean接口

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class Cat implements InitializingBean, DisposableBean {
@Override
public void destory() throws Exception {
...
}

@Override
public void afterPropertiesSet() throws Excetpion{
...
}
}

3. 使用JSR250

@PostConstruct,@PreDestory

  • @PostConstruct在bean创建完成并且属性赋值完成;来执行初始化方法
  • @PreDestory在容器销毁bean之前通知我们进行清理工作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Dog{
    ...
    // 对象创建并赋值之后调用
    @PostConstruct
    public void init(){
    ...
    }
    ...
    // 容器移除对象之前执行这个方法
    @PreDestory
    public void destory(){
    ...
    }
    ...
    }

4. 在Bean的初始化方法调用前/后的预置方法

BeanPostProcessor接口有2个方法需要去实现:

  1. postProcessBeforeInitialization:在bean初始化之前执行
  2. postProcessAfterInitialization:在初始化之后执行
    对于Bean而言,实现了上面这两个接口,就可以在Bean初始化之前和之后做一些事情.

二、扫描包

扫描包内注解.

1. xml方式扫描

1
<context:component-scan base-package="包"/>

2. 注解方式扫描

使用注解类

  • @ComponentScan
    用于通过注解指定spring在创建容器时要扫描的包.我们使用此注解就等同于在xml中配置了:
    1
    <context:component-scan base-package="czf"></context:component-scan>

@Filter通过excludeFilterincludeFilter可以实现在扫描的时候扫描部分或排除部分类.

三、面向切面编程(AOP)

0. 注意!

使用AOP时,一定要导入aspectj!!!解析切入点表达式需要AspectJ Weaver, 使用环绕通知的ProceedingJoinPoint对象需要AspectJ Runtime!! 不要忘记了!!

1. spring中基于XML的AOP配置步骤

  1. 把通知Bean也交给spring来管理

  2. 使用aop:config标签表明开始AOP的配置

  3. 使用aop:aspect标签表明配置切面

    • id属性:是给切面提供一个唯一标识
    • ref属性:是指定通知类bean的Id。
  4. 在aop:aspect标签的内部使用对应标签来配置通知的类型

    • aop:before:表示配置前置通知

      • method属性:用于指定Logger类中哪个方法是前置通知

      • pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

        • 切入点表达式的写法:

          • 关键字:execution(表达式)

          • 表达式:
            访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)

          • 标准的表达式写法:
            public void com.itheima.service.impl.AccountServiceImpl.saveAccount()

          • 访问修饰符可以省略
            void com.itheima.service.impl.AccountServiceImpl.saveAccount()

          • 返回值可以使用通配符,表示任意返回值

            • com.itheima.service.impl.AccountServiceImpl.saveAccount()
          • 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个......AccountServiceImpl.saveAccount())

          • 包名可以使用..表示当前包及其子包* ..AccountServiceImpl.saveAccount()
            类名和方法名都可以使用*来实现通配: * *..
            .*()

          • 参数列表:

            • 可以直接写数据类型:
              • 基本类型直接写名称int
              • 引用类型写包名.类名的方式 java.lang.String
            • 可以使用通配符表示任意类型,但是必须有参数:
              • 可以使用..表示有无参数均可,有参数可以是任意类型
          • 全通配写法:

            • * ...*(..)
          • 实际开发中切入点表达式的通常写法:

            • 切到业务层实现类下的所有方法
              • * com.itheima.service.impl..(..)
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                <!-- 步骤1 -->
                <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
                <bean id="logger" class="com.itheima.utils.Logger"></bean>
                <!--步骤2: 配置AOP-->
                <aop:config>
                <!--步骤3: 配置切面 -->
                <aop:aspect id="logAdvice" ref="logger">
                <!-- 步骤4: 配置通知的类型,并且建立通知方法和切入点方法的关联-->
                <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
                </aop:aspect>
                </aop:config>
  5. 重用切入点表达式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!--配置AOP-->
    <aop:config>
    <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
    此标签写在aop:aspect标签内部只能当前切面使用。
    它还可以写在aop:aspect外面,此时就变成了所有切面可用
    -->
    <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
    <!--配置切面 -->
    <aop:aspect id="logAdvice" ref="logger">
    <!-- 配置前置通知:在切入点方法执行之前执行-->
    <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
    <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
    <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
    <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
    <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
    <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
    <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
    </aop:aspect>
    </aop:config>
  6. 环绕切入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <aop:config>
    <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
    此标签写在aop:aspect标签内部只能当前切面使用。
    它还可以写在aop:aspect外面,此时就变成了所有切面可用
    -->
    <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
    <!--配置切面 -->
    <aop:aspect id="logAdvice" ref="logger">
    <!-- 配置环绕通知 详细的注释请看下面的Logger类中-->
    <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
    </aop:aspect>
    </aop:config>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;

/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
...
...
... (前置,后置,异常,最终通知的实现..)
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数

System.out.println("前置通知");

rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

System.out.println("后置通知");

return rtValue;
}catch (Throwable t){
System.out.println("异常通知");
throw new RuntimeException(t);
}finally {
System.out.println("最终通知");
}
}
}

2. spring中基于注解的AOP配置

  1. 扫描注解

    • 在xml中添加<context:component-scan base-package="com.itheima"></context:component-scan>来扫描包,或者使用配置类来扫描..
  2. 开启spring注解的AOP支持

    • 在xml中添加<aop:aspectj-autoproxy></aop:aspectj-autoproxy> 或者在配置类上加 @EnableAspectJAutoProxy 开启spring注解AOP的支持
  3. 配置切面

    • 在增强类上(如上面的Log类),加@Aspect注解..表明该类作为切面,用于增强”切入点”
  4. 配置切入点

    • 切入点表达式的写法:
      1
      2
      3
      4
      // 下面,pt1()就是切入点表达式"execution(* com.itheima.service.impl.*.*(..))"的id,
      // pt1(),函数名加括号,就是id!! 一个字母都不能少!!
      @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
      private void pt1(){}
  5. 配置通知:前置通知,后置通知,异常通知,最终通知

    • 在增强方法上,加上注解@Before("切入点表达式id"), @After(..), @AfterReturning(..), @AfterThrowing(..)这里需要注意的是, 尽量不要使用上面这几个注解来做,因为它们的调用顺序可能会出问题.. 所以尽量还是用xml的方法来做..如果非要使用注解的话,那就用环绕注解来做.
  6. 配置环绕通知:

    • 在增强类中的方法加上@Around("切入点表达式id")注解,就说明当前方法是增强方法.

先来看一个例子:

1. Calculator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package czf.bean;

import org.springframework.stereotype.Component;

/**
* @author czf
* @Date 2020/4/27 11:36 下午
*/
@Component("calculator")
public class Calculator {
public int cal(int x, int y){
return x/y;
}
}

2. LogAspect.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package czf.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
* @author czf
* @Date 2020/4/27 11:35 下午
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(public int czf.bean.Calculator.*(..))")

public void calculator() {
}

@Before("calculator()")
public void BeforeLog(JoinPoint joinPoint) {
// 通过JoinPoint来获取切入点信息(方法信息)
Object [] args = joinPoint.getArgs();
System.out.println("开始执行" + joinPoint.getSignature().getName() + "...");
System.out.println("输入参数为: " + Arrays.asList(args));
}

@After("calculator()")
public void AfterLog(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getDeclaringTypeName() + "执行完毕");
}

@AfterReturning(value = "calculator()", returning = "object")
public void AfterReturningLog(JoinPoint joinPoint, Object object) {
// 通过JoinPoint来获取切入点信息(方法信息)
// 通过指定 returning = "xxx", xxx为“接”返回值的那个形参的名称
// 注意!!! JointPoint必须写在形参的第一个位置,不然会error
System.out.println("返回值为:" + object);
}

@AfterThrowing(value = "calculator()", throwing = "ex")
public void ExceptionLog(JoinPoint joinPoint, Exception ex) {
// 通过JoinPoint来获取切入点信息(方法信息)
// 通过指定 throwing = "xxx", xxx为“接”异常的那个形参的名称
// 注意!!! JointPoint必须写在形参的第一个位置,不然会error
System.out.println(joinPoint.getSignature().getName() + "出现异常,异常信息:" + ex);
}

/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
@Around("calculator()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数

System.out.println("前置");

rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

System.out.println("后置");

return rtValue;
}catch (Throwable t){ // 这里必须要用Throwable来捕获!!!Exception捕获不到!!
System.out.println("异常");
throw new RuntimeException(t);
}finally {
System.out.println("最终");
}
}


}

最后再总结一下AOP的使用

  • 1、导入aop模块;Spring AOP:(spring-aspects)

  • 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)

  • 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;

    • 通知方法:
      • 前置通知(@Before):logStart:在目标方法(div)运行之前运行
      • 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
      • 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
      • 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
      • 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
  • 4、给切面类的目标方法标注何时何地运行(通知注解);

  • 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;

  • 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)

  • [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】

    • 在Spring中很多的 @EnableXXX;

    AOP三步:

  • a、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)

  • b、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)

  • c、开启基于注解的aop模式;@EnableAspectJAutoProxy

四、记录一些点

1. 单元测试中使用spring

@ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)`联合使用用来测试,

@RunWith(SpringJUnit4ClassRunner.class)用于替换单元测试类的启动器(本身的启动器无法使用spring的IOC)。

@ContextConfiguration(locations={"aaa.xml"})@ContextConfiguration(classes={"配置类.class"}) 进行扫描操作,用于获取装有Bean的容器.

下面看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Config.class})
public class XXXTest {
@Resource(name="accountService")
private IAccountService ac = null;

@Autowired
private Account act = null;

@Resource(name = "myjpt")
private CzfJoinPointer jp = null;

@Test
public void TestSave(){
ac.save(act);
}

@Test
public void TestLog(){
jp.helloAccount();
}
}

2. 在使用AOP的时候,需要额外依赖aspectjweaver!!!! 别忘记了!!!

3. 如果配置了AOP,那么只有是从容器里拿到的Bean才是被AOP增强过的,AutoWired等其他途径初始化得到的都是原始的Bean!

4. 从容器中使用getBean获取到的对象, 如果是实现了某个接口的类的话,一定要强制转成其接口类

因为spring用的是代理类,而不是实际的子类
关于代理,百度一下spring 代理,就理解其机制了
就是说,虽然你知道你拿到的是UserServiceImpl类型的对象,但实际上,spring存的是一个Proxy对象,该Proxy指向了一个UserServiceImpl对象,你直接对Proxy进行强制转换自然是失败的。


最后附上一个Spring文档: