注解开发与Aop
注解开发:
1. 注解实现自动装配:
在使用注解之前我们还需要在bean文件中加入注解驱动(在spring4之后使用注解开发要保证 aop 包导入了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | <?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:context="http://www.springframework.org/schema/context"     xsi:schemaLocation="http://www.springframework.org/schema/beans         https://www.springframework.org/schema/beans/spring-beans.xsd         http://www.springframework.org/schema/context         https://www.springframework.org/schema/context/spring-context.xsd">          <context:annotation-config/>          <bean id="cat" class="ruoxijun.pojo.Cat" p:name="猫"/>     <bean id="dog" class="ruoxijun.pojo.Dog" p:name="狗"/>     <bean id="people" class="ruoxijun.pojo.People">         <property name="name" value="若惜君"/>     </bean> </beans>
   | 
 
- 使用 @Autowired 注解:
 
1 2 3 4 5 6 7 8 9 10
   | @Data public class People {          @Autowired      private Cat cat;          @Autowired(required=false)      private Dog dog;     private String name; }
   | 
 
原理 :先按照 类型 在容器中寻找相应组件,找到多个时根据 bean id 匹配(使用变量名与id进行匹配),未找到时报错。
- @Autowired 和 @Qualifier(value = “id”) 结合使用可指定装配某一个bean:
 
1 2 3
   | @Autowired  @Qualifier(value = "cat")  private Cat cat;
   | 
 
扩展:**@Autowired** 和 @Qualifier(value = “id”) 还能为方法使用,注意使用 Autowired 注解的方法会装配后自动运行。
1 2 3 4
   | @Autowired  private void show(@Qualifier("a") A a){      System.out.println(a); }
   | 
 
- java中jdk自带有一个和 @Autowired 类似的注解 @Resource:
 
1 2 3 4 5 6 7 8
   | @Data public class People {     @Resource      private Cat cat;     @Resource(name = "dog")      private Dog dog;     private String name; }
   | 
 
注意Resource与Autowired的区别:Resource先按照名称装配,其次按照类型装配,扩展性强。
Resource与Autowired都可以标注在属性的set方法上,且Autowired只能在加入了IOC容器中的类才能使用。
2. 注解实现bean功能:
在使用这些注解之前还需要在bean文件中添加下面这句,spring会去自动扫描base-package对应的路径或者该路径的子包下面的java文件,如果扫描到文件中带有@Service,@Component,@Repository,@Controller等这些注解的类,则把这些类注入到IOC容器中:
1 2
   |  <context:component-scan base-package="ruoxijun.pojo"/>
 
  | 
 
1 2 3 4 5 6
   |  <context:component-scan base-package="ruoxijun.service">     
      <context:exclude-filter type="annotation" expression="注解全类名"/> </context:component-scan>
 
  | 
 
1 2 3 4 5
   | 
  <context:component-scan base-package="ruoxijun.service" use-default-filters="false">     <context:include-filter type="assignable" expression="指定类全类名"/> </context:component-scan>
 
  | 
 
注意: 此注解同时启用了注释驱动的 <context:annotation-config/> 配置
- 注解实现bean( @Component )与属性值注入( @value ):
 
1 2 3 4 5 6 7
   |  @Component  public class Cat {          @Value("猫")     private String name; }
 
  | 
 
java中web开发按照MVC分为dao,service,controller层。
| 包层 | 
注解 | 
| dao | 
@Repository | 
| service | 
@Service | 
| controller | 
@Controller | 
这三个注解的作用与 @Component 作用相同只是表达的含义不同而已。
1 2 3
   |  @Scope("singleton")  @Scope("prototype") 
 
  | 
 
3. 注解实现配置类(bean文件):
1 2 3 4 5
   | @Data public class User {     @Value("若惜君")     private String name; }
   | 
 
- 利用 @Configuration 实现一个 bean 配置文件
 
1 2 3 4 5 6 7 8
   | @Configuration  public class Config {     @Bean           public User user(){         return new User();     } }
   | 
 
- 使用
AnnotationConfigApplicationContext构造器获取Spring上下文对象: 
1 2 3
   | ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); User user = (User) context.getBean("user");
 
   | 
 
使用组件 bean 对象调用方法也同样可以拿到对应对象,注解有个 proxyBeanMethods 属性默认为 true(单实例)。
1 2 3 4
   |  @ComponentScan("ruoxijun")
  @Import(Config.class) 
 
  | 
 
AOP知识储备:
  OOP:Object Oriented Programming 面向对象编程
  AOP:Aspect Oriented Programming 面向切面编程(基于OOP)
  面向切面编程:指在程序运行期间,将某段代码 动态切入到指定位置 运行。

1. 代理模式:
客户类与目标类之间存在中介类,中介类就称之为代理类。
- 优点:可以限制客户类直接访问目标类,实现了解耦。同时完成业务时可再在其中附加一些功能,完成功能增强。
 
- 缺点:目标类较多时会产生大量的代理类。
 
