【Spring框架/笔记】Spring-IoC与AOP(一)

71 阅读6分钟

注: Ioc是Spring全家桶各个功能模块的基础,创建对象的容器,AOP是以IoC为基础,AOP是切面编程,抽象化的面向对象。

一、Spring IoC

控制反转,将对象的创建进行反转,常规情况下,对象需要开发者手动创建,使用IoC开发者不需要手动创建对象,而是由IoC容器根据需求自动创建项目所需要的对象。 不用IoC:所有对象以及值需要开发者自己赋值创建 使用IoC:对象不用开发者创建,交给Spring框架完成

  • 基于xml
    • 开发者把需要的对象在XML中进行配置,Spring框架会读取这个配置,根据配置信息来创建对象。

@Data
public class TestConfig {
    private Integer id;
    private String name;
}

<?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
         ">
    <bean class="com.test.config.TestConfig" id="testConfig">
        <property name="id" value="1" />
        <property name="name" value="测试" />
     </bean>

</beans>
 ApplicationContext  context = new ClassPathXmlApplicationContext("spring.xml");
        System.out.println( context.getBean("testConfig"));
        //输出结果是 TestConfig(id=1, name=测试)

ClassPathXmlApplicationContext 会解析xml文件,并把bean里面property标签的参数,通过Set赋值进对象里。 通过xml配置bean参数,再通过ApplicationContext.getBean('bean名”) 来获取自动生成好的对象

  • 基于注解
    • 配置类
      • 用一个java类来代替XML文件,把XML的配置内容放到配置类中。
@Configuration
public class BeanConfiguration {
	//默认方法名,也可以指定名
	// @Bean(value = "config")
    @Bean
    public TestConfig testConfig(){
        TestConfig testConfig = new TestConfig();
        testConfig.setId(1);
        testConfig.setName("测试");
        return testConfig;
    }
}

 ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
        System.out.println(context.getBean("testConfig"));
        // 输出结果  TestConfig(id=1, name=测试)
        //通过类也可以去获取
        System.out.println(context.getBean(TestConfig.class));
         // 输出结果  TestConfig(id=1, name=测试)

这是通过声明配置类的方式,来读取bean对象。在通过ApplicationContext来获取Bean对象,默认方法名获取,也可以加@Bean(value="config")去指定名。@Bean是声明Bean对象,contenxt可以根据方法名获取,也可以通过类获取 【配置类很多的时候可以指定包名】

//一个包下有若干个配置类,那么通过填写包名去获取bean对象
 ApplicationContext context = new AnnotationConfigApplicationContext("com.test.config");
        System.out.println(context.getBean("config"));
        System.out.println(context.getBean(TestConfig.class));
        //输出结果   TestConfig(id=1, name=测试)
  • 扫包-注解 不在依赖于XML或者配置类,而是直接将Bean的创建交给目标类,在目标类添加注解来创建
// 加上component ,spring框架读到这个类的时候,会将这个注释的类 直接创建放入到IoC里面
@Component
public class TestConfig {
	@Value("1")
    private Integer id;
    @Value("测试")
    private String name;
}
ApplicationContext context = new AnnotationConfigApplicationContext("com.test.config");
        System.out.println(context.getBean("config"));
        System.out.println(context.getBean(TestConfig.class));
        //输出结果 TestConfig(id=null, name=null)

加上component ,spring框架包扫描的时候读到这个类有@Component注释的时候,会将这个类直接创建放入到IoC容器里面,@Value可以为变量赋值。 自动创建对象,依赖注入(A配置类里面有B配置类,将B配置类也注入到A配置类里面)

@Data
@Component
public class GlobalConfig {
    @Value("1")
    private Integer id;
    @Value("全局测试")
    private String name;
    // 两个组合可以通过名字去找
    //   @Autowired
    // @Qualifier("config")
    @Autowired
    private TestConfig testConfig;
}

 ApplicationContext context = new AnnotationConfigApplicationContext("com.test.config");
        System.out.println(context.getBean(GlobalConfig.class));
        //输出结果 GlobalConfig(id=1, name=全局测试, testConfig=TestConfig(id=1, name=测试))
  • 依赖注入
    • @Autowired(根据bean类型进行注入)
    • @Resource(先根据name去获取name为空就通过bean类型去取)
    • @Qualifier(@Autowired配合@Qualifier 可以根据名字映射)

二、AOP

概念:面向切面编程,是一种抽象化的面向对象编程,对面向对象编程的一种补充(要在多个指定位置统一处理相同业务逻辑,则可以用到切面编程)。底层使用动态代理机制 作用:打印日志、事务、权限处理,【做到核心代码和非业务代码的解耦合】

  • execution 用法【更侧重签名方法签名的匹配】
    • 表达式语法结构
      • 返回值类型模式 execution( void com.xx.jj.service.userService.add(...) )【指返回void类型的com.xx.jj包下userService下的add的方法名】
      • 类名模式 execution( void com.xx.jj..*)【包名后跟..表示当前包和子包 * 表示包下的所有类】
      • 方法名模式 execution(* *.add*(..)) 【表示所有add前缀的方法】
      • 参数模式(..) execution( * *.add*(int,..))【表示所有add前缀且第一个参数为int,后面跟任意数量参数的方法名】
      • 主要格式 【execution([修饰符模式] 返回值类型模式 [类名模式]方法名模式(参数模式)[异常模式])】 ..任意个数 *模糊匹配 void指定类型 com.xx.jj 具体包名 com.xx.jj.service.userService 具体类名
      • 常用表达式
        • execution(* com.example.dao..*.*(..))【com.example.dao下的所有类的所有方法】
        • execution(* com.example.service.UserService.*(..))【com.example.service.UserService.*(..) userService指定类下的所有方法】
        • execution(* com.example.dao.GenericDAO+.*(..))【匹配com.example.dao.GenericDAO下的所有实现类的方法】
        • execution(* save*(..))【匹配save开头的所有方法名】
    • 多表达式组合
      • 或(|| 或 or ) execution(* *.add(..)) || execution(* *.delete(..)) 【表示所有add或者delete方法】
      • 与(&& 或 and ) execution(* *..*.*(..)) && args(String) 【表示所有包下的所有类下的所有方法名 ,且参数包含String类型的方法】
      • 非(! 或 not )!execution(* *.get*(..)) 【表示除了以get为前缀的方法名】
  • within用法【更侧重包的范围匹配】
    • 用法
      • within(包名..*) 指定包下所有类
      • within(com.example.service.UserService) 匹配指定类
  • @annotation表达式


//作用在方法上
@Target(ElementType.METHOD)
//注释的注释 决定@Target保留方式   source保留在源文件,编译后失效 class文件jvm加载后遗弃, runtime保留到jvm加载class之后
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggerTest {

}
@Pointcut("@annotation(com.test.annotation.LoggerTest)")
    public void loggableMethods() {
        System.out.println("进入了");
    }

    @Before("loggableMethods()")
    public void before(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"方法的参数是"+ Arrays.toString(joinPoint.getArgs()));
    }

