69代理:Day69.Spring: 使用注解开发、代理模式、AOP、切面、通知、AOP对IOC的影响、动态代理技术:JDK | cglib

目录

 

总结

使用注解开发

一、标记与扫描  ★

1.基本类型:HappyComponent的注解实现IoC (@Component  @Value)

2.引用类型:HappyMachine的注解实现IoC  (@Autowired)

二、分层开发自动装配  ★

注解管理IoC的总结

1. @Autowired的过程  ★

三、全注解开发   @ComponentScan  

四、Spring5整合Junit4  @RunWith @ContextConfiguration  ★

AOP (面向切面编程)  ★★★

一、代理模式  ★★

1. 静态代理

2. 动态代理 

二、AOP术语 (核心概念)  ★

三、AOP第一个示例:添加日志(准备)

四、AOP第一个示例:添加日志(实现)@Aspect  @Before.. 

五、重用切入点  @Pointcut

六、获取连接点 (信息)

七、切入点表达式语法  execution(* ... .method())

八、环绕通知  @Around (关于事物的切面)  ★

九、多个切面执行顺序 (切面优先级)  @Order

十、有无接口的动态代理技术 (JDK、CGLIB)  ★

十一、应用AOP 对 IoC中获取bean的影响 ★

十一、AOP 使用XML配置(了解)


总结

  • spring.xml 标签配置:
<!--指定注解扫描基准路径 会自动扫描base-package及其子包下的注解--><context:component-scan base-package="com.atguigu"></context:component-scan><!-- 开启基于注解的AOP功能 --><aop:aspectj-autoproxy/><!-- 启动AOP自动代理 proxy-target-class:底层采用什么动态代理技术 false: 默认 有接口使用JDK动态代理,没有接口使用CGLIB true:不管是否有接口,都使用CGLIB--><aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
  • 创建Bean的注解:

@Component  标记该类,spring配置文件中base-package 扫描后会创建对象并装配到IoC容器

@Controller  标记控制器组件(控制层)

@Service  标记业务逻辑组件(业务逻辑层)

@Repository  标记持久化层组件(持久化、数据访问层)

@Configuration 说明这是一个配置类,要达到全注解开发还需要使用 @ComponentScan 

以上注解本质上相同,只是在@Component  基础上换了名字提高了代码的可读性。 

  • 注入Bean的注解:

@Value  给基本类型的参数注入赋值,可以用在属性、set方法上

@Autowired  给引用类型参数注入赋值,可以用在属性、set、构造方法上

@Qualifier  指定Bean的name;不会单独使用,而是和@Autowired配合使用

  • 全注解开发:

@ComponentScan()  组件扫描根据定义的扫描路径,把符合扫描规则的类装配到IoC容器中。

  • 整合Junit4:

@RunWith(SpringJUnit4ClassRunner.class)  指定Spring为Junit提供的运行器

@ContextConfiguration  指定Spring配置文件的位置

单个文件@ContextConfiguration(locations = {"classpath:spring.xml"})@ContextConfiguration("classpath:spring.xml") (locations{}也可省略)@ContextConfiguration(classes = SimpleConfiguration.class)多个文件@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring2.xml"})
  • AOP 面向切面编程:声明切面类与通知方法

@Aspect  表示这个类是一个切面类

@Before(value)  前置通知方法

@AfterReturning(value) 返回通知方法

@AfterThrowing(value) 异常通知方法

@After(value)  后置通知方法

@Around(value):环绕通知方法 对应整个try...catch...finally结构,包括四种通知的所有功能。
需要在对应方法声明ProceedingJoinPoint类型的形参,proceed() 调用切入点的方法。

value()属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上

@Aspect//这是一个切面类public class LogAspect { //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式) @Before("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void beforeLog(){ System.out.println("[日志] xxx 方法开始执行,参数是:"); }
  • 创建IOC容器对象

BeanFactory IoC容器的基本实现类
ApplicationContext  BeanFactory 的子接口,更多功能。

ClassPathXmlApplicationContext 根据XML配置文件创建IoC容器对象

// ClassPathXmlApplicationContext根据XML配置文件创建IOC容器对象private ApplicationContext iocContainer = new ClassPathXmlApplicationContext("applicationContext.xml");

