69代理:Day69.Spring: 使用注解开发、代理模式、AOP、切面、通知、AOP对IOC的影响、动态代理技术:JDK | cglib 2024-04-03 02:31:49 0 0 目录 总结 使用注解开发 一、标记与扫描 ★ 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 收尾工作"); }} 收藏(0)