可以给 aop将注释作为切入点,将注释了@LoggerTest的方法进行切入 。

  @LoggerTest
    @Override
    public int testLog(int num1, int num2) {
        return 0;
    }

  • 打印日志 1.创建切面类

@Component
@Aspect
public class LoggerAspect {

    @Before("execution(public int com.test.calTest.CalculateImpl.*(..))")
    public void before(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"方法的参数是"+joinPoint.getArgs().toString());
    }
    @AfterReturning(value="execution(public int com.test.calTest.CalculateImpl.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"方法的结果是"+result);
    }
}
@Component
public class CalculateImpl implements Calculate {
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    @Override
    public int sub(int num1, int num2) {
        return num1 - num2;
    }

    @Override
    public int mul(int num1, int num2) {
        return num1 * num2;
    }

    @Override
    public int div(int num1, int num2) {
        return num1 / num2;
    }
}
<?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"
       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/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--    <bean class="com.test.config.TestConfig" id="testConfig">-->
<!--        <property name="id" value="1" />-->
<!--        <property name="name" value="测试" />-->
<!--     </bean>-->
<!--    自动扫包-->
    <context:component-scan base-package="com.test.calTest" ></context:component-scan>
<!--    也可以在aspect类上加个 @EnableAspectJAutoProxy 开启自动代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
<!-- 或者给 aspect加一个-->
        ApplicationContext  context = new ClassPathXmlApplicationContext("spring.xml");
        Calculate calculate = context.getBean(Calculate.class);
        System.out.println(calculate.add(1,2));
        System.out.println(calculate.sub(1,2));
        System.out.println(calculate.mul(1,2));
        System.out.println(calculate.div(1,2));
        /**输出结果 
			add方法的参数是[1, 2]
			add方法的结果是3
			3
			sub方法的参数是[1, 2]
			sub方法的结果是-1
			-1
			mul方法的参数是[1, 2]
			mul方法的结果是2
			2
			div方法的参数是[1, 2]
			div方法的结果是0
			0
		**/
  • @PointCut 切入点,主要是提高个代码的复用性,多个@Before可以直接通过 切入点方法名()的方式去切入。让代码更整洁。
  • 五种通知方式
    • 正常顺序【@Aroundproceed之前->@Before->执行方法->@AfterReturning->@After->@Aroundproceed之后】
    • 异常顺序【@Aroundproceed之前->@Before->执行方法->@AfterThrowing->@After->throw e】
  @Pointcut("@annotation(com.test.annotation.LoggerTest)")
    public void loggableMethods() {
        System.out.println("进入了");
    }
    //环绕通知
    @Around("loggableMethods()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("proceed之前打印");
        Object result = joinPoint.proceed();
        System.out.println("之后打印"+result.toString());
        return result;
    }

    //前业务
    @Before("loggableMethods()")
    public void before(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"方法的参数是"+ Arrays.toString(joinPoint.getArgs()));
    }
    //后业务
    @After("loggableMethods()")
    public void after(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"执行方法之后");
    }
    //异常
    @AfterThrowing(value = "loggableMethods()",throwing = "e")
    public void afterThrow(JoinPoint joinPoint,Exception e) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"报错的结果"+e.getMessage());
    }
    //后返回值
    @AfterReturning(value="loggableMethods()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println(methodName+"方法的结果是"+result);
    }
    /**输出结果
    	proceed之前打印
		testLog方法的参数是[1, 2]
		testLog方法的结果是0
		testLog执行方法之后
		之后打印0
    **/
    /**
    	异常处理的结果
    	proceed之前打印
		div方法的参数是[1, 0]
		div报错的结果/ by zero
		div执行方法之后
		这里就是报错了
    **/