2. 动态代理(了解原理):
利用jdk反射机制创建代理对象,并动态的指定要代理的目标类。我们只需创建目标对象,后利用如下两个类:
1 2 3 4 5 6 7 8 9 10 11
   | InvocationHandler:实现动态代理的接口,invoke方法,表示代理对象要执行的方法,如下参数:     Object proxy:代理对象     Method method:method.invoke(目标对象实例, args)           Object[] args:目标对象方法的参数           Proxy:创建动态代理对象,通过它静态的 newProxyInstance 方法,如下参数:     ClassLoader loader:通过反射获取类加载器:目标类对象.getClass().getClassLoader()     Class<?>[] interfaces:目标对象实现的所有接口数组:目标类对象.getClass().getInterfaces()     InvocationHandler h:代理类要完成功能(上方实现的 InvocationHandler 接口类)
   | 
 
3. 动态代理实现案例:
jdk动态代理的弊端时目标类必须有实现的接口,所以需要为目标类编写接口。
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
   |  class B implements A{     @Override      public void show() { System.out.println("A的show方法执行了"); } }
  public interface A {     void show();     public static void main(String[] args) {                  A a = new B();                  InvocationHandler in = new InvocationHandler() {             @Override             public Object invoke(Object proxy, Method method, Object[] args)                     throws Throwable {                 System.out.println("方法执行之前");                 Object o = method.invoke(a,args);                 System.out.println("方法执行之后");                 return o;             }         };                  ClassLoader loader = a.getClass().getClassLoader();                  Class<?>[] interfaces = a.getClass().getInterfaces();                  A o = (A) Proxy.newProxyInstance(loader,interfaces,in);                  o.show();     } }
 
  | 
 
AOP的实现:
Spring使用AOP需要导入 aspectjweaver 依赖包:
1 2 3 4 5 6
   |  <dependency>     <groupId>org.aspectj</groupId>     <artifactId>aspectjweaver</artifactId>     <version>1.9.6</version> </dependency>
 
  | 
 
1. API接口方式实现:
- MthodBeforeAdvice:目标方法实施前
 
- AfterReturningAdvice:目标方法实施后
 
- ThrowsAdvice 异常抛出
 
- IntroductionAdvice 为目标类添加新的属性和方法。可以构建组合对象来实现多继承
 
- MethodInterceptor 方法拦截器,环绕在方法执行前之前,在方法执行后之前
 
1 2 3 4 5 6 7 8
   | public interface UserService {      void add();     void delete(); } public class UserServiceImpl implements UserService {      public void add() { System.out.println("add User"); }     public void delete() { System.out.println("delete User"); } }
  | 
 
MethodBeforeAdvice接口 根据需求在方法执行前实现额外功能:
1 2 3 4 5 6 7
   | public class UserBefore implements MethodBeforeAdvice {      @Override      public void before(Method method, Object[] objects, Object o) throws Throwable {         System.out.println("MethodBeforeAdvice:"+o.getClass().getName()+                 "类"+method.getName()+"方法");     } }
  | 
 
