Spring入门

为什么使用Spring?

spring是一个开源的轻量级的应用程序开发框架,其目的是简化企业的应用程序开发,降低侵入性,Spring提供的IOC和AOP功能,可以将组件之间的耦合度降到最低,便于后期的维护和升级,实现了软件的高内聚低耦合思想。
1)方便解耦,简化开发(高内聚、低耦合)
Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理
2)AOP编程的支持
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程
3)方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis等)的直接支持
4)降低JavaEE API的使用难度
Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低

IOC 控制反转

Ioc (Inversion of Control),中文叫做控制反转,这是一个概念,也是一种思想。控制反转,实际上就是值对一个对象的的控制权的反转。
例子:
比如说 现在有一个Employee类

public class Employee implements Serializable {
	private static final long serialVersionUID = 7833677928421758477L;
	private Integer id;
	private String name;
	private Double salary;
	private Integer age;
	private Status status;
	// 省略get/set
}

现在需要使用这个对象,我们正常的方式是这样用的:

	@Test
	void Test() {
		Employee employee = new Employee();
		employee.setName("Lang");
		employee.setAge(18);
		employee.setSalary(9999.99);
	}

在这种情况下,Employee 对象的控制权在Test()这个方法里面,这样的话,Employee和 Test() 高度耦合。如果在其他的地方需要使用到这个Employee对象,那就得重新创建,也就是说,在这个例子中,对象的创建,初始化,销毁等操作,都是由开发者来完成,如果能够将这些操作交给容器来管理,开发者就可以极大的从对象的创建中解脱出来。

使用Spring 之后,我们可以将对象的创建、初始化、销毁等操作交给Spring 容器来管理。就是说,在项目启动时,所有的Bean 都将自己注册到Spring 容器中去(如果有必要的话),然后如果其他Bean 需要使用到这个Bean ,则不需要自己去 new,而是直接去Spring 容器去要。通过一个简单的例子看下这个过程。
首先创建一个普通的Maven 项目,然后引入spring-context 依赖,如下:

<dependencies>    
	<dependency>       
	<groupId>org.springframework</groupId>        
	<artifactId>spring-context</artifactId>        
	<version>5.1.9.RELEASE</version>    
	</dependency>
</dependencies>

接下来 在resources文件下创建一个 Spring的配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.java.Employee" id="Employee" />

</beans>

class 属性表示需要注册的bean 的全路径,id 则表示bean 的唯一标记。

接下来 加载这个配置文件:

	public static void main(String[] args) {
		ClassPathXmlApplicationContext con = new ClassPathXmlApplicationContext("applicationContext.xml");
	}

执行Main 方法时,配置文件就会被加载,进而在Spring中初始化 一个 Employee 实例。

Bean 实例的获取

	public static void main(String[] args) {
		ClassPathXmlApplicationContext con = new ClassPathXmlApplicationContext("applicationContext.xml");
		// Employee employee = (Employee) con.getBean("Employee");
		Employee employee = con.getBean("Employee",Employee.class);
		System.out.println(employee);
	}

输出结果:
“init 是在Employee 构造方法中输出的”
在这里插入图片描述

属性注入(DI):

a) 构造方法注入:

通过bean 的构造方法 给bean 的属性注入值。

  • 1.给bean 添加对应的构造方法
  • 2.在xml中注入bean 这里需要注意的是,它会根据注入的参数类型/个数 去匹配对应的构造方法。

在这里插入图片描述

b)set方法注入:

如上类似,它是通过bean 的set方法注入属性

在这里插入图片描述
c)外部bean注入:

有时候,我们需要使用一些外部bean,这些bean 可能没有构造方法 ,所以无法通过new来获取,而是通过builder / 特定的静态方法 来构造。
例如:
在这里插入图片描述
这里 Calendar 这个实例,我们可以使用 静态工厂注入 或者 实例工厂注入

静态工厂:

public class CalendarUtils {

	private static Calendar calendar;

	public static Calendar getInstance() {
		if (calendar == null) {
			calendar = Calendar.getInstance();
		}
		return calendar;
	}
}

。。。。。。。。。。。。后面都是简单注入,懒得写了,

Java 配置:

@configuration 注解表示这是一个Java配置类,配置类的作用类似于 applicationContext.xml

@Configuration
public class Config {

	@Bean("employeeInstance") // 不指定则默认为方法名
	Employee employee() {
		return new Employee();
	}

}

这样就把一个bean注册到spring 中了

使用:

		AnnotationConfigApplicationContext con = new AnnotationConfigApplicationContext(Config.class);
		Employee employee = con.getBean("employeeInstance", Employee.class);
		System.out.println(employee);

自动化配置:
例如现在有一个 UserService ,我希望在自动化扫描时,这个类能够自动注册到Spring容器中去,那么可以给该类添加一个@Service ,作为一个标记。
@Service 注解功能类似的注解 一共有四个:
@Component :在其他组件上使用
@Repository : 在Dao层使用
@Service :在 Service 层使用
@Controller :在Controller 层使用

