1. Spring
Spring具有控制反转(IoC)和面向切面(AOP)两大核心。Java Spring 框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
2. Spring 优势
1. 方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给Spring 管理。
2. 方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。
3. 降低 Java EE API 的使用难度
Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。
4. 方便程序的测试
Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。
5. AOP 编程的支持
Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
6. 声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。
3. IOC
3.1 IOC(xml)
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想(反射)。
3.1.1 初始化object
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {}
}
首先,先定义一个spring config文件application.xml
在resources路径下:
<bean id="person" class="com.practice.bean.Person"></bean>
一个bean标签表示一个对象:
- id: 对象名
- class: 完全限定名 -> spring底层是根据反射方式创建对象的,因此不能写接口
- scope: singleton/prototype
- singleton: 单例, default,容器启动完毕之后单例对象就被创建了,而且容器中只有唯一的一个对象
- prototype: 多例,多例的对象是 什么时候使用什么时候创建,每次获取的时候都创建新对象
- lazy-init: true/false (prototype 本身就是懒加载)
- true: 获取对象时才创建对象
- false: 立即创建对象,不管是否使用。
生命周期相关:
- init-method: 对象被创建完毕之后,立即调用初始化方法
- destroy-method: spring容器调用销毁方法时,立即执行
// Scenario 1
String config = "application.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
// Scenario 2
String absolutePath = "D:/work/kaikeba/11 ssm/11.2 spring/task/mySpring/practice/src/main/resources/application.xml";
ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext(absolutePath);
// Scenario 3
BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(absolutePath));
beanFactory.getBean("person");
以上三种方法都可以初始化spring中的类。但方案三中的 XmlBeanFactory
是一个已弃用的方法,因此不建议使用。
3.1.2 config 文件中 创建Object的三种方法
public class PersonFactory {
public Person instancePerson() {
return new Person("Smith", 27);
}
public static Person instanceStaticPerson() {
return new Person("John", 18);
}
}
<!-- 方法 1 default constructor -->
<bean id="person" class="com.practice.bean.Person" lazy-init="default" init-method="init_Person" destroy-method="destroy_Person"/>
<!-- 方法 2 带参数的 constructor -->
<bean id="person_customized" class="com.practice.bean.Person" lazy-init="default" init-method="init_Person" destroy-method="destroy_Person">
<constructor-arg name="name" value="Tom"/>
<constructor-arg name="age" value="5"/>
</bean>
<!-- 方法 3.1 factory instance -->
<bean id="factory" class="com.practice.bean.PersonFactory"/>
<bean id="instancePerson" factory-bean="factory" factory-method="instancePerson" />
<!-- 方法 3.2 factory static instance -->
<bean id="staticFactory" class="com.practice.bean.PersonFactory" factory-method="instanceStaticPerson"/>
3.1.3 基于XML的DI (Dependency Injection)依赖注入
Ioc和DI是同一个概念的不同角度描述。IoC是一种思想,概念,DI是实现它的手段。Spring框架使用依赖注入实现IoC.
PersonDao.java
public class PersonDao {
public PersonDao() {
System.out.println(" ------------- PersonDao constructor");
}
public void add() {
System.out.println("PersonDao ----- add ----");
}
}
PersonService.java
public class PersonService {
private PersonDao dao;
public PersonService() {
System.out.println(" ---- PersonService constructor");
}
public PersonService(PersonDao dao) {
this.dao = dao;
}
public void add() {
dao.add();
System.out.println("PersonService ----- add ");
}
public PersonDao getDao() {
System.out.println("PersonService ---- getDao -----");
return dao;
}
public void setDao(PersonDao dao) {
System.out.println("PersonService ---- setDao -----");
this.dao = dao;
}
}
xml 文件的配置有以下三种:
- 通过set方法实现注入 (setDao)
<bean id="dao" class="com.practice.dao.PersonDao"/>
<bean id="service_setDao" class="com.practice.service.PersonService">
<property name="dao" ref="dao" />
</bean>
- 通过构造方法实现注入 (这个需要有包含dao的构造函数)
<bean id="dao" class="com.practice.dao.PersonDao"/>
<bean id="service_setDao" class="com.practice.service.PersonService">
<constructor-arg name="dao" ref="dao"/>
</bean>
- 自动注入
这种方法和通过set方法实现注入代码的实现步骤是一样的
(1) 通过byName自动注入 (需要查询的变量名需要和bean的id同名)
<bean id="dao" class="com.practice.dao.PersonDao"/>
<bean id="service_byName" class="com.practice.service.PersonService" autowire="byName" />
(2)通过byType自动注入 (需要查询的变量名需要和bean的属性相同)
相同属性的变量只能有一个,如果存在多个,则查找失败,程序报错“无法找到”。
<bean id="dao" class="com.practice.dao.PersonDao"/>
<bean id="service_byName" class="com.practice.service.PersonService" autowire="byType" />
3.2 IOC (annotation)
3.2.1 Annotations
<bean id="personDao" class="com.practice.dao.PersonDao"/>
等同于
@Component(value="personDao")
@Component(value="personDao")
public class PersonDao {
}
@Component
注释标识在类上,表示对象由Spring容器创建。
value谁能够表示创建的 id 值, value 可以省略,给value赋的值也可以省略。省略后默认的值与类名相同但首字母小写。
此时 xml 配置文件需要做一些调整:增加context部分,并对添加注解的包进行扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.practice.dao"></context:component-scan>
</beans>
除此之外,Spring中还提供了其他3个用于创建对象的注解:
@Repository : 用于dao实现类的的注解
@Service: 用户service实现类的注解
@Controller: 用于controller实现类的注解
这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service创建业务层对象,业务层对象可以加入事务功能,Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
<!-- 单个包扫描 -->
<context:component-scan base-package="com.practice.dao"></context:component-scan>
<!-- 多个包扫描 1 -->
<context:component-scan base-package="com.practice.dao"></context:component-scan>
<context:component-scan base-package="com.practice.service"></context:component-scan>
<context:component-scan base-package="com.practice.controller"></context:component-scan>
<!-- 多个包扫描 2 (用逗号","、分号";"、空格" " 做分隔符,空格不推荐) -->
<context:component-scan base-package="com.practice.dao;com.practice.service;com.practice.controller"></context:component-scan>
<!-- 多个包扫描 3 (使用上一级目录路径,但不推荐目录范围过大) -->
<context:component-scan base-package="com.practice"></context:component-scan>
3.2.2 属性注入的annotations
要想想设置xml一样,实现通过 PersonService 调取 PersonDao 中的 add方法
需要以下注解
@Service
public class PersonService {
@Autowrited
private PersonDao dao;
public PersonService() {}
public void add() {
dao.add();
}
}
@Autowrited (byType)
在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
@Qualifier (byName)与 @Autowrited 配合使用
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
@Resource
JDK 版本 > 1.6。 Spring提供了对 jdk中@Resource注解的支持。
@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean。默认是按名称注入。@Resource(name="Person", type=Person.class)
@Value
需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
@Value("Tom")
String name;
4. AOP
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP的作用:不修改源码的情况下,程序运行期间对方法进行功能增强
好处:
1、减少代码的重复,提高开发效率,便于维护。
2、专注核心业务的开发。
核心业务和服务性代码混合在一起
开发中:各自做自己擅长的事情,运行的时候将服务性代码织入到核心业务中。
通过spring工厂自动实现将服务性代码以切面的方式加入到核心业务代码中。
4.1 基于JDK的动态代理
JDK的动态代理主要使用的是ProxyFactory
方法
// AOP interface
public interface AOP {
void before();
void after();
void exception();
void myfinally();
}
// class for transaction
public class TransactionAOPImpl implements AOP {
@Override
public void before() {
System.out.println("__transaction_before__");
}
@Override
public void after() {
System.out.println("__transaction_before__");
}
@Override
public void exception() {
System.out.println("__transaction_exception__");
}
@Override
public void myfinally() {
System.out.println("__transaction_finally__");
}
}
// class for Log
public class LogAOPImpl implements AOP {
@Override
public void before() {
System.out.println("__Log_before__");
}
@Override
public void after() {
System.out.println("__Log_before__");
}
@Override
public void exception() {
System.out.println("__Log_exception__");
}
@Override
public void myfinally() {
System.out.println("__Log_finally__");
}
}
public class ProxyHandler implements InvocationHandler {
private MyService service;
private AOP aop;
public ProxyHandler(MyService service, AOP aop) {
this.service = service;
this.aop = aop;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
aop.before();
Object invoke = method.invoke(service, args);
aop.after();
return invoke;
} catch (Exception e) {
aop.exception();
e.printStackTrace();
throw e;
} finally {
aop.myfinally();
}
}
}
public class ProxyFactory {
private MyService service;
private AOP aop;
public ProxyFactory(MyService service, AOP aop) {
this.service = service;
this.aop = aop;
}
// get dynamic Proxy instance
public Object getInstance() {
return Proxy.newProxyInstance(
service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new ProxyHandler(service, aop));
}
}
public static void main(String[] args) {
MyService service = new PersonService();
AOP transactionAOP = new TransactionAOPImpl();
AOP logAOP = new LogAOPImpl();
MyService transactionService = (MyService) new ProxyFactory(service, transactionAOP).getInstance();
MyService logService = (MyService) new ProxyFactory(transactionService, logAOP).getInstance();
logService.add();
System.out.println(service.getClass());
System.out.println(transactionAOP.getClass()+"--------");
System.out.println(logAOP.getClass()+"--------");
}
4.2 基于CGLIB的动态代理
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。具体实现方法与 JDK 动态代理方法类似
@Test
public void test() {
PersonService service = new PersonService();
TranService tranService = (TranService) Enhancer.create(
service.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
System.out.println("开始事务");
Object invoke = methodProxy.invokeSuper(o, objects);//核心
System.out.println("提交事务");
return invoke;
}catch (Exception e){
System.out.println("事务回滚");
throw e;
}finally {
System.out.println("finally------------");
}
}
});
tranService.add();
}
5. Spring AOP
Spring 框架下,对于 object的创建是由 Spring 来完成的。整体思路与 4 AOP 一致,有interface
用jdk方法
实现;没有interface
用cglib方法
实现.
5.1 Annotation
如之前一样,创建一个Service接口,并根据接口创建实现类。
public interface Nature {
void sound();
}
@Component("animal")
@Lazy
public class Animal implements Nature {
@Value("1") // 通过 setter进行赋值,所以也可以写在 setter上
private int age;
@Override
public void sound() {
System.out.println("make sound");
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Animal{" +
"age=" + age +
'}';
}
}
public interface Aop {
void beforeAop();
Object afterAop(JoinPoint joinPoint, Object res);
void exceptionAop();
void finallyAop();
}
@Component
@Aspect
public class AopImpl {
@Pointcut("execution(* com.practice..*.*(..))")
private void pointCut() {}
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(">>>>>环绕方法---目标方法的执行之前");
Object proceed = pjp.proceed();
System.out.println("<<<<<环绕方法---目标方法的执行之后");
return proceed;
}
@Before(value = "pointCut()")
public void beforeAop(JoinPoint joinPoint){
System.out.println("\nAOP前置通知:在目标方法执行之前被调用的通知");
// String name = joinPoint.getSignature().getName();
// System.out.println("attached method name: " + name);
// System.out.println("number of parameters: " + joinPoint.getArgs().length);
}
@AfterReturning(value = "pointCut()", returning = "result")
public void afterAop(Object result){
System.out.println("AOP后置通知:在目标方法执行之后被调用的通知 result: " + result);
}
@AfterThrowing(value = "pointCut()", throwing = "e")
public void exceptionAop(JoinPoint jp, Throwable e){
System.out.println("AOP异常通知:在目标方法执行出现异常的时候才会别调用的通知,否则不执行");
// System.out.println(jp.getSignature() + " Error " + e.getMessage());
}
@After("pointCut()")
public void finallyAop(){
System.out.println("AOP最终通知:无论是否出现异常都是最后被调用的通知");
}
}
public class FunTest {
@Test
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Aop aop = (Aop) applicationContext.getBean("aopImpl");
Animal animal = (Animal) applicationContext.getBean("animal");
animal.sound();
}
}
在spring中的xml中配置相关的依赖。
- 添加依赖包:context,aop
- 配置annotation的扫描路径。
- 开启 aspectj 代理服务。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
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.xsd
">
<context:component-scan base-package="com.practice.service;com.practice.aop"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
5.2 xml
除了使用 annotation的实现代理,还可以使用xml配置。
虽然使用xml配置代理服务与使用annotation相比显得繁琐且复杂。但配置信息较为集中、方便统一管理。
<!--xml方式实现aop-->
<aop:config>
<!--声明切入点的表达式,可以声明多个-->
<aop:pointcut id="pt1" expression="execution(* com.practice.service..*.add*(..))"/>
<aop:pointcut id="pt2" expression="execution(* com.practice.service..*.update*(..))"/>
<aop:pointcut id="pt3" expression="execution(* com.practice.service..*.del*(..))"/>
<aop:pointcut id="pt4" expression="execution(* com.practice.service..*.insert*(..))"/>
<aop:aspect ref="aop">
<aop:before method="before" pointcut="execution(* com.practice.service..*.*(..))"></aop:before>
<aop:after-returning method="afterReturn" pointcut-ref="pt2" returning="result"></aop:after-returning>
<aop:after-throwing method="exception" pointcut-ref="pt1" throwing="ex"></aop:after-throwing>
<aop:after method="myFinally" pointcut-ref="pt1"></aop:after>
<aop:around method="around" pointcut-ref="pt2"></aop:around>
</aop:aspect>
</aop:config>
6 spring中应用JDBC
在开始前需要添加一些JDBC使用时必须的依赖。
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
在 javaEE 编写时,通常会把加载连接数据库所需要的配置信息写在类似对db.property
命名的文件中。在spring中,这些配置信息会被写在spring的配置文件中,连接数据库的操作完全交由spring处理。之后,利用变量jdbcTemplate
来操作数据库。
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="user" value="root"></property>
<property name="password" value="123"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/web_practice?serverTimezone=UTC"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="gradeDao" class="com.practice.dao.GradeDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
public class Grade {
private int gradeId;
private String gradeName;
public Grade() {}
public Grade(int gradeId, String gradeName) {
this.gradeId = gradeId;
this.gradeName = gradeName;
}
public int getGradeId() {
return gradeId;
}
public void setGradeId(int gradeId) {
this.gradeId = gradeId;
}
public String getGradeName() {
return gradeName;
}
public void setGradeName(String gradeName) {
this.gradeName = gradeName;
}
@Override
public String toString() {
return "Grade{" +
"gradeId=" + gradeId +
", gradeName='" + gradeName + '\'' +
'}';
}
}
JdbcDaoSupport类
非常关键,我们可以中这个类中获取到变量jdbcTemplate
并对数据库进行操作。查询用 query()
;增、删、改使用update()
.
public class GradeDao extends JdbcDaoSupport {
public void searchAll() {
String query = "select * from grade";
List<Grade> grades = this.getJdbcTemplate().query(query, new BeanPropertyRowMapper<Grade>(Grade.class));
System.out.println("Dao: " + grades);
}
}
@Test
public void test01() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
GradeDao dao = (GradeDao) applicationContext.getBean("gradeDao");
dao.searchAll();
}
6.1 事务(Transaction)
在使用 sql 时无法避免事务问题。
我们可以为service中的方法,添加一些事务注释来添加事务,或者通过配置spring文件的xml来添加事务,从而增加程序中对数据库操作的可控性。官方些的解释就是事务的相关属性:原子性(ATOMICITY)、一致性(CONSISTENCY)、隔离性(ISOLATION)、持久性(DURABILITY)。
6.1.1 Annotation
@Override
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<Person> searchAll() {
return dao.searchAll();
}
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
6.1.2 xml
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.practice.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>