Javaweb框架 Spring
耦合性与降低耦合
Spirng的核心是IoC(Inverse Of Control)反转控制和AOP(Aspect Oriented Programming)面向切片编程
耦合:程序间的依赖关系,包括类之间的依赖和方法间的依赖
解耦:降低程序之间的依赖关系
在实际编程中应该做到:编译期不依赖,运行时才依赖
降低耦合解决思路:
**第一步:使用反射来创建对象,而避免使用关键字new。**这样做可以使得新创建的对象仅依赖于Class.forname()中的类名这一字符串,而不是依赖具体的类
**第二步:通过读取配置文件来获取要创建对象的全限定类名。**这样做可以避免Class.forname()中的类名写死的情况
例如:在传统的JDBC连接操作时,如果不导入jar包则在编译期无法通过,这就是类之间的依赖关系;在Class.forname()中通过全限定类名来创建对象,做到了运行期才依赖
工厂模式解耦
- javabean工厂类的构建:工厂类根据预定义的配置文件propertie构建好beans的map容器,并提供getbean()接口
- javabean工厂类的使用:在业务逻辑中创建对象时无需使用new关键字,而是给getbean()接口传递对应的类名即可
//工厂类
public class BeanFactory {
//定义propeties对象
private static Properties props;
//定义一个Map用于存储创建的beans对象,我们称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties赋值
static {
try {
props = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
Enumeration keys = props.keys();
while(keys.hasMoreElements()){
String key = keys.nextElement().toString();
String beanpath = props.getProperty(key);
Object value = Class.forName(beanpath).newInstance();
beans.put(key,value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
//使用方式
IAccountService = (IAccountService)BeanFactory.getbean("accountService");
Spring IOC控制反转
控制反转:把创建对象的权利交给框架(BeanFactory类),它包括了依赖注入DI和依赖查找
Spring使用示例
package ui;
import dao.IAccountDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
import service.impl.AccountServiceImpl;
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
IAccountService as =(IAccountService)ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
ApplicationContext的三个常用类:
ClassPathXmlApplicationContext:加载类路径下的配置文件
FileSystemXmlApplicationContext:加载磁盘任意路径下的配置文件
AnnotationConfigApplicationContext:读取注解创建容器
Spring 依赖注入
依赖关系交给Spring管理,依赖关系的维护就称之为依赖注入
能注入的有三种类型:
1)基本类型和String
2)其他bean类型
3)复杂类型
"singleton"用于指定单例,scope="prototype"是多例,单例是指每次从容器中get的时候只会有一个bean对象,
注入的方式有三种:
- 使用构造函数,非基础类型如Date的注入需要使用引用类
构造函数
public AccountServiceImpl(String name ,Integer age,Date birthday){
this.name = name;
this.age = age;
this.birthday= birthday;
}
构造函数注入配置文件
<bean id="accountService" class="service.impl.AccountServiceImpl",scope="singleton">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id ="now" class="java.util.Date"></bean>
2)使用set方法(最常使用),给被注入的类添加set方法
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount(){
System.out.println("service中的方法执行了。。。。"+name+" "+age+" "+birthday);
}
}
<bean id ="now" class="java.util.Date",scope="prototype"></bean>
<bean id="accountService2" class="service.impl.AccountServiceImpl2">
<property name="name" value="test"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
3)使用注解
1.用于创建对象的注解@Component
第一步:在bean.xml中加入标签,把当前包下的类包含进来,此时xml文件中可以不包含 <bean id=“accountService2” class=“com.service.impl.AccountServiceImpl2”>这样的注入
<context:component-scan base-package="com"></context:component-scan>
第二步:把@Component(“123”)加在需要构建的类前,"123"表示bean对象的别名
第三步:在使用的时候创建对象的时候
IAccountService as2 =(IAccountService)ac.getBean("123");
用于创建对象的其他注解
@Controller:用于表现层
@Service:用于业务层
@Repository:用于持久层
以上三个注解和@Component无实质区别,只是习惯命名
2.用于注入数据的注解@Autowired
作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
出现位置:可以是变量上,也可以是方法上
@Qualifier:按照类的基础上再按照名称注入,必须和@Autowired配合使用
使用@Autowired和@Qualifier注解
@Autowired
@Qualifier("accountDao1")
private IAccountDao iAccountDao;
在IAccountDao接口实现类前加@Repository("accountDao1")
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户11111");
}
}
@Resource:直接指定name,相当于@Autowired和@Qualifier加在一起
@Resource("accountDao1")
@Autowired和@Qualifier和@Resource都只能注入bean类型,无法注入基本类型和String类型
@Value可以实现基本类型和String类型的注解,它可以使用spring中的SpEL写法,即${表达式}
@Value("ly")
private String name;
@Value("24")
private Integer age;
3.用于指定范围的注解@Scope,@Scope(“singleton”)表示单例,@Scope(“prototype”)表示多例
Spring AOP 面向切片编程
名次解释:
**Jointpoint(连接点)😗*是指被拦截到的点,在spring中,这些点是指方法。因为spring只支持方法类型的连接点
**Pointcut(切入点):**是指我们要对那些Jointpoint进行拦截的定义
通知(增强)的四种类型:前置通知,后置通知,异常通知,最终通知,这些通知是根据切入点的方法调用的位置决定搞得
Introduction(引介):是一种特殊的通知,Introduction可以在运行期为类动态地添加一些方法
Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程
Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
Aspect(切面):是切入点和通知的结合
a)开发阶段(我们做的):
编写业务核心代码,把公用代码抽取出来,制作成通知,在配置文件中,声明切入点与通知间的关系,即切面
b)运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态的创建目标的代理对象,根据通知类别,在代理的对应位置,将通知的功能织入,完成完整的代码运行逻辑
不使用Spring的AOP自己实现的动态代理通知过程和四种通知如下图:
例子:增强accountServiceImp类中的saveAccount()方法,使用的是Logger类中的pringlog方法作为前置通知
beam.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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc把service对象配置进来-->
<bean id ="accountService" class="com.impl.AccountServiceImpl"></bean>
<!--spring中基于xml的aop配置-->
<!--1.把通知的bean也交给spring来管理-->
<bean id="logger" class="com.utils.Logger"></bean>
<!--2.使用aopconfig标签表明开始aop的配置-->
<aop:config>
<!--3.开始配置aop切面
id属性:给切面配置一个唯一标志
ref属性:指定通知类的bean的id
-->
<aop:aspect id ="logAdvice" ref="logger">
<!--4.配置通知类型,因为printlog方法需要在accountService之前执行,所以是前置通知
method属性;指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中的哪些方法增强
切入点的写法:excution(表达式)-->
<aop:before method="printLog" pointcut="execution(public void com.impl.AccountServiceImpl.saveAccount())"></aop:before>
<!--*和..可以实现通配,实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.impl.*(..)
-->
</aop:aspect>
</aop:config>
</beans>
AOPtest类
package com.test;
import com.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPtest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as =(IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
}
运行结果
4种通知类型及其示例:
前置通知:在切入点方法之前执行
后置通知:在切入点方法正常执行之后执行,和异常通知只能执行一个
异常通知:在发生异常之后执行
最终通知:无论切入点是否正常执行它都会在其后执行
<aop:config>
<!--3.开始配置aop切面
id属性:给切面配置一个唯一标志
ref属性:指定通知类的bean的id
-->
<aop:aspect id ="logAdvice" ref="logger">
<aop:before method="printLog" pointcut-ref="pt1"></aop:before>
<aop:after-returning method="printLog" pointcut-ref="pt1"></aop:after-returning>
<aop:after-throwing method="printLog" pointcut-ref="pt1"></aop:after-throwing>
<aop:after id="pt1" method="printLog" pointcut="execution(public void com.impl.AccountServiceImpl.saveAccount())"></aop:after>
</aop:aspect>
</aop:config>
注解方式实现AOP
AccountServiceImpl类
package com.impl;
import com.IAccountService;
import org.springframework.stereotype.Service;
@Service("accountService" )
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
}
public void updateAccount(int i) {
System.out.println("执行了更新");
}
public int deleteAccount() {
System.out.println("执行了保存");
return 0;
}
}
Loger类
package com.utils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component("logger")
@Aspect
public class Logger {
@Pointcut("execution(public void com.impl.AccountServiceImpl.saveAccount())")
private void pt1(){}
@Before("pt1()")
public void printLog(){
System.out.println("Logger类中的pringlog方法开始记录日志。。。。。。");
}
}