这四个中,另外三个都是基于 @Component 做出来的,而且从目前的源码来看,功能基本一直,那么为什么要搞三个?,主要是为了在不同的类上添加时方便

例子:
使用Java配置包扫描:
在这里插入图片描述
在这里插入图片描述
使用xml,配置包扫描:

<?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
   http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<context:component-scan base-package="com.java.service" />

</beans>
		ClassPathXmlApplicationContext con = new ClassPathXmlApplicationContext("applicationContext.xml");
		EmployeeService bean = con.getBean("employeeService", EmployeeService.class);
		bean.getAll();

对象注入

当使用自动扫描时的对象注入有三种方式:
@Autowired
是根据类型去查找,然后赋值,这个类型只可以有一个对象。否则就会报错。 它也可以配合@Qualifier 实现通过变量名查找到变量。
@Resoutce
是根据名称去查找,默认情况下,定义的变量名,就是查找的名称。
@Injected


@Repository
public class UserDao {
	public String getName() {
		return "Test";
	}
}

注册后 ,使用:

@Service
public class EmployeeService {

	@Autowired
	UserDao userDao;

	public List<String> getAll() {
		String name = userDao.getName();
		return Arrays.asList(name);
	}
}

条件注解

条件注解 就是在满足某一个条件的情况下,才生效的注解

案例: 假如 需要在Windows 中 查看文件夹的目录命令是 dir,而在Linx 中查看文件夹目录的命令是ls,我现在希望当系统运行Windows 上时,自动打印出windows 上的目录命令,Linux运行时,则自动展示 linux上的目录命令。

首先 定义一个命令显示接口:

public interface ShowCmd {
	String showCmd();
}

接着 win / Linux 分别实现该接口

public class WinShowCmd implements ShowCmd {

	public String showCmd() {
		return "dir";
	}

}
public class LinuxShowCmd implements ShowCmd {

	public String showCmd() {
		return "ls";
	}

}

接下来 定义两个条件。一个时windows 下的条件,一个是Linux 下的条件。


public class WindowsCondition implements Condition {

	public boolean matches(ConditionContext con, AnnotatedTypeMetadata anno) {
		return con.getEnvironment().getProperty("os.name").toLowerCase().contains("win"); // 获取当前允许环境的系统名称 如果包含"win"
																							// 返回true
	}

}
public class LinuxCondition implements Condition {

	public boolean matches(ConditionContext con, AnnotatedTypeMetadata anno) {
		return con.getEnvironment().getProperty("os.name").toLowerCase().contains("linux"); // 获取当前允许环境的系统名称 如果包含"linux"
																							// 返回true
	}

}

接下来 在注册bean 的时候就可以去配置条件注解了。

自动配置类:

	@Bean("cmd")
	@Conditional(WindowsCondition.class) // 条件判断注解 根据此条件类去觉得是否注册bean
	ShowCmd winCmd() {
		return new WinShowCmd();
	}

	@Conditional(LinuxCondition.class) // 条件判断注解 根据此条件类去觉得是否注册bean
	@Bean("cmd")
	ShowCmd linuxCmd() {
		return new LinuxShowCmd();
	}

这里 一定要给两个bean 取相同的名字,这样在调用时,才可以自动匹配,因为最终只会注册一个bean ,当条件中的 matches 方法返回true 的时候,这个bean的定义才会生效

		AnnotationConfigApplicationContext con = new AnnotationConfigApplicationContext(Config.class);
		ShowCmd showCmd = con.getBean("cmd", ShowCmd.class);
		System.out.println(showCmd.showCmd());

Bean 的作用域

		AnnotationConfigApplicationContext con = new AnnotationConfigApplicationContext(Config.class);
		ShowCmd showCmd1 = con.getBean("cmd", ShowCmd.class);

		ShowCmd showCmd2 = con.getBean("cmd", ShowCmd.class);
		System.out.println(showCmd1 == showCmd2); // true

如上,从Spring 容器中,多次获取同一个Bean ,默认情况下,获取到的实际上是同一个实例,我们也可以自己手动配置:

xml 配置

	<bean id="employee" class="com.java.Employee" scope="prototype" />

通过在xml 节点中,设置scope 属性,我们可以调整默认的实例个数。

scope的值默认为 singleton(默认),表示这个Bean在spring容器中,是以单例的模式存在的
如果scope 的值为 prototype ,表示这个Bean 在spring 容器中不是单例,多次获取将会获取到不同的实例。
除了 singleton 和 prototype 之外,还有两个取值,requestsession ,这两个取值在 web 环境下有效。。
除了可以在xml 中配置,也可以在Java 中配置

	@Bean("employeeInstance") // 不指定则默认为方法名
	@Scope("prototy")
	Employee employee() {
		return new Employee();
	}