AfterReturningAdvice接口 根据需求在方法执行后实现额外功能:
1 2 3 4 5 6 7 8
   | public class UserAfter implements AfterReturningAdvice {      @Override     public void afterReturning(Object o, Method method, Object[] objects, Object o1)             throws Throwable {         System.out.println("AfterReturningAdvice:"+o1.getClass().getName()+                 "类"+method.getName()+"方法,返回"+o);     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  <bean id="userService" class="ruoxijun.service.UserServiceImpl"/> <bean id="userBefore" class="ruoxijun.proxy.UserBefore"/> <bean id="userAfter" class="ruoxijun.proxy.UserAfter"/>
 
  <aop:config>     
      <aop:pointcut id="pointcut"                   expression="execution(* ruoxijun.service.UserServiceImpl.*(..))"/>     
 
      <aop:advisor advice-ref="userBefore" pointcut-ref="pointcut"/>          <aop:advisor advice-ref="userAfter"                  pointcut="execution(* ruoxijun.service.UserServiceImpl.*(..))"/> </aop:config>
 
  | 
 
1 2 3 4 5 6
   | ApplicationContext context = new         ClassPathXmlApplicationContext("applicationContext.xml");
 
  UserService userService = (UserService) context.getBean("userService"); userService.add();
   | 
 
2. 自定义类实现:
之前我们分别使用两个类分别实现两个接口的方式,在目标方法执行前后的额外功能实现,再在bean文件中配置组合成了代理类。现在我们需要在一个自定义类中,定义在目标方法执行前后的额外功能实现,通过这个自定义类和bean文件配置来实现代理类。
1 2 3 4 5 6 7 8 9 10
   | public class UserProxy {          public void before(){         System.out.println("目标方法执行前");     }          public void after(){         System.out.println("目标方法执行后");     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | <bean id="userProxy" class="ruoxijun.proxy.UserProxy"/>
  <aop:config >          <aop:aspect ref="userProxy">                  <aop:pointcut id="pointcut" expression="execution(* ruoxijun.service.UserServiceImpl.*(..))"/>                  <aop:before method="before" pointcut-ref="pointcut"/>                  <aop:after method="after" pointcut-ref="pointcut"/>     </aop:aspect> </aop:config>
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   |  <aop:aspect ref="countAspect" order="1">     
      <aop:after-returning method = "aspectAfterReturning"                          pointcut-ref = "pointcut"                          returning = "result"/>     
      <aop:after-throwing method = "aspectAfterThrowing"                         pointcut-ref = "pointcut"                         throwing = "exception"/> </aop:aspect>
 
  | 
 
3. 注解实现AOP:
1 2 3 4
   |  <aop:aspectj-autoproxy/>
  <bean id="userProxy" class="ruoxijun.proxy.UserProxy"/>
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | @Aspect  public class UserProxy {          @Before("execution(* ruoxijun.service.UserServiceImpl.*(..))")     public void before(){         System.out.println("目标方法执行前");     }          @After("execution(* ruoxijun.service.UserServiceImpl.*(..))")     public void after(){         System.out.println("目标方法执行后");     }          @Around("execution(* ruoxijun.service.UserServiceImpl.*(..))")     public void around(ProceedingJoinPoint pjp) throws Throwable {         System.out.println("环绕开始");         Object re = pjp.proceed();          System.out.println("环绕结束");         return re;      } }
   | 
 
常用注解如下:
1 2 3 4 5 6 7 8
   | @Aspect 
  @Before  @After  @AfterReturning  @AfterThrowing 
  @Around 
   | 
 
4. Spring AOP 执行流程:
1 2 3 4 5 6 7 8 9
   | try{     @Before      pjp.procced(args)      @AfterReturning  } catch(Exception exception) {     @AfterThrowing  } finally {     @After  }
  | 
 
1. spring 4 执行流程:
正常执行顺序:Before -> After -> AfterReturning
异常执行顺序:Before -> After -> AfterThrowing
添加环绕通知(环绕优先):环绕前置 -> Before ->方法执行 -> 环绕后置 -> After -> AfterReturning
2. spring 5 执行流程:
正常执行顺序:Before -> AfterReturning -> After
异常执行顺序:Before -> AfterThrowing -> After
添加环绕通知:环绕前置 -> Before ->方法执行 -> AfterReturning -> After -> 环绕后置
AOP细节:
spring注解实现AOP只对通知方法内的参数做检查,对通知方法的修饰符,返回值,方法名都无要求可以任意。但配置(xml)实现AOP时必须保证方法的修饰符是可以让配置文件访问到的。
1. 获取方法基本信息:
1 2 3 4 5 6 7 8 9
   | @Before("execution(* ruoxijun.service.CountImpl.*(..))")
  public void aspectBefore(JoinPoint joinPoint){          Object[] args = joinPoint.getArgs();          Signature signature = joinPoint.getSignature();     signature.getName();  }
  | 
 
2. 获取方法返回值:
1 2 3 4 5 6
   | @AfterReturning(value = "execution(* ruoxijun.service.CountImpl.*(..))",         returning = "result")
  public void aspectAfterReturning(Object result){     System.out.println("目标方法的返回值:"+result); }
   | 
 
3. 方法产生异常后获取异常:
1 2 3 4 5 6
   | @AfterThrowing(value = "execution(* ruoxijun.service.CountImpl.*(..))",         throwing = "exception")
  public void aspectAfterThrowing(Exception exception){     System.out.println("目标方法抛出异常:"+exception); }
   | 
 
4. 简化切入点表达式:
1 2 3 4
   | @Pointcut("execution(* ruoxijun.service.CountImpl.*(..))")  public void init(){};
  @Before(value = "init()")
  | 
 
5. expression表达式:
原型:execution(访问权限 返回值类型 方法全类名(参数列表))
统配符:*:任意个字符(在方法参数中表示任意一个参数),..:任意个参数
execution(* ruoxijun.service.UserServiceImpl.*(..))
 expression表达式分为4个部分加上expression关键字为5个部分:
- 第一部分“ * ”号表示返回值,这里表示可以为任何类型
 
- 第二部分为包名
 
- 第三部分为类名,我们这里是
UserServiceImpl这个类,也可以 service.* 表示service包中的所有类 
- 第四部分为方法名和参数,“ * ”号表示所有方法括号内的 “ .. “ 表示任何参数。
 
6. 指定动态代理的实现方式:
开启对aop注解的支持标签中 proxy-target-class 属性能控制动态代理的实现方式,Spring 默认使用JDK基于接口的代理实现即属性值为false , 属性值修改为true则使用 cglib 模拟子类继承的方式 实现动态代理:
1 2
   |  <aop:aspectj-autoproxy proxy-target-class="true"/>
 
  | 
 
动态代理的目标类都需要有接口的实现,并使用时需要利用接口类型接受bean这是因为此时得到的类时一个Porxy类它也实现了该接口,这都是使用jdk实现动态代理。使用cglib进行代理便可以不需要接口给任意类添加代理,返回的bean也是本类型。
spring5 自动根据运行类选择JDK或CGLIB代理,无需设置proxy-target-class属性。
7. 多切面运行:
优先级高的切面前置方法先执行,后置方法越后执行。