AnnotationConfigApplicationContext  根据XML配置类创建IoC容器对象 (全注解开发)

​// AnnotationConfigApplicationContext根据配置类创建IOC容器对象private ApplicationContext iocContainerAnnotation = new AnnotationConfigApplicationContext(MyConfiguration.class);

WebApplicationContext  Web项目 

  • 切入点表达式语法

execution (* com.atguigu...method(int i))

  • 重用切入点

@Pointcut ("execution(切入点表达式)")

需要使用在一个方法上,使用时直接传入该方法即可

  • 获取连接点信息

获取方法信息:通知方法加入形参JoinPoint joinPoint方法内调用 getSignature()

获取方法返回值@AfterReturning中通过returning属性设置名称,通知方法中声明对应名称Object类型 形参

获取方法抛出异常:@AfterThrowing中声明throwing属性设定名称,通知方法中声明对应名称声明 Exception类型 形参

环绕通知:声明ProceedingJoinPoint类型的形参,里面包含各种方法,可通过proceed() 调用切入点的方法。

  • 多个切面优先级

@Order(int i)  数值越小越早执行

使用注解开发

1、注解的作用

①注解

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

②扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

一、标记与扫描  ★

1.基本类型:HappyComponent的注解实现IoC (@Component  @Value)

  • 使用@Component 标记该类,默认名称是该类名称首字母小写

  • 使用@Value 基本类型的参数(包括String)赋值,位置可以在setter方法或者成员变量上。
    如果在setter方法上,就会使用反射调用setter方法。
    如果在成员变量上,就是使用反射直接操作Field成员变量。
    不能写在构造方法上

  • 在spring配置文件中指定注解的扫描基准包,base-package,会扫描该包及其子包下的注解,比如@Component,创建对象并放入IoC容器

  • //@Component //默认类名首字母小写@Component(value = "happyComponent")public class HappyComponent{ @Value("engine")//给基本数据类型,包括String private String componentName; @Value("engine") public void setComponentName(String componentName) { this.componentName = componentName; } public void doWork() { System.out.println("doWork01--componentName:"+componentName); }} <!--指明注解扫描基准路径 base-package:会扫描该包及其子包下的注解--><context:component-scan base-package="com.atguigu.pojo"></context:component-scan>

    2.引用类型:HappyMachine的注解实现IoC  (@Autowired)

  • 使用@Autowired标记引用类型的成员变量(除了String),实现自动装配。

  • @Autowired可以写在成员变量、setter方法、构造方法上,均是通过反射实现注入

  • 建议写在成员变量上

  • @Componentpublic class HappyMachine { @Value("BYD") private String machineName;//简单属性 @Autowired //自动装配 private HappyComponent happyComponent;//复杂属性 @Autowired public void setHappyComponent(HappyComponent happyComponent) { this.happyComponent = happyComponent; } @Autowired //构造器中不能有非引用参数 public HappyMachine(HappyComponent happyComponent) { this.happyComponent = happyComponent; } public HappyMachine() { } public HappyMachine(String machineName, HappyComponent happyComponent) { this.machineName = machineName; this.happyComponent = happyComponent; }}

    二、分层开发自动装配  

    控制层@Controller //底层就是Component注解,为了可读性,应用于控制层public class UserController { @Autowired //自动装配 private UserService userService; public void addUser(){ userService.addUser(); }} 业务逻辑层@Service(value = "userService") //默认的名称:userServiceImplpublic class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public void addUser() { userDao.saveUser(); }} 持久化层@Repository(value = "userDao") //底层就是Component注解,为了可读性,应用于Dao层public class UserDaoImpl implements UserDao { @Override public void saveUser() { System.out.println("UserDaoImpl: saveUser"); }} spring.xml<!--指定注解扫描基准路径 会自动扫描base-package及其子包下的注解--><context:component-scan base-package="com.atguigu"></context:component-scan> 测试 @Test public void testIoC2_2(){ //创建IoC的容器 ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); //从IoC获取指定Bean UserController userController = context.getBean("userController",UserController.class); userController.addUser(); }

     异常:没有这样的对象定义。

    NoSuchBeanDefinitionException: No bean named 'userController' available

    原因:配置文件之扫描了pojo包,要改一下,(包下找不到userController对象)

    注解管理IoC的总结

    1. @Autowired的过程  

    • 按照ByType进行自动装配(如果只有一个,肯定可以装配成功)

    • 如果存在多个同类型的Bean,就会尝试ByName(变量名与类名)。如果有匹配的name,自动装配成功。

    • 如果没有匹配的name,就会尝试@Qualifier,匹配bean的name。

    • 如果@Qualifier也不匹配,报异常。

    2. <context:component-scan base-package> 

    • base-package指定扫描的基准包

    • base-package 可以写多个,以逗号隔开

    • 扫描创建Bean的注解:扫描org.springframework.stereotype包下的四个注解

    3. 创建Bean的注解 

    • @Component通用性的注解,实际开发中可以替代@Controller @Service @Repository,但

    • @Controller用在控制层

    • @Service用在业务层

    • @Repository用在DAO层

    注解都写在实现类上,不能写在接口上。Bean的默认名称是类型首字母小写。

    这四个注解本质是相同的,都是@Component,只是为了代码可读性。

    4. 注入Bean的注解 

    • @Value:注入基本类型的参数(setter方法 属性)

    • @Autowired:注入引用类型的Bean(setter方法 构造方法 属性)

    • @Qualifier:指定Bean的name;不会单独使用,而是和@Autowired配合使用

    5. @Autowired的位置 

    • setter方法:底层会使用反射调用setter方法 getMethod(“setUserDao”)

    • 构造方法:底层会使用反射调用构造方法 getContructor()

    • 成员变量:底层使用反射直接操作成员变量 getDeclaredField()

    三、全注解开发   @ComponentScan  

    体验完全注解开发,是为了学习SpringBoot打基础。因为在SpringBoot中,就是完全舍弃XML配置文件,全面使用注解来完成主要的配置。

    使用注解开发,需要实现两个功能,目前XML的作用主要是两个:组件扫描、配置外部Bean

    定义一个配置类

    @Configuration //说明这是一个配置类@ComponentScan(value = "com.atguigu")//组件扫描public class SpringConfig { //一般用于第三方Bean操作。第三方的Bean无法使用注解 @Bean //将当前方法中创建的Bean放入到IoC容器中,Bean的名称就是方法名 public DataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis-example?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"); dataSource.setUsername("root"); dataSource.setPassword("*******"); return dataSource; }}

    测试类 

    //使用全注解开发 @Test public void testAllAnnotation() throws SQLException { //1.创建IoC容器 (使用全注解,此时xml已经不存在了) //ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml"); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); //2.congIoC容器中获取Bean DataSource dataSource = context.getBean("dataSource", DataSource.class); UserController userController = context.getBean("userController", UserController.class); //3.使用Bean System.out.println(dataSource); userController.addUser(); }

    四、Spring5整合Junit4  @RunWith @ContextConfiguration  

    • 好处1:不需要自己创建IOC容器对象了 (ClassPathXmlApplicationContext.getBean())
    • 好处2:任何需要的bean都可以在测试类中直接享受自动装配

    1. 添加依赖

    <!-- Spring的测试包 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version></dependency>

    2.创建测试类

    // junit的@RunWith注解:指定Spring为Junit提供的运行器@RunWith(SpringJUnit4ClassRunner.class)// Spring的@ContextConfiguration指定Spring配置文件的位置@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring2.xml"})public class TestIoC3 { @Autowired private DataSource dataSource; @Autowired private UserController userController; @Test public void testIntegration() throws SQLException { //减少了创建IoC容器、装配的操作,直接使用 System.out.println(dataSource.getConnection()); userController.addUser(); }}

    AOP (面向切面编程)  ★★★

    AOP (Aspect Oriented Programming) 面向切面编程

    通过预编译方式和运行期间动态代理,在不修改源代码的情况下,给程序动态统一的添加功能的一种技术,简称AOP。是spring框架的一个重要内容,可以说是对OOP (面向对象编程) 的补充和完善

    OOP解决纵向的业务问题。

    AOP解决横向的问题,比如日志、安全验证、事务、异常

    一、代理模式  ★★

    二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法时,不再是直接对目标方法进行调用,而是通过代理类间接调用解耦。

    代理类符合了开闭原则:对修改关闭对扩展开放

    • 目标类(TargetSubject):具体实现业务功能的类 (核心逻辑代码);

    • 代理类(ProxyObject):提供一些被代理类功能(核心逻辑代码)之外的额外功能的类;

    • 请求类(RequestObject):业务调用类。

    Day34.反射-动态代理、动态代理与AOP_焰火青年·的博客-CSDN博客

    1. 静态代理

    优点:

  • 不需要去修改目标类代码,就可以增加新的功能(比如日志)。目标类只关注业务即可

  • 代理所有的实现了该接口的目标类。

  • 缺点:

  • 在编译时就已经将接口、被代理类、代理类等确定下来,只能代理一个接口的目标类。代码都写死了,不具备任何的灵活性,共同方法没有进行统一管理。

  • 静态代理类是需要开发者亲自写代码开发出来的,看得见摸得着的。

  • 定义静态代理类,要求代理类实现和目标类实现一样的接口

    /** 静态代理: 实现了角色分离* 关键点:* 关联和目标类一样的接口,可以来指向真正的目标类,从而调用目标类方法* */public class ShoesFactoryStaticProxy implements ShoesFactory { //相同的接口 ShoesFactory shoesFactory; //构造器,set方法,指定目标类 public ShoesFactoryStaticProxy(ShoesFactory shoesFactory) { this.shoesFactory = shoesFactory; } public void setShoesFactory(ShoesFactory shoesFactory) { this.shoesFactory = shoesFactory; } //调用方法 @Override public Shoes production() { beforeLog(); //日志方法 Shoes shoes = shoesFactory.production(); afterLog(); return shoes; } //日志方法 public void beforeLog(){ System.out.println("[日志] 生产开始了!"); } public void afterLog(){ System.out.println("[日志] 生产结束了!"); }}

    测试静态代理类

    public class Test { public static void main(String[] args) { //创建一个经纪人 CalculatorLogStaticProxy proxy = new CalculatorLogStaticProxy(); //指定代理类代理谁 Calculator calculator = new CalculatorPureImpl(); proxy.setCalculator(calculator); //调用代理类的方法 int result = proxy.add(10, 20); System.out.println(result); }}

    目标类 

    //鞋子public class Shoes { public String name;}//鞋子制造厂public interface ShoesFactory { public Shoes production();}//阿迪达斯制造厂public class AdidasShoesFactoryImpl implements ShoesFactory { @Override public Shoes production() { Shoes shoes = new Shoes("阿迪达斯"); return shoes; }}//耐克制造厂public class NikeShoesFactoryImpl implements ShoesFactory { @Override public Shoes production() { Shoes shoes = new Shoes("耐克"); return shoes; }}

    2. 动态代理 

    代理类在程序运行时创建的代理方式被成为动态代理

    相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法(共同方法解耦)。 

    AOP底层原理就是动态代理技术。

    定义动态代理工厂类

    /** 日志代理类的工厂* 代理日志功能,都是接口可以代理* 工厂: 由这个类来产生代理对象并返回* */public class LogDynamicProxyFactory<T> {//泛型接口,可以传入目标接口 private T target; //指向目标类 public void setTarget(T target){ this.target = target; } //创建代理对象并返回 public T getProxy(){ //目标类的类加载器 ClassLoader classLoader = target.getClass().getClassLoader(); //目标类实现的接口 Class<?>[] interfaces = target.getClass().getInterfaces(); /* * 代理对象要干什么? * Object proxy: 代理对象 * Method method: 目标类的方法 比如add(int i,int j) saveUser() * Object[] args: 目标类方法的参数: (int i,int j) * */ InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args) -> { Object result = null; try{ //新皇登基: 前置通知 System.out.println("[日志]" +method.getName()+ " 方法开始执行了,方法的参数是:"+ Arrays.toString(args)); result = method.invoke(target, args);//让大明星拍广告 //寿终正寝: 返回通知 System.out.println("[日志]" +method.getName()+ " 方法执行结束了,结果是:"+result); }catch (Exception e){ //死于非命: 异常通知 System.out.println("[日志]" +method.getName()+ "方法出现了异常,异常信息:"+e.toString()); }finally { //风光大葬: 后置通知 System.out.println("[日志] finally 收尾工作"); } return result; }; return (T) Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); }}

    测试类

    public class TestDynamicProxy { @Test public void testCalculator(){ //创建动态代理工厂类的对象 LogDynamicProxyFactory<Calculator> factory = new LogDynamicProxyFactory(); factory.setTarget(new CalculatorPureImpl()); //由工厂创建动态代理对象 Calculator proxy = factory.getProxy(); int add = proxy.add(10, 20); System.out.println(add); } @Test public void testUser(){ //创建动态代理工厂类的对象 LogDynamicProxyFactory<UserDao> factory = new LogDynamicProxyFactory(); factory.setTarget(new UserDaoImpl()); //由工厂创建动态代理对象 UserDao proxy = factory.getProxy(); proxy.saveUser(); }}

    二、AOP术语 (核心概念)  

    概念描述

    Spring中

    横切关注点对哪些方法进行拦截,拦截后怎么处理
    通知|增强 advice拦截到连接点之后要做的事情。@Before 表现为一个方法。分为前置、后置、异常、最终、环绕五类
    连接点 joinpoint在哪些地方加入通知?被拦截到的目标类的方法。add() sub)
    切入点 pointcut定位连接点的方式。一个表达式,能涵盖所有的连接点。
    切面 aspect类是对物体特征的抽象,切面就是对横切关注点的抽象。@Aspect 修饰的类切面是一个类,包括两部分:通知(做什么)+ 切入点(在哪做)。
    目标 target

    被代理的目标对象。

    被代理的目标对象。(如 NikeFactory)
    代理 proxy向目标对象应用通知之后创建的代理对象。(...DynamicProxy)
    织入 weaving把切面应用到目标上,生成代理对象的过程。

    可以在编译期织入,也可以在运行期织入,Spring采用后者。

    1、横切关注点 

    • 纵向关注点(核心关注点 业务关注点):add()  sub()  findAll()  insert()  OOP解决纵向关注点

    • 横切关注点(非业务的功能) 日志、安全验证、事务、异常。AOP解决横切关注点

    2、通知|增强 (advice) 

    每一个横切关注点上要做的事情称为通知。通知需要写一个方法来实现。也被称为增强。

    就是AOP的要做的事情,比如写日志,比如要进行事务处理。在Spring中,通知表现为一个方法。

    • 前置通知:在被代理的目标方法前执行(新皇登基)@Before

    • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)@AfterReturning

    • 异常通知:在被代理的目标方法异常结束后执行(死于非命)@AfterThrowing

    • 后置通知:在被代理的目标方法最终结束后执行(风光大葬)@After

    • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置和功能。

    3、连接点 (joinpoint) 

     在什么地方加入通知呢? 在add()  sub()  findAll()  在Spring中,连接点是一个方法

    这也是一个纯逻辑概念,不是语法定义的。指那些被拦截到的点。在 Spring 中,可以被动态代理拦截目标类的方法。

    4、切入点 (pointcut) 

    一个表达式,能涵盖所有的连接点的表达式

    execution(* com.atguigu.service.CalculatorImpl.*(..))作用与所有方法

    execution(* com.atguigu.service.CalculatorImpl.add(..)) 只作用域add方法

    过滤器:<url-pattern>*.html</url-pattern>   切入点 (过滤以html结尾的)

    连接点:http://www.atguigui.com/adfa/adfa/index.html 连接点 (过滤具体的请求路径)

    <filter-mapping> <filter-name>AdminFilter</filter-name> <url-pattern>*.html</url-pattern></filter-mapping>

    5、切面 (aspect) 

    切入点和通知的结合。是一个类,通知(干什么)+切入点(在哪里干)

    类比过滤器:定义一个类(EncodingFilter)+过滤路径(<url-pattern>*.html</url-pattern>)

    6、目标( target) 

    被代理的目标对象。(UserDaoImpl  CalculatorPureImpl )

    7、代理 (proxy)  (静态代理和动态代理)

    向目标对象应用通知之后创建的代理对象。

    8、织入 (weave) (目标 + 通知 --> 代理对象) 

    指把通知应用到目标上,生成代理对象的过程。可以在编译期织入,也可以在运行期织入,Spring采用后者。

    9、切面和过滤器有什么共同点和不同点 

    共同点:都可以用来解决横切性问题,比如日志、事务

    不同点:

    Filter必须是Web项目,离不开Servlet容器。 AOP没有这个要求

    Filter过滤是http请求的路径,AOP过滤的是方法(哪些包下哪些类的哪些方法)

    三、AOP第一个示例:添加日志(准备)

    总体思路:使用OOP实现计算器的业务功能,使用AOP在不修改计算器代码的基础上增强日志功能。

    1. 添加依赖

    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version></dependency><!-- junit测试 --><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope></dependency><!-- Spring的测试包 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version></dependency>

    2. 定义目标接口和目标类

    //计算器接口public interface Calculator { int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j);}//实现类@Componentpublic class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result = i + j; System.out.println("方法内部 result = " + result); return result; } @Override public int sub(int i, int j) { int result = i - j; System.out.println("方法内部 result = " + result); return result; } @Override public int mul(int i, int j) { int result = i * j; System.out.println("方法内部 result = " + result); return result; } @Override public int div(int i, int j) { int result = i / j; System.out.println("方法内部 result = " + result); return result; }}

    3. 配置(扫描注解)

    <!--指明注解扫描基准路径 base-package:会扫描该包及其子包下的注解--> <context:component-scan base-package="com.atguigu"></context:component-scan>

    4. 测试

    // junit的@RunWith注解:指定Spring为Junit提供的运行器@RunWith(SpringJUnit4ClassRunner.class)// Spring的@ContextConfiguration指定Spring配置文件的位置@ContextConfiguration(locations = "classpath:spring.xml")public class TestAOP { @Autowired//引用类型自动匹配 Calculator calculator; @Test public void test(){ int result = this.calculator.add(10, 20); System.out.println(result); System.out.println("---------------------------"); result = this.calculator.div(10, 0); System.out.println(result); }}

    四、AOP第一个示例:添加日志(实现)@Aspect  @Before.. 

    1. 添加依赖

    <!-- spring-aspects会帮我们传递过来aspectjweaver --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version></dependency>

    2. 定义切面(通知+切入点)

    @Component //组件扫描,被<context:component-scan>扫描到,创建对象并放入IoC容器@Aspect //这是一个切面public class LogAspect { //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式) @Before("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void beforeLog(){ System.out.println("[日志] xxx 方法开始执行,参数是:"); } //返回通知 @AfterReturning("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void afterReturningLog(){ System.out.println("[日志]" +" 方法执行结束了,结果是:"); } //异常通知 @AfterThrowing("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void afterThrowingLog(){ System.out.println("[日志]" +"方法出现了异常,异常信息:"); } //后置通知 @After("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void afterLog(){ System.out.println("[日志] finally 收尾工作"); }}

    3. 配置文件中启动AOP

    <!-- 注解扫描:@Component @Service @Controller @Reposity--><context:component-scan base-package="com.atguigu"></context:component-scan><!-- 启动AOP自动代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    4. 测试

    @RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:spring.xml")public class TestAOP { @Autowired Calculator calculator; @Test public void testAdd(){ int add = calculator.add(10, 20); System.out.println(add); System.out.println("-------"); int div = calculator.div(10, 0); System.out.println(div); System.out.println(div); }}

     

    五、重用切入点  @Pointcut

    在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。

    可以创建存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于管理。

    //重用切入点 @Pointcut("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void pointcut1(){ }

    同一个类中使用:

    //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式) @Before("pointcut1()") public void beforeLog(){ ... }

    不同类中使用:

    @Around(value = "com.atguigu.aspect.LogAspect.pointcut1()")public Object roundAdvice(ProceedingJoinPoint joinPoint) {}

    六、获取连接点 (信息)

    1、方法信息 (JoinPoint接口)

    org.aspectj.lang.JoinPoint

    • 要点1:JoinPoint接口通过 getSignature()方法获取目标方法的签名
    • 要点2:通过目标方法签名对象获取方法名
    • 要点3:通过JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数
    @Before("pointcut1()") public void beforeLog(JoinPoint joinPoint){ //连接点 Object[] args = joinPoint.getArgs();//获取方法参数 Signature signature = joinPoint.getSignature();//获取签名 String name = signature.getName();//获取方法名 System.out.println("[日志] "+name+" 方法开始执行,参数是:"+Arrays.toString(args)); }

    2、方法返回值

    1.在@AfterReturning通过 returning 属性设置一个名称

    2.使用returning属性设置的名称在通知方法中声明一个对应的形参

    //返回通知 @AfterReturning(value = "pointcut1()",returning = "result") public void afterReturningLog(Object result){ //返回值 注意类型是Object System.out.println("[日志]" +" 方法执行结束了,结果是:" +result); }

    3、目标方法抛出的异常

    @AfterThrowing中声明throwing属性设定形参名称

    使用 throwing 属性指定的名称在通知方法声明形参

    @AfterThrowing(value = "pointcut1()",throwing = "exception") public void afterThrowingLog(JoinPoint joinPoint,Exception exception){ Signature signature = joinPoint.getSignature();//获取修饰符 String name = signature.getName(); System.out.println("[日志]"+ name +"方法出现了异常,异常信息:"+exception.toString()); }

    七、切入点表达式语法  execution(* ... .method())

    切入点最终会落到方法上面。

    1.某包某类的无参数的方法::execution(* com.atguigu.service.impl.Student.test())

    2.某包某类带有参数的方法: execution(* com.atguigu.service.impl.Student.test(String,int))

    3.某包某类的某个同名的所有方法:execution(* com.atguigu.service.impl.Student.test(..))
    .. 表示任意个数任意类型的参数

    4.某类的所有方法:execution(* com.atguigu.service.impl.Student. * (..))
    * 表示任意的类名,方法名,包名

    5.所有类的所有方法:execution(* com.atguigu.service.impl.* . *(..))

    • * *.Student.test(..)  包只有一级,包名任意,com.Student,cn.Student
    • * *..Student.test(..)   所有层级的包都匹配,可以是com.Student,com.atguigu.Student
    • * com.atguigu.service.impl.*Service.test() 指定包下类名须是Service结尾
    • execution(public int *..*Service.*(.., int)) 可以
    • execution(* int *..*Service.*(.., int)) 修饰符任意 不可以
    • execution(public * *..*Service.*(.., int)) 返回值任意 可以

    八、环绕通知  @Around (关于事物的切面)  ★

    环绕通知对应整个try...catch...finally结构,包括四种通知的所有功能,属于一个通知。

    注意:环绕通知需要返回 指定连接点方法的返回值

    //环绕通知: 关于事物的切面@Aspect //声明为一个切面类@Componentpublic class TransactionAspect { //CalculatorImpl.*(..)) 链接点为该类下的所有方法 @Around("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public Object doTransaction (ProceedingJoinPoint pjp){ //调用目标方法 Object proceed = null; try{ //设置事物不在自动所提交 System.out.println("[事务] 设置事物不再自动提交 conn.setAutoCommit(false)"); //调用方法 proceed = pjp.proceed(); //提交事务 System.out.println("[事务] 成功结束,手动提交 conn.commit()"); }catch (Throwable e){ //Throwable Exception父类 e.printStackTrace(); //手动回滚事务 System.out.println("[事务] 异常结束,手动回滚 conn.rollback()"); }finally { //关闭资源 System.out.println("[事务] 无论成功失败,关闭链接 conn.close()"); } //返回调用方法的返回值 return proceed; }}

    九、多个切面执行顺序 (切面优先级)  @Order

    相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

    通过加入@Order(int)   参数越小越早执行。

    @Aspect //声明为切面类@Component@Order(2)//越小越早执行public class TransactionAspect { @Component //组件扫描,被<context:component-scan>扫描到,创建对象并放入IoC容器@Aspect //这是一个切面@Order(3)//越小越早执行public class LogAspect {

    十、有无接口的动态代理技术 (JDK、CGLIB)  ★

    两种动态代理技术:

    JDK:SUN公司官方提供的,Proxy为JDK动态代理类的父类

    CGLIB:第三方提供的动态代理的技术,更加强大

    • 情况1:没有切面:创建的当前类对象自身,直接从IoC容器获取,没有使用动态代理。

    • 情况2:
      使用切面,但是bean对应的类有接口(CalculatorPureImpl底层自动使用 JDK 的动态代理)
      使用切面,但是bean对应的类没有接口(HappyComponent 自动的使用 CGLIB 的动态代理)

    • 情况3:使用切面,不管是否有接口,都使用CGLIB (通过配置AOP自动代理)
    <!-- 启动AOP自动代理 proxy-target-class:底层采用什么动态代理技术 false : 默认 有接口使用JDK动态代理,没有接口使用CGLIB true:不管是否有接口,都使用CGLIB--><aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

    问题1:为什么使用JDK动态代理,目标Bean对象必须有接口

    因为JDK动态代理生成的代理类已继承了Proxy,而Java是单继承的,所以必须有接口。

    问题2:为什么使用CGLIB实现动态代理,目标Bean对象不要求有接口

     因为CGLIB生成的代理类没有固定的父类,所以可以直接继承目标类,不需要接口。

    十一、应用AOP 对 IoC中获取bean的影响 ★

    1、情况一:没有使用AOP切面

    根据bean本身、或接口的类型,正常获取到IOC容器中的那个bean对象

    注意:使用接口,IoC容器中只能有一个实现类;使用实现类,IoC容器中有一个实例。

    否则抛出异常:NoUniqueBeanDefinitionException,表示IOC容器中这个类型的bean有多个。

    2、情况2: 使用AOP,但是使用JDK动态代理(Proxy)

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

    使用接口可以获取,使用实现类不可以。原因,JDK动态代理继承了Proxy,而java是单继承

    应用了切面后,真正放在IOC容器中的是代理类的对象,目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的。

    从内存分析的角度来说,IOC容器中引用的是代理对象,代理对象引用的是目标对象。IOC容器并没有直接引用目标对象,所以根据目标类本身在IOC容器范围内查找不到。 

    3、情况3: 使用AOP,但是使用CGLIB

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

    使用接口、实现类都可以获取。

     

    十一、AOP 使用XML配置(了解)

    目前Spring的开发以注解开发为主。XML配置基本不使用。

    spring.xml<!--配置目标类 --> <bean id="calculator" class="com.atguigu.service.impl.CalculatorPureImpl"></bean> <!-- 配置切面类--> <bean id="logAspect" class="com.atguigu.aspect.LogAspect"></bean> <!-- 配置切面--> <aop:config> <!--配置日志切面 --> <aop:aspect ref="logAspect" order="3"> <!-- 配置切入点--> <aop:pointcut id="pointcut1" expression="execution(public * com.atguigu.service.impl.*.*(..))"/> <!--配置各种通知--> <aop:before method="beforeLog" pointcut-ref="pointcut1"></aop:before> <aop:after-returning method="afterReturningLog" pointcut-ref="pointcut1" returning="resultValue"></aop:after-returning> <aop:after-throwing method="afterThrowingLog" pointcut-ref="pointcut1" throwing="exception"></aop:after-throwing> <aop:after method="afterLog" pointcut-ref="pointcut1"></aop:after> </aop:aspect> <!--配置事务切面...待补充 --> </aop:config> 对应使用注解方式@Component //组件扫描,被<context:component-scan>扫描到,创建对象并放入IoC容器@Aspect //这是一个切面@Order(3)//越小越早执行public class LogAspect { //重用切入点 @Pointcut("execution(* com.atguigu.service.impl.CalculatorImpl.*(..))") public void pointcut1(){ } //前置通知: 指定的方法在执行之前要做什么 (切入点,表达式) @Before("pointcut1()") public void beforeLog(JoinPoint joinPoint){ //连接点 Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature();//获取修饰符 String name = signature.getName(); System.out.println("[日志] "+name+" 方法开始执行,参数是:"+Arrays.toString(args)); } //返回通知 @AfterReturning(value = "pointcut1()",returning = "result") public void afterReturningLog(Object result){ //返回值 注意类型是Object System.out.println("[日志]" +" 方法执行结束了,结果是:" +result); } //异常通知 @AfterThrowing(value = "pointcut1()",throwing = "exception") public void afterThrowingLog(JoinPoint joinPoint,Exception exception){ Signature signature = joinPoint.getSignature();//获取修饰符 String name = signature.getName(); System.out.println("[日志]"+ name +"方法出现了异常,异常信息:"+exception.toString()); } //后置通知 @After("pointcut1()") public void afterLog(){ System.out.println("[日志] finally 收尾工作"); }}

    相关推荐

    相关文章