AOP

Aop,面向切面编程,这是对面向对象思想的一种补充。通俗来说,就是在不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景:日志,性能监控,事务

在Aop中有几个常见的概念:

  • 切点:要增加代码的地方,称作切点。
  • 通知:通知就是向切点动态添加的具体代码。
  • 切面:切点 + 通知 就是一个切面
  • 连接点:切点所在的方法。

实现:
CGLIB:

JDK:

MyUser 接口

public interface MyUser {
	void selectUser();
}

MyUserImpl 实现类

public class MyUserImpl implements MyUser {

	public void selectUser() {
		System.out.println("My name is User");
	}

}

UserProxy 代理类

public class UserProxy implements InvocationHandler {

	private Object obj;

	public UserProxy(Object obj) {
		this.obj = obj;
	}

	public Object getUserProxy() {
		ClassLoader classLoader = obj.getClass().getClassLoader();
		Class<?>[] interfaces = obj.getClass().getInterfaces();

		return Proxy.newProxyInstance(classLoader, interfaces, this);
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before");
		Object invoke = method.invoke(obj, args);
		System.out.println("after");
		return invoke;
	}

}

测试:

public class Main {
	public static void main(String[] args) {
		MyUser myUser = new MyUserImpl();
		UserProxy userProxy = new UserProxy(myUser);
		MyUser myUserSrervice = (MyUser) userProxy.getUserProxy();
		myUserSrervice.selectUser();
	}

}

切点定义:注解 + 规则定义

使用注解进行切面

先自定义一个注解:

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {

}

Java 自动配置类

@ComponentScan // 开启包扫描 把当前包下的带注解的bena注册到spring
@Configuration // 代表是一个配置类
@EnableAspectJAutoProxy // 开启自动代理
public class Config {
}

定义一个接口

public interface MathCalculator {

    int add(int a, int b);

    int sub(int a, int b);
}

其实现类

@Component
public class MathCalculatorImpl implements MathCalculator {

    @Action
    @Override
    public int add(int a, int b) {
        System.out.println("add 方法执行了");
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("sub 方法执行了");
        return a - b;
    }
}

切面类:

@Aspect // 表示这个一个切面
@Component
public class LogAspect {

    // 前置通知
    @Before("@annotation(Action)")
    public void before(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("Before Methods " + name);
    }
}

运行测试:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        MathCalculator matchCalculator = ctx.getBean(MathCalculator.class);
        matchCalculator.add(1, 3);
    }

五大通知 类型

@Aspect // 表示这个一个切面
@Component
public class LogAspect {

    /**
     * 前置通知 在方法执行之前执行
     *
     * @param joinPoint
     */
    @Before("@annotation(BeforeAction)")
    public void before(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行开始了");
    }

    /**
     * 后置通知 在方法执行之后执行
     *
     * @param joinPoint
     */
    @After("@annotation(AfterAction)")
    public void after(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行结束了");
    }

    /**
     * 返回通知 r 为返回的结果,如果被拦截的方法没有返回值 ,如果返回的参数类型不匹配也不会执行。
     * 则此方法不会执行
     *
     * @param joinPoint
     * @param r
     */
    @AfterReturning(value = "@annotation(AfterReturningAction)", returning = "r") // 这个 r 要于下面参数的一致,这是拦截那个方法的返回值
    public void returning(JoinPoint joinPoint, Integer r) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法的的返回值是" + r);
    }

    /**
     * 异常通知 ,当目标方法抛出异常时,该方法触发
     *
     * @param joinPoint
     * @param e         异常的参数
     */
    @AfterThrowing(value = "@annotation(AfterReturningAction)", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法出现异常,异常通知:" + e.getMessage());
    }

    /**
     * 环绕通知
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("@annotation(MyAround)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 这里类似于 反射的method.invoke // 我们可以在这个方法的前后分别添加日志,相等于前置/后置 通知
        Object proceed = joinPoint.proceed();
        return  4;
    }
}

统一定义 切入点:

    @Pointcut("@annotation(BeforeAction)") // 统一定义切入点
    public void ponitcut() {
    }


    /**
     * 前置通知 在方法执行之前执行
     *
     * @param joinPoint
     */
    @Before("ponitcut()")
    public void before(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println(name + "方法执行开始了");
    }

通知执行顺序

正常情况下: Around => Before => Methods => After => AfterReturning
如果方法中抛出了异常: Around => Before => Methods => After => AfterThrowing

使用注解 切入其实是侵入式的,我们使用的更多的是 自定义拦截规则
例:

    @Pointcut("execution(* com.java.service..*Impl.*(..))") // 统一定义切入点
    					//  返回值类型 包名.类名(参数类型)
    public void ponitcut() {
    }

Aop